mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 12:21: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.
|
* 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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.
|
* 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user