feat(model): implement ExamAssignment with day/slot system and ScheduleConfiguration

This commit is contained in:
feyzagereme
2025-12-13 22:57:43 +03:00
parent 3bd461e3b2
commit 1fb89b967d
3 changed files with 635 additions and 37 deletions

View File

@@ -4,20 +4,53 @@ import java.util.Objects;
/** /**
* Represents an exam assignment in the CSP-based scheduling system. * 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. * 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 { public class ExamAssignment {
private String courseCode; private String courseCode;
private TimeSlot timeSlot;
private String classroomId; 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; private int studentCount;
// Optional: reference to the TimeSlot object for detailed time info
private TimeSlot timeSlot;
// Assignment status // Assignment status
private boolean isLocked; // If true, this assignment cannot be changed by the algorithm 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 courseCode The course code for this exam
* @param timeSlot The time slot assigned to this exam * @param timeSlot The time slot assigned to this exam
@@ -27,6 +60,8 @@ public class ExamAssignment {
this.courseCode = courseCode; this.courseCode = courseCode;
this.timeSlot = timeSlot; this.timeSlot = timeSlot;
this.classroomId = classroomId; this.classroomId = classroomId;
this.day = -1;
this.timeSlotIndex = -1;
this.studentCount = 0; this.studentCount = 0;
this.isLocked = false; this.isLocked = false;
} }
@@ -35,47 +70,85 @@ public class ExamAssignment {
* Creates an unassigned exam (only course is known). * Creates an unassigned exam (only course is known).
*/ */
public ExamAssignment(String courseCode) { 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. * Creates a deep copy of this assignment.
*/ */
public ExamAssignment copy() { 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.studentCount = this.studentCount;
copy.isLocked = this.isLocked; copy.isLocked = this.isLocked;
copy.timeSlot = this.timeSlot;
return copy; 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() { 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; return timeSlot != null && classroomId != null;
} }
/** /**
* Checks if this assignment conflicts with another assignment. * Checks if this assignment conflicts with another assignment.
* Two assignments conflict if: * Two assignments conflict if:
* 1. Same classroom at overlapping time slots * 1. Same classroom at the same day and time slot
* 2. (Checked separately) Same student in both courses at overlapping time * 2. (Checked separately) Same student in both courses at the same day/time
* slots
*/ */
public boolean conflictsWith(ExamAssignment other) { public boolean conflictsWith(ExamAssignment other) {
if (!this.isAssigned() || !other.isAssigned()) { if (!this.isAssigned() || !other.isAssigned()) {
return false; return false;
} }
// Check classroom conflict // Check classroom conflict - same room, same day, same slot
if (this.classroomId.equals(other.classroomId) && if (this.classroomId.equals(other.classroomId) &&
this.timeSlot.overlapsWith(other.timeSlot)) { this.day == other.day &&
this.timeSlotIndex == other.timeSlotIndex) {
return true; return true;
} }
return false; 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 // Getters and Setters
public String getCourseCode() { public String getCourseCode() {
@@ -86,14 +159,6 @@ public class ExamAssignment {
this.courseCode = courseCode; this.courseCode = courseCode;
} }
public TimeSlot getTimeSlot() {
return timeSlot;
}
public void setTimeSlot(TimeSlot timeSlot) {
this.timeSlot = timeSlot;
}
public String getClassroomId() { public String getClassroomId() {
return classroomId; return classroomId;
} }
@@ -102,6 +167,30 @@ public class ExamAssignment {
this.classroomId = classroomId; 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() { public int getStudentCount() {
return studentCount; return studentCount;
} }
@@ -125,14 +214,15 @@ public class ExamAssignment {
if (o == null || getClass() != o.getClass()) if (o == null || getClass() != o.getClass())
return false; return false;
ExamAssignment that = (ExamAssignment) o; ExamAssignment that = (ExamAssignment) o;
return Objects.equals(courseCode, that.courseCode) && return day == that.day &&
Objects.equals(timeSlot, that.timeSlot) && timeSlotIndex == that.timeSlotIndex &&
Objects.equals(courseCode, that.courseCode) &&
Objects.equals(classroomId, that.classroomId); Objects.equals(classroomId, that.classroomId);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(courseCode, timeSlot, classroomId); return Objects.hash(courseCode, classroomId, day, timeSlotIndex);
} }
@Override @Override
@@ -140,6 +230,17 @@ public class ExamAssignment {
if (!isAssigned()) { if (!isAssigned()) {
return courseCode + " [Unassigned]"; 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);
} }
} }

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

View File

@@ -6,19 +6,24 @@ import java.util.*;
* Represents the complete state of an exam schedule. * Represents the complete state of an exam schedule.
* This is the main data structure used by the CSP solver to track * This is the main data structure used by the CSP solver to track
* the current schedule state and validate constraints. * the current schedule state and validate constraints.
*
* Supports both TimeSlot-based and day/timeSlotIndex-based assignments.
*/ */
public class ScheduleState { public class ScheduleState {
// All exam assignments indexed by course code // All exam assignments indexed by course code
private Map<String, ExamAssignment> assignments; private Map<String, ExamAssignment> assignments;
// Quick lookup structures for constraint checking // Quick lookup structures for constraint checking
private Map<String, Set<String>> classroomTimeSlotUsage; // classroomId -> set of timeSlot IDs private Map<String, Set<String>> classroomTimeSlotUsage; // classroomId -> set of timeSlot keys
private Map<String, Set<String>> timeSlotCourseMapping; // timeSlotId -> set of course codes private Map<String, Set<String>> timeSlotCourseMapping; // timeSlotKey -> set of course codes
// Available resources // Available resources
private List<TimeSlot> availableTimeSlots; private List<TimeSlot> availableTimeSlots;
private List<Classroom> availableClassrooms; private List<Classroom> availableClassrooms;
// Configuration reference
private ScheduleConfiguration configuration;
// Statistics // Statistics
private int totalCourses; private int totalCourses;
private int assignedCourses; private int assignedCourses;
@@ -30,11 +35,23 @@ public class ScheduleState {
this.timeSlotCourseMapping = new HashMap<>(); this.timeSlotCourseMapping = new HashMap<>();
this.availableTimeSlots = new ArrayList<>(); this.availableTimeSlots = new ArrayList<>();
this.availableClassrooms = new ArrayList<>(); this.availableClassrooms = new ArrayList<>();
this.configuration = null;
this.totalCourses = 0; this.totalCourses = 0;
this.assignedCourses = 0; this.assignedCourses = 0;
this.constraintViolations = 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. * Creates a deep copy of this schedule state.
*/ */
@@ -55,6 +72,7 @@ public class ScheduleState {
copy.availableTimeSlots = new ArrayList<>(this.availableTimeSlots); copy.availableTimeSlots = new ArrayList<>(this.availableTimeSlots);
copy.availableClassrooms = new ArrayList<>(this.availableClassrooms); copy.availableClassrooms = new ArrayList<>(this.availableClassrooms);
copy.configuration = this.configuration;
copy.totalCourses = this.totalCourses; copy.totalCourses = this.totalCourses;
copy.assignedCourses = this.assignedCourses; copy.assignedCourses = this.assignedCourses;
copy.constraintViolations = this.constraintViolations; 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); ExamAssignment assignment = assignments.get(courseCode);
if (assignment == null) { if (assignment == null) {
return false; return false;
@@ -97,9 +116,15 @@ public class ScheduleState {
} }
// Update assignment // Update assignment
assignment.setTimeSlot(timeSlot); assignment.setDay(day);
assignment.setTimeSlotIndex(timeSlotIndex);
assignment.setClassroomId(classroomId); 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 // Add new assignment to lookup structures
if (assignment.isAssigned()) { if (assignment.isAssigned()) {
updateLookupStructures(assignment, true); updateLookupStructures(assignment, true);
@@ -109,6 +134,39 @@ public class ScheduleState {
return true; 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). * Removes an assignment (resets it to unassigned state).
*/ */
@@ -123,6 +181,8 @@ public class ScheduleState {
assignedCourses--; assignedCourses--;
} }
assignment.setDay(-1);
assignment.setTimeSlotIndex(-1);
assignment.setTimeSlot(null); assignment.setTimeSlot(null);
assignment.setClassroomId(null); assignment.setClassroomId(null);
@@ -131,11 +191,39 @@ public class ScheduleState {
/** /**
* Updates the quick lookup structures when an assignment changes. * Updates the quick lookup structures when an assignment changes.
* Uses day/timeSlotIndex-based key.
*/ */
private void updateLookupStructures(ExamAssignment assignment, boolean add) { private void updateLookupStructures(ExamAssignment assignment, boolean add) {
if (!assignment.isAssigned()) if (!assignment.isAssigned())
return; 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 classroomId = assignment.getClassroomId();
String timeSlotId = assignment.getTimeSlot().getId(); String timeSlotId = assignment.getTimeSlot().getId();
String courseCode = assignment.getCourseCode(); 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) { public boolean isClassroomAvailable(String classroomId, TimeSlot timeSlot) {
Set<String> usedSlots = classroomTimeSlotUsage.get(classroomId);
if (usedSlots == null) {
return true;
}
// Check for overlapping time slots // Check for overlapping time slots
for (ExamAssignment assignment : assignments.values()) { for (ExamAssignment assignment : assignments.values()) {
if (assignment.isAssigned() && if (assignment.isAssignedWithTimeSlot() &&
assignment.getClassroomId().equals(classroomId) && assignment.getClassroomId().equals(classroomId) &&
assignment.getTimeSlot().overlapsWith(timeSlot)) { assignment.getTimeSlot().overlapsWith(timeSlot)) {
return false; 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) { public Set<String> getCoursesAtTimeSlot(TimeSlot timeSlot) {
Set<String> courses = new HashSet<>(); Set<String> courses = new HashSet<>();
for (ExamAssignment assignment : assignments.values()) { for (ExamAssignment assignment : assignments.values()) {
if (assignment.isAssigned() && if (assignment.isAssignedWithTimeSlot() &&
assignment.getTimeSlot().overlapsWith(timeSlot)) { assignment.getTimeSlot().overlapsWith(timeSlot)) {
courses.add(assignment.getCourseCode()); courses.add(assignment.getCourseCode());
} }
@@ -204,6 +306,19 @@ public class ScheduleState {
return unassigned; 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). * Checks if the schedule is complete (all courses are assigned).
*/ */
@@ -220,6 +335,22 @@ public class ScheduleState {
return (assignedCourses * 100.0) / totalCourses; 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 // Getters and Setters
public Map<String, ExamAssignment> getAssignments() { public Map<String, ExamAssignment> getAssignments() {
@@ -246,6 +377,14 @@ public class ScheduleState {
this.availableClassrooms = availableClassrooms; this.availableClassrooms = availableClassrooms;
} }
public ScheduleConfiguration getConfiguration() {
return configuration;
}
public void setConfiguration(ScheduleConfiguration configuration) {
this.configuration = configuration;
}
public int getTotalCourses() { public int getTotalCourses() {
return totalCourses; return totalCourses;
} }
@@ -267,4 +406,22 @@ public class ScheduleState {
return String.format("ScheduleState[%d/%d assigned, %.1f%% complete, %d violations]", return String.format("ScheduleState[%d/%d assigned, %.1f%% complete, %d violations]",
assignedCourses, totalCourses, getCompletionPercentage(), constraintViolations); 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();
}
} }