diff --git a/src/main/java/org/example/se302/model/ExamAssignment.java b/src/main/java/org/example/se302/model/ExamAssignment.java
index 31b3982..1e64e7e 100644
--- a/src/main/java/org/example/se302/model/ExamAssignment.java
+++ b/src/main/java/org/example/se302/model/ExamAssignment.java
@@ -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.
+ *
+ *
Assignment Structure:
+ *
+ *
+ * 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, ...)
+ * }
+ *
*/
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);
}
}
diff --git a/src/main/java/org/example/se302/model/ScheduleConfiguration.java b/src/main/java/org/example/se302/model/ScheduleConfiguration.java
new file mode 100644
index 0000000..c5af497
--- /dev/null
+++ b/src/main/java/org/example/se302/model/ScheduleConfiguration.java
@@ -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.
+ *
+ * Configuration Parameters:
+ *
+ *
+ * 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
+ * }
+ *
+ */
+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 generateTimeSlots() {
+ List 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();
+ }
+}
diff --git a/src/main/java/org/example/se302/model/ScheduleState.java b/src/main/java/org/example/se302/model/ScheduleState.java
index bf77b1e..cde073a 100644
--- a/src/main/java/org/example/se302/model/ScheduleState.java
+++ b/src/main/java/org/example/se302/model/ScheduleState.java
@@ -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 assignments;
// Quick lookup structures for constraint checking
- private Map> classroomTimeSlotUsage; // classroomId -> set of timeSlot IDs
- private Map> timeSlotCourseMapping; // timeSlotId -> set of course codes
+ private Map> classroomTimeSlotUsage; // classroomId -> set of timeSlot keys
+ private Map> timeSlotCourseMapping; // timeSlotKey -> set of course codes
// Available resources
private List availableTimeSlots;
private List 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 slots = classroomTimeSlotUsage.get(classroomId);
+ if (slots != null) {
+ slots.remove(timeSlotKey);
+ }
+
+ Set 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 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 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 getCoursesAtDaySlot(int day, int timeSlotIndex) {
+ String timeSlotKey = "D" + day + "_S" + timeSlotIndex;
+ Set 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 getCoursesAtTimeSlot(TimeSlot timeSlot) {
Set 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 getAssignedCoursesList() {
+ List 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> getScheduleByDaySlot() {
+ Map> 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 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();
+ }
}