mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 20:31:22 +00:00
feat(model): implement ExamAssignment with day/slot system and ScheduleConfiguration
This commit is contained in:
@@ -4,20 +4,53 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an exam assignment in the CSP-based scheduling system.
|
||||
* An ExamAssignment links a course to a specific time slot and classroom.
|
||||
* An ExamAssignment links a course to a specific day, time slot, and classroom.
|
||||
* This is the core data structure for the schedule state.
|
||||
*
|
||||
* <h3>Assignment Structure:</h3>
|
||||
*
|
||||
* <pre>
|
||||
* ExamAssignment = {
|
||||
* courseCode: "CourseCode_01"
|
||||
* classroomId: "Classroom_01"
|
||||
* day: 0-based index (0 = Day 1, 1 = Day 2, ...)
|
||||
* timeSlotIndex: 0-based index (0 = first slot, 1 = second slot, ...)
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class ExamAssignment {
|
||||
private String courseCode;
|
||||
private TimeSlot timeSlot;
|
||||
private String classroomId;
|
||||
private int day; // 0-based day index (-1 if unassigned)
|
||||
private int timeSlotIndex; // 0-based time slot index (-1 if unassigned)
|
||||
private int studentCount;
|
||||
|
||||
// Optional: reference to the TimeSlot object for detailed time info
|
||||
private TimeSlot timeSlot;
|
||||
|
||||
// Assignment status
|
||||
private boolean isLocked; // If true, this assignment cannot be changed by the algorithm
|
||||
|
||||
/**
|
||||
* Creates a new exam assignment.
|
||||
* Creates a new exam assignment with all details.
|
||||
*
|
||||
* @param courseCode The course code for this exam
|
||||
* @param classroomId The classroom assigned to this exam
|
||||
* @param day The day index (0-based)
|
||||
* @param timeSlotIndex The time slot index within the day (0-based)
|
||||
*/
|
||||
public ExamAssignment(String courseCode, String classroomId, int day, int timeSlotIndex) {
|
||||
this.courseCode = courseCode;
|
||||
this.classroomId = classroomId;
|
||||
this.day = day;
|
||||
this.timeSlotIndex = timeSlotIndex;
|
||||
this.studentCount = 0;
|
||||
this.isLocked = false;
|
||||
this.timeSlot = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new exam assignment with TimeSlot object.
|
||||
*
|
||||
* @param courseCode The course code for this exam
|
||||
* @param timeSlot The time slot assigned to this exam
|
||||
@@ -27,6 +60,8 @@ public class ExamAssignment {
|
||||
this.courseCode = courseCode;
|
||||
this.timeSlot = timeSlot;
|
||||
this.classroomId = classroomId;
|
||||
this.day = -1;
|
||||
this.timeSlotIndex = -1;
|
||||
this.studentCount = 0;
|
||||
this.isLocked = false;
|
||||
}
|
||||
@@ -35,47 +70,85 @@ public class ExamAssignment {
|
||||
* Creates an unassigned exam (only course is known).
|
||||
*/
|
||||
public ExamAssignment(String courseCode) {
|
||||
this(courseCode, null, null);
|
||||
this.courseCode = courseCode;
|
||||
this.classroomId = null;
|
||||
this.day = -1;
|
||||
this.timeSlotIndex = -1;
|
||||
this.studentCount = 0;
|
||||
this.isLocked = false;
|
||||
this.timeSlot = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep copy of this assignment.
|
||||
*/
|
||||
public ExamAssignment copy() {
|
||||
ExamAssignment copy = new ExamAssignment(this.courseCode, this.timeSlot, this.classroomId);
|
||||
ExamAssignment copy = new ExamAssignment(this.courseCode, this.classroomId, this.day, this.timeSlotIndex);
|
||||
copy.studentCount = this.studentCount;
|
||||
copy.isLocked = this.isLocked;
|
||||
copy.timeSlot = this.timeSlot;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this assignment is complete (has both time slot and classroom).
|
||||
* Checks if this assignment is complete (has classroom, day and time slot).
|
||||
*/
|
||||
public boolean isAssigned() {
|
||||
return classroomId != null && day >= 0 && timeSlotIndex >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this assignment is assigned using TimeSlot object.
|
||||
*/
|
||||
public boolean isAssignedWithTimeSlot() {
|
||||
return timeSlot != null && classroomId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this assignment conflicts with another assignment.
|
||||
* Two assignments conflict if:
|
||||
* 1. Same classroom at overlapping time slots
|
||||
* 2. (Checked separately) Same student in both courses at overlapping time
|
||||
* slots
|
||||
* 1. Same classroom at the same day and time slot
|
||||
* 2. (Checked separately) Same student in both courses at the same day/time
|
||||
*/
|
||||
public boolean conflictsWith(ExamAssignment other) {
|
||||
if (!this.isAssigned() || !other.isAssigned()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check classroom conflict
|
||||
// Check classroom conflict - same room, same day, same slot
|
||||
if (this.classroomId.equals(other.classroomId) &&
|
||||
this.timeSlot.overlapsWith(other.timeSlot)) {
|
||||
this.day == other.day &&
|
||||
this.timeSlotIndex == other.timeSlotIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this exam is at the same time as another (regardless of classroom).
|
||||
*/
|
||||
public boolean sameTimeAs(ExamAssignment other) {
|
||||
if (!this.isAssigned() || !other.isAssigned()) {
|
||||
return false;
|
||||
}
|
||||
return this.day == other.day && this.timeSlotIndex == other.timeSlotIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique key for the (day, timeSlot) combination.
|
||||
*/
|
||||
public String getTimeKey() {
|
||||
return "D" + day + "_S" + timeSlotIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique key for the (day, timeSlot, classroom) combination.
|
||||
*/
|
||||
public String getFullKey() {
|
||||
return "D" + day + "_S" + timeSlotIndex + "_" + classroomId;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public String getCourseCode() {
|
||||
@@ -86,14 +159,6 @@ public class ExamAssignment {
|
||||
this.courseCode = courseCode;
|
||||
}
|
||||
|
||||
public TimeSlot getTimeSlot() {
|
||||
return timeSlot;
|
||||
}
|
||||
|
||||
public void setTimeSlot(TimeSlot timeSlot) {
|
||||
this.timeSlot = timeSlot;
|
||||
}
|
||||
|
||||
public String getClassroomId() {
|
||||
return classroomId;
|
||||
}
|
||||
@@ -102,6 +167,30 @@ public class ExamAssignment {
|
||||
this.classroomId = classroomId;
|
||||
}
|
||||
|
||||
public int getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public void setDay(int day) {
|
||||
this.day = day;
|
||||
}
|
||||
|
||||
public int getTimeSlotIndex() {
|
||||
return timeSlotIndex;
|
||||
}
|
||||
|
||||
public void setTimeSlotIndex(int timeSlotIndex) {
|
||||
this.timeSlotIndex = timeSlotIndex;
|
||||
}
|
||||
|
||||
public TimeSlot getTimeSlot() {
|
||||
return timeSlot;
|
||||
}
|
||||
|
||||
public void setTimeSlot(TimeSlot timeSlot) {
|
||||
this.timeSlot = timeSlot;
|
||||
}
|
||||
|
||||
public int getStudentCount() {
|
||||
return studentCount;
|
||||
}
|
||||
@@ -125,14 +214,15 @@ public class ExamAssignment {
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
ExamAssignment that = (ExamAssignment) o;
|
||||
return Objects.equals(courseCode, that.courseCode) &&
|
||||
Objects.equals(timeSlot, that.timeSlot) &&
|
||||
return day == that.day &&
|
||||
timeSlotIndex == that.timeSlotIndex &&
|
||||
Objects.equals(courseCode, that.courseCode) &&
|
||||
Objects.equals(classroomId, that.classroomId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(courseCode, timeSlot, classroomId);
|
||||
return Objects.hash(courseCode, classroomId, day, timeSlotIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,6 +230,17 @@ public class ExamAssignment {
|
||||
if (!isAssigned()) {
|
||||
return courseCode + " [Unassigned]";
|
||||
}
|
||||
return courseCode + " -> " + classroomId + " @ " + timeSlot;
|
||||
return courseCode + " -> " + classroomId + " @ Day" + (day + 1) + " Slot" + (timeSlotIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description of the assignment.
|
||||
*/
|
||||
public String toDisplayString() {
|
||||
if (!isAssigned()) {
|
||||
return courseCode + " - Not Scheduled";
|
||||
}
|
||||
return String.format("%s | Day %d, Slot %d | Room: %s | Students: %d",
|
||||
courseCode, day + 1, timeSlotIndex + 1, classroomId, studentCount);
|
||||
}
|
||||
}
|
||||
|
||||
340
src/main/java/org/example/se302/model/ScheduleConfiguration.java
Normal file
340
src/main/java/org/example/se302/model/ScheduleConfiguration.java
Normal file
@@ -0,0 +1,340 @@
|
||||
package org.example.se302.model;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Configuration for exam schedule generation.
|
||||
* Defines the scheduling parameters and optimization strategy.
|
||||
*
|
||||
* <h3>Configuration Parameters:</h3>
|
||||
*
|
||||
* <pre>
|
||||
* ScheduleConfiguration = {
|
||||
* numDays: Number of exam days (e.g., 5)
|
||||
* slotsPerDay: Number of time slots per day (e.g., 4)
|
||||
* startDate: First day of exam period
|
||||
* slotDuration: Duration of each slot in minutes (e.g., 120)
|
||||
* dayStartTime: First slot start time (e.g., 09:00)
|
||||
* optimizationStrategy: Strategy for optimization
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class ScheduleConfiguration {
|
||||
|
||||
/**
|
||||
* Optimization strategies for the scheduling algorithm.
|
||||
*/
|
||||
public enum OptimizationStrategy {
|
||||
/** Minimize total number of days used */
|
||||
MINIMIZE_DAYS,
|
||||
/** Spread exams evenly across days */
|
||||
BALANCED_DISTRIBUTION,
|
||||
/** Minimize consecutive exams for students */
|
||||
STUDENT_FRIENDLY,
|
||||
/** Maximize classroom utilization */
|
||||
MAXIMIZE_ROOM_USAGE,
|
||||
/** Default balanced approach */
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
// Core scheduling parameters
|
||||
private int numDays;
|
||||
private int slotsPerDay;
|
||||
private LocalDate startDate;
|
||||
|
||||
// Time configuration
|
||||
private int slotDurationMinutes;
|
||||
private LocalTime dayStartTime;
|
||||
private int breakBetweenSlotsMinutes;
|
||||
|
||||
// Optimization settings
|
||||
private OptimizationStrategy optimizationStrategy;
|
||||
private boolean allowBackToBackExams;
|
||||
private int maxExamsPerDay;
|
||||
|
||||
// Algorithm parameters
|
||||
private long timeoutMs;
|
||||
private boolean useHeuristics;
|
||||
|
||||
/**
|
||||
* Creates a default schedule configuration.
|
||||
*/
|
||||
public ScheduleConfiguration() {
|
||||
this.numDays = 5;
|
||||
this.slotsPerDay = 4;
|
||||
this.startDate = LocalDate.now().plusDays(7); // Default: start in a week
|
||||
this.slotDurationMinutes = 120; // 2 hours
|
||||
this.dayStartTime = LocalTime.of(9, 0); // 09:00
|
||||
this.breakBetweenSlotsMinutes = 30; // 30 minute break
|
||||
this.optimizationStrategy = OptimizationStrategy.DEFAULT;
|
||||
this.allowBackToBackExams = true;
|
||||
this.maxExamsPerDay = 0; // 0 = no limit
|
||||
this.timeoutMs = 60000; // 60 seconds
|
||||
this.useHeuristics = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a schedule configuration with specified days and slots.
|
||||
*
|
||||
* @param numDays Number of exam days
|
||||
* @param slotsPerDay Number of time slots per day
|
||||
*/
|
||||
public ScheduleConfiguration(int numDays, int slotsPerDay) {
|
||||
this();
|
||||
this.numDays = numDays;
|
||||
this.slotsPerDay = slotsPerDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a schedule configuration with all core parameters.
|
||||
*
|
||||
* @param numDays Number of exam days
|
||||
* @param slotsPerDay Number of time slots per day
|
||||
* @param optimizationStrategy Strategy for optimization
|
||||
*/
|
||||
public ScheduleConfiguration(int numDays, int slotsPerDay, OptimizationStrategy optimizationStrategy) {
|
||||
this(numDays, slotsPerDay);
|
||||
this.optimizationStrategy = optimizationStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of available time slots across all days.
|
||||
*/
|
||||
public int getTotalSlots() {
|
||||
return numDays * slotsPerDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of TimeSlot objects based on this configuration.
|
||||
*
|
||||
* @return List of all available TimeSlots
|
||||
*/
|
||||
public List<TimeSlot> generateTimeSlots() {
|
||||
List<TimeSlot> timeSlots = new ArrayList<>();
|
||||
|
||||
LocalDate currentDate = startDate;
|
||||
for (int day = 0; day < numDays; day++) {
|
||||
LocalTime currentTime = dayStartTime;
|
||||
|
||||
for (int slot = 0; slot < slotsPerDay; slot++) {
|
||||
LocalTime endTime = currentTime.plusMinutes(slotDurationMinutes);
|
||||
timeSlots.add(new TimeSlot(currentDate, currentTime, endTime));
|
||||
|
||||
// Move to next slot (add duration + break)
|
||||
currentTime = endTime.plusMinutes(breakBetweenSlotsMinutes);
|
||||
}
|
||||
|
||||
// Move to next day
|
||||
currentDate = currentDate.plusDays(1);
|
||||
}
|
||||
|
||||
return timeSlots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TimeSlot for a specific day and slot index.
|
||||
*
|
||||
* @param day Day index (0-based)
|
||||
* @param slotIndex Slot index within the day (0-based)
|
||||
* @return The corresponding TimeSlot
|
||||
*/
|
||||
public TimeSlot getTimeSlot(int day, int slotIndex) {
|
||||
if (day < 0 || day >= numDays || slotIndex < 0 || slotIndex >= slotsPerDay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalDate date = startDate.plusDays(day);
|
||||
LocalTime startTime = dayStartTime;
|
||||
|
||||
// Calculate start time for this slot
|
||||
for (int i = 0; i < slotIndex; i++) {
|
||||
startTime = startTime.plusMinutes(slotDurationMinutes + breakBetweenSlotsMinutes);
|
||||
}
|
||||
|
||||
LocalTime endTime = startTime.plusMinutes(slotDurationMinutes);
|
||||
return new TimeSlot(date, startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a flat slot index to (day, slotIndex) pair.
|
||||
*
|
||||
* @param flatIndex Flat index (0 to totalSlots-1)
|
||||
* @return Array of [day, slotIndex]
|
||||
*/
|
||||
public int[] flatIndexToDaySlot(int flatIndex) {
|
||||
int day = flatIndex / slotsPerDay;
|
||||
int slot = flatIndex % slotsPerDay;
|
||||
return new int[] { day, slot };
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts (day, slotIndex) to a flat slot index.
|
||||
*
|
||||
* @param day Day index
|
||||
* @param slotIndex Slot index
|
||||
* @return Flat index
|
||||
*/
|
||||
public int daySlotToFlatIndex(int day, int slotIndex) {
|
||||
return day * slotsPerDay + slotIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a display name for a specific slot.
|
||||
*
|
||||
* @param day Day index (0-based)
|
||||
* @param slotIndex Slot index (0-based)
|
||||
* @return Display name like "Day 1 - 09:00-11:00"
|
||||
*/
|
||||
public String getSlotDisplayName(int day, int slotIndex) {
|
||||
TimeSlot slot = getTimeSlot(day, slotIndex);
|
||||
if (slot == null) {
|
||||
return "Invalid Slot";
|
||||
}
|
||||
return String.format("Day %d - %s", day + 1, slot.getStartTime() + "-" + slot.getEndTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the configuration.
|
||||
*
|
||||
* @return Error message if invalid, null if valid
|
||||
*/
|
||||
public String validate() {
|
||||
if (numDays <= 0) {
|
||||
return "Number of days must be positive";
|
||||
}
|
||||
if (slotsPerDay <= 0) {
|
||||
return "Slots per day must be positive";
|
||||
}
|
||||
if (slotDurationMinutes <= 0) {
|
||||
return "Slot duration must be positive";
|
||||
}
|
||||
if (startDate == null) {
|
||||
return "Start date is required";
|
||||
}
|
||||
if (dayStartTime == null) {
|
||||
return "Day start time is required";
|
||||
}
|
||||
if (timeoutMs <= 0) {
|
||||
return "Timeout must be positive";
|
||||
}
|
||||
return null; // Valid
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public int getNumDays() {
|
||||
return numDays;
|
||||
}
|
||||
|
||||
public void setNumDays(int numDays) {
|
||||
this.numDays = numDays;
|
||||
}
|
||||
|
||||
public int getSlotsPerDay() {
|
||||
return slotsPerDay;
|
||||
}
|
||||
|
||||
public void setSlotsPerDay(int slotsPerDay) {
|
||||
this.slotsPerDay = slotsPerDay;
|
||||
}
|
||||
|
||||
public LocalDate getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(LocalDate startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public int getSlotDurationMinutes() {
|
||||
return slotDurationMinutes;
|
||||
}
|
||||
|
||||
public void setSlotDurationMinutes(int slotDurationMinutes) {
|
||||
this.slotDurationMinutes = slotDurationMinutes;
|
||||
}
|
||||
|
||||
public LocalTime getDayStartTime() {
|
||||
return dayStartTime;
|
||||
}
|
||||
|
||||
public void setDayStartTime(LocalTime dayStartTime) {
|
||||
this.dayStartTime = dayStartTime;
|
||||
}
|
||||
|
||||
public int getBreakBetweenSlotsMinutes() {
|
||||
return breakBetweenSlotsMinutes;
|
||||
}
|
||||
|
||||
public void setBreakBetweenSlotsMinutes(int breakBetweenSlotsMinutes) {
|
||||
this.breakBetweenSlotsMinutes = breakBetweenSlotsMinutes;
|
||||
}
|
||||
|
||||
public OptimizationStrategy getOptimizationStrategy() {
|
||||
return optimizationStrategy;
|
||||
}
|
||||
|
||||
public void setOptimizationStrategy(OptimizationStrategy optimizationStrategy) {
|
||||
this.optimizationStrategy = optimizationStrategy;
|
||||
}
|
||||
|
||||
public boolean isAllowBackToBackExams() {
|
||||
return allowBackToBackExams;
|
||||
}
|
||||
|
||||
public void setAllowBackToBackExams(boolean allowBackToBackExams) {
|
||||
this.allowBackToBackExams = allowBackToBackExams;
|
||||
}
|
||||
|
||||
public int getMaxExamsPerDay() {
|
||||
return maxExamsPerDay;
|
||||
}
|
||||
|
||||
public void setMaxExamsPerDay(int maxExamsPerDay) {
|
||||
this.maxExamsPerDay = maxExamsPerDay;
|
||||
}
|
||||
|
||||
public long getTimeoutMs() {
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
public void setTimeoutMs(long timeoutMs) {
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
public boolean isUseHeuristics() {
|
||||
return useHeuristics;
|
||||
}
|
||||
|
||||
public void setUseHeuristics(boolean useHeuristics) {
|
||||
this.useHeuristics = useHeuristics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ScheduleConfiguration[%d days, %d slots/day, strategy=%s, start=%s]",
|
||||
numDays, slotsPerDay, optimizationStrategy, startDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a detailed summary of the configuration.
|
||||
*/
|
||||
public String toDetailedString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("=== Schedule Configuration ===\n");
|
||||
sb.append(String.format("Days: %d\n", numDays));
|
||||
sb.append(String.format("Slots per day: %d\n", slotsPerDay));
|
||||
sb.append(String.format("Total slots: %d\n", getTotalSlots()));
|
||||
sb.append(String.format("Start date: %s\n", startDate));
|
||||
sb.append(String.format("Day starts at: %s\n", dayStartTime));
|
||||
sb.append(String.format("Slot duration: %d minutes\n", slotDurationMinutes));
|
||||
sb.append(String.format("Break between slots: %d minutes\n", breakBetweenSlotsMinutes));
|
||||
sb.append(String.format("Optimization: %s\n", optimizationStrategy));
|
||||
sb.append(String.format("Allow back-to-back: %s\n", allowBackToBackExams));
|
||||
sb.append(String.format("Timeout: %d ms\n", timeoutMs));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,24 @@ import java.util.*;
|
||||
* Represents the complete state of an exam schedule.
|
||||
* This is the main data structure used by the CSP solver to track
|
||||
* the current schedule state and validate constraints.
|
||||
*
|
||||
* Supports both TimeSlot-based and day/timeSlotIndex-based assignments.
|
||||
*/
|
||||
public class ScheduleState {
|
||||
// All exam assignments indexed by course code
|
||||
private Map<String, ExamAssignment> assignments;
|
||||
|
||||
// Quick lookup structures for constraint checking
|
||||
private Map<String, Set<String>> classroomTimeSlotUsage; // classroomId -> set of timeSlot IDs
|
||||
private Map<String, Set<String>> timeSlotCourseMapping; // timeSlotId -> set of course codes
|
||||
private Map<String, Set<String>> classroomTimeSlotUsage; // classroomId -> set of timeSlot keys
|
||||
private Map<String, Set<String>> timeSlotCourseMapping; // timeSlotKey -> set of course codes
|
||||
|
||||
// Available resources
|
||||
private List<TimeSlot> availableTimeSlots;
|
||||
private List<Classroom> availableClassrooms;
|
||||
|
||||
// Configuration reference
|
||||
private ScheduleConfiguration configuration;
|
||||
|
||||
// Statistics
|
||||
private int totalCourses;
|
||||
private int assignedCourses;
|
||||
@@ -30,11 +35,23 @@ public class ScheduleState {
|
||||
this.timeSlotCourseMapping = new HashMap<>();
|
||||
this.availableTimeSlots = new ArrayList<>();
|
||||
this.availableClassrooms = new ArrayList<>();
|
||||
this.configuration = null;
|
||||
this.totalCourses = 0;
|
||||
this.assignedCourses = 0;
|
||||
this.constraintViolations = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ScheduleState with configuration.
|
||||
*/
|
||||
public ScheduleState(ScheduleConfiguration configuration) {
|
||||
this();
|
||||
this.configuration = configuration;
|
||||
if (configuration != null) {
|
||||
this.availableTimeSlots = configuration.generateTimeSlots();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep copy of this schedule state.
|
||||
*/
|
||||
@@ -55,6 +72,7 @@ public class ScheduleState {
|
||||
|
||||
copy.availableTimeSlots = new ArrayList<>(this.availableTimeSlots);
|
||||
copy.availableClassrooms = new ArrayList<>(this.availableClassrooms);
|
||||
copy.configuration = this.configuration;
|
||||
copy.totalCourses = this.totalCourses;
|
||||
copy.assignedCourses = this.assignedCourses;
|
||||
copy.constraintViolations = this.constraintViolations;
|
||||
@@ -78,9 +96,10 @@ public class ScheduleState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing assignment with new time slot and classroom.
|
||||
* Updates an existing assignment with new day, time slot index, and classroom.
|
||||
* This is the preferred method for the new day/slot-based system.
|
||||
*/
|
||||
public boolean updateAssignment(String courseCode, TimeSlot timeSlot, String classroomId) {
|
||||
public boolean updateAssignment(String courseCode, int day, int timeSlotIndex, String classroomId) {
|
||||
ExamAssignment assignment = assignments.get(courseCode);
|
||||
if (assignment == null) {
|
||||
return false;
|
||||
@@ -97,9 +116,15 @@ public class ScheduleState {
|
||||
}
|
||||
|
||||
// Update assignment
|
||||
assignment.setTimeSlot(timeSlot);
|
||||
assignment.setDay(day);
|
||||
assignment.setTimeSlotIndex(timeSlotIndex);
|
||||
assignment.setClassroomId(classroomId);
|
||||
|
||||
// Also set TimeSlot if configuration is available
|
||||
if (configuration != null) {
|
||||
assignment.setTimeSlot(configuration.getTimeSlot(day, timeSlotIndex));
|
||||
}
|
||||
|
||||
// Add new assignment to lookup structures
|
||||
if (assignment.isAssigned()) {
|
||||
updateLookupStructures(assignment, true);
|
||||
@@ -109,6 +134,39 @@ public class ScheduleState {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing assignment with new time slot and classroom.
|
||||
* Legacy method for TimeSlot-based system.
|
||||
*/
|
||||
public boolean updateAssignment(String courseCode, TimeSlot timeSlot, String classroomId) {
|
||||
ExamAssignment assignment = assignments.get(courseCode);
|
||||
if (assignment == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (assignment.isLocked()) {
|
||||
return false; // Cannot modify locked assignments
|
||||
}
|
||||
|
||||
// Remove old assignment from lookup structures
|
||||
if (assignment.isAssigned() || assignment.isAssignedWithTimeSlot()) {
|
||||
updateLookupStructures(assignment, false);
|
||||
assignedCourses--;
|
||||
}
|
||||
|
||||
// Update assignment
|
||||
assignment.setTimeSlot(timeSlot);
|
||||
assignment.setClassroomId(classroomId);
|
||||
|
||||
// Add new assignment to lookup structures
|
||||
if (assignment.isAssignedWithTimeSlot()) {
|
||||
updateLookupStructuresTimeSlot(assignment, true);
|
||||
assignedCourses++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an assignment (resets it to unassigned state).
|
||||
*/
|
||||
@@ -123,6 +181,8 @@ public class ScheduleState {
|
||||
assignedCourses--;
|
||||
}
|
||||
|
||||
assignment.setDay(-1);
|
||||
assignment.setTimeSlotIndex(-1);
|
||||
assignment.setTimeSlot(null);
|
||||
assignment.setClassroomId(null);
|
||||
|
||||
@@ -131,11 +191,39 @@ public class ScheduleState {
|
||||
|
||||
/**
|
||||
* Updates the quick lookup structures when an assignment changes.
|
||||
* Uses day/timeSlotIndex-based key.
|
||||
*/
|
||||
private void updateLookupStructures(ExamAssignment assignment, boolean add) {
|
||||
if (!assignment.isAssigned())
|
||||
return;
|
||||
|
||||
String classroomId = assignment.getClassroomId();
|
||||
String timeSlotKey = assignment.getTimeKey(); // D0_S0 format
|
||||
String courseCode = assignment.getCourseCode();
|
||||
|
||||
if (add) {
|
||||
classroomTimeSlotUsage.computeIfAbsent(classroomId, k -> new HashSet<>()).add(timeSlotKey);
|
||||
timeSlotCourseMapping.computeIfAbsent(timeSlotKey, k -> new HashSet<>()).add(courseCode);
|
||||
} else {
|
||||
Set<String> slots = classroomTimeSlotUsage.get(classroomId);
|
||||
if (slots != null) {
|
||||
slots.remove(timeSlotKey);
|
||||
}
|
||||
|
||||
Set<String> courses = timeSlotCourseMapping.get(timeSlotKey);
|
||||
if (courses != null) {
|
||||
courses.remove(courseCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates lookup structures using TimeSlot ID (legacy method).
|
||||
*/
|
||||
private void updateLookupStructuresTimeSlot(ExamAssignment assignment, boolean add) {
|
||||
if (!assignment.isAssignedWithTimeSlot())
|
||||
return;
|
||||
|
||||
String classroomId = assignment.getClassroomId();
|
||||
String timeSlotId = assignment.getTimeSlot().getId();
|
||||
String courseCode = assignment.getCourseCode();
|
||||
@@ -157,17 +245,22 @@ public class ScheduleState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a classroom is available at a given time slot.
|
||||
* Checks if a classroom is available at a given day and time slot.
|
||||
*/
|
||||
public boolean isClassroomAvailable(String classroomId, int day, int timeSlotIndex) {
|
||||
String timeSlotKey = "D" + day + "_S" + timeSlotIndex;
|
||||
Set<String> usedSlots = classroomTimeSlotUsage.get(classroomId);
|
||||
|
||||
return usedSlots == null || !usedSlots.contains(timeSlotKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a classroom is available at a given time slot (legacy method).
|
||||
*/
|
||||
public boolean isClassroomAvailable(String classroomId, TimeSlot timeSlot) {
|
||||
Set<String> usedSlots = classroomTimeSlotUsage.get(classroomId);
|
||||
if (usedSlots == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for overlapping time slots
|
||||
for (ExamAssignment assignment : assignments.values()) {
|
||||
if (assignment.isAssigned() &&
|
||||
if (assignment.isAssignedWithTimeSlot() &&
|
||||
assignment.getClassroomId().equals(classroomId) &&
|
||||
assignment.getTimeSlot().overlapsWith(timeSlot)) {
|
||||
return false;
|
||||
@@ -178,12 +271,21 @@ public class ScheduleState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all courses scheduled at a specific time slot.
|
||||
* Gets all courses scheduled at a specific day and time slot.
|
||||
*/
|
||||
public Set<String> getCoursesAtDaySlot(int day, int timeSlotIndex) {
|
||||
String timeSlotKey = "D" + day + "_S" + timeSlotIndex;
|
||||
Set<String> courses = timeSlotCourseMapping.get(timeSlotKey);
|
||||
return courses != null ? new HashSet<>(courses) : new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all courses scheduled at a specific time slot (legacy method).
|
||||
*/
|
||||
public Set<String> getCoursesAtTimeSlot(TimeSlot timeSlot) {
|
||||
Set<String> courses = new HashSet<>();
|
||||
for (ExamAssignment assignment : assignments.values()) {
|
||||
if (assignment.isAssigned() &&
|
||||
if (assignment.isAssignedWithTimeSlot() &&
|
||||
assignment.getTimeSlot().overlapsWith(timeSlot)) {
|
||||
courses.add(assignment.getCourseCode());
|
||||
}
|
||||
@@ -204,6 +306,19 @@ public class ScheduleState {
|
||||
return unassigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all assigned courses.
|
||||
*/
|
||||
public List<ExamAssignment> getAssignedCoursesList() {
|
||||
List<ExamAssignment> assigned = new ArrayList<>();
|
||||
for (ExamAssignment assignment : assignments.values()) {
|
||||
if (assignment.isAssigned()) {
|
||||
assigned.add(assignment);
|
||||
}
|
||||
}
|
||||
return assigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the schedule is complete (all courses are assigned).
|
||||
*/
|
||||
@@ -220,6 +335,22 @@ public class ScheduleState {
|
||||
return (assignedCourses * 100.0) / totalCourses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schedule as a 2D structure [day][slot] -> list of assignments.
|
||||
*/
|
||||
public Map<String, List<ExamAssignment>> getScheduleByDaySlot() {
|
||||
Map<String, List<ExamAssignment>> schedule = new TreeMap<>();
|
||||
|
||||
for (ExamAssignment assignment : assignments.values()) {
|
||||
if (assignment.isAssigned()) {
|
||||
String key = assignment.getTimeKey();
|
||||
schedule.computeIfAbsent(key, k -> new ArrayList<>()).add(assignment);
|
||||
}
|
||||
}
|
||||
|
||||
return schedule;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public Map<String, ExamAssignment> getAssignments() {
|
||||
@@ -246,6 +377,14 @@ public class ScheduleState {
|
||||
this.availableClassrooms = availableClassrooms;
|
||||
}
|
||||
|
||||
public ScheduleConfiguration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setConfiguration(ScheduleConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public int getTotalCourses() {
|
||||
return totalCourses;
|
||||
}
|
||||
@@ -267,4 +406,22 @@ public class ScheduleState {
|
||||
return String.format("ScheduleState[%d/%d assigned, %.1f%% complete, %d violations]",
|
||||
assignedCourses, totalCourses, getCompletionPercentage(), constraintViolations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a detailed summary of the schedule.
|
||||
*/
|
||||
public String toDetailedString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("=== Schedule State ===\n");
|
||||
sb.append(String.format("Assigned: %d/%d (%.1f%%)\n",
|
||||
assignedCourses, totalCourses, getCompletionPercentage()));
|
||||
sb.append(String.format("Violations: %d\n", constraintViolations));
|
||||
sb.append("\nAssignments:\n");
|
||||
|
||||
for (ExamAssignment assignment : assignments.values()) {
|
||||
sb.append(" ").append(assignment.toDisplayString()).append("\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user