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(); + } }