For ScheduleConfiguration.java:

feat(scheduling): add MINIMIZE_CLASSROOMS and BALANCE_CLASSROOMS strategies

  Extended optimization strategies to include classroom-focused options.
  Completes Task 1.5 enum requirements.

  For ScheduleGeneratorService.java:
  feat(scheduling): implement optimization strategy logic in CSP algorithm

  - MINIMIZE_DAYS: Pack exams into fewest days (fills early days first)
  - BALANCED_DISTRIBUTION: Spread exams evenly across all days (round-robin)
  - MINIMIZE_CLASSROOMS: Reuse same classrooms (prefer most-used rooms)
  - BALANCE_CLASSROOMS: Distribute across classrooms (prefer least-used rooms)
This commit is contained in:
sabazadam
2025-12-14 00:30:23 +03:00
parent 452f833ac5
commit 57b80a63b2
2 changed files with 140 additions and 40 deletions

View File

@@ -28,14 +28,21 @@ public class ScheduleConfiguration {
* Optimization strategies for the scheduling algorithm. * Optimization strategies for the scheduling algorithm.
*/ */
public enum OptimizationStrategy { public enum OptimizationStrategy {
/** Minimize total number of days used */ /** Minimize total number of days used - pack exams into fewest days */
MINIMIZE_DAYS, MINIMIZE_DAYS,
/** Spread exams evenly across days */
/** Spread exams evenly across days - balanced distribution */
BALANCED_DISTRIBUTION, BALANCED_DISTRIBUTION,
/** Minimize consecutive exams for students */
/** Minimize number of classrooms used - reuse same classrooms */
MINIMIZE_CLASSROOMS,
/** Balance classroom usage across days - even distribution */
BALANCE_CLASSROOMS,
/** Minimize consecutive exams for students (bonus strategy) */
STUDENT_FRIENDLY, STUDENT_FRIENDLY,
/** Maximize classroom utilization */
MAXIMIZE_ROOM_USAGE,
/** Default balanced approach */ /** Default balanced approach */
DEFAULT DEFAULT
} }

View File

@@ -102,40 +102,41 @@ public class ScheduleGeneratorService {
ExamAssignment assignment = scheduleState.getAssignment(currentCourse.getCourseCode()); ExamAssignment assignment = scheduleState.getAssignment(currentCourse.getCourseCode());
// Try each day and time slot // Get time slots ordered by strategy
for (int day = 0; day < config.getNumDays(); day++) { List<DaySlotPair> orderedTimeSlots = getTimeSlotsOrderedByStrategy(config, scheduleState);
for (int slot = 0; slot < config.getSlotsPerDay(); slot++) {
if (cancelled.get()) {
return false;
}
// Get suitable classrooms for this day/slot // Try each time slot
List<Classroom> suitableClassrooms = getSuitableClassrooms( for (DaySlotPair timeSlot : orderedTimeSlots) {
currentCourse, day, slot, scheduleState); if (cancelled.get()) {
return false;
}
// Try each classroom // Get suitable classrooms for this day/slot (ordered by strategy)
for (Classroom classroom : suitableClassrooms) { List<Classroom> suitableClassrooms = getSuitableClassroomsOrdered(
// Temporarily assign currentCourse, timeSlot.day, timeSlot.slot, scheduleState, config);
assignment.setDay(day);
assignment.setTimeSlotIndex(slot);
assignment.setClassroomId(classroom.getClassroomId());
// Validate assignment // Try each classroom
ConstraintValidator.ValidationResult validationResult = for (Classroom classroom : suitableClassrooms) {
validator.validateAssignment(assignment, scheduleState); // Temporarily assign
assignment.setDay(timeSlot.day);
assignment.setTimeSlotIndex(timeSlot.slot);
assignment.setClassroomId(classroom.getClassroomId());
if (validationResult.isValid()) { // Validate assignment
// Assignment is valid, try to assign remaining courses ConstraintValidator.ValidationResult validationResult =
if (backtrack(scheduleState, courses, courseIndex + 1, config)) { validator.validateAssignment(assignment, scheduleState);
return true; // Success!
} if (validationResult.isValid()) {
// Assignment is valid, try to assign remaining courses
if (backtrack(scheduleState, courses, courseIndex + 1, config)) {
return true; // Success!
} }
// Backtrack: reset assignment
assignment.setDay(-1);
assignment.setTimeSlotIndex(-1);
assignment.setClassroomId(null);
} }
// Backtrack: reset assignment
assignment.setDay(-1);
assignment.setTimeSlotIndex(-1);
assignment.setClassroomId(null);
} }
} }
@@ -160,12 +161,53 @@ public class ScheduleGeneratorService {
} }
/** /**
* Get classrooms suitable for a course at a specific day and time slot. * Get time slots ordered by optimization strategy.
*/ */
private List<Classroom> getSuitableClassrooms(Course course, private List<DaySlotPair> getTimeSlotsOrderedByStrategy(ScheduleConfiguration config, ScheduleState scheduleState) {
int day, List<DaySlotPair> timeSlots = new ArrayList<>();
int timeSlotIndex,
ScheduleState scheduleState) { // Generate all day/slot combinations
for (int day = 0; day < config.getNumDays(); day++) {
for (int slot = 0; slot < config.getSlotsPerDay(); slot++) {
timeSlots.add(new DaySlotPair(day, slot));
}
}
// Order based on strategy
switch (config.getOptimizationStrategy()) {
case MINIMIZE_DAYS:
// Already in order (day 0 slot 0, day 0 slot 1, ... day 1 slot 0, ...)
// This fills earlier days first
break;
case BALANCED_DISTRIBUTION:
// Round-robin across days: day 0 slot 0, day 1 slot 0, day 2 slot 0, ... day 0 slot 1, ...
timeSlots.sort(Comparator.comparingInt((DaySlotPair p) -> p.slot)
.thenComparingInt(p -> p.day));
break;
case STUDENT_FRIENDLY:
// Try to space out exams - prefer later slots on same day to avoid consecutive
// (This is a simple heuristic - more sophisticated would track student conflicts)
break;
default:
// DEFAULT or others: chronological order
break;
}
return timeSlots;
}
/**
* Get classrooms suitable for a course at a specific day and time slot,
* ordered according to optimization strategy.
*/
private List<Classroom> getSuitableClassroomsOrdered(Course course,
int day,
int timeSlotIndex,
ScheduleState scheduleState,
ScheduleConfiguration config) {
List<Classroom> suitable = new ArrayList<>(); List<Classroom> suitable = new ArrayList<>();
for (Classroom classroom : scheduleState.getAvailableClassrooms()) { for (Classroom classroom : scheduleState.getAvailableClassrooms()) {
@@ -191,12 +233,63 @@ public class ScheduleGeneratorService {
} }
} }
// Sort by capacity (prefer smaller classrooms that fit) // Order based on strategy
suitable.sort(Comparator.comparingInt(Classroom::getCapacity)); switch (config.getOptimizationStrategy()) {
case MINIMIZE_CLASSROOMS:
// Prefer classrooms that are already in use (reuse same classrooms)
suitable.sort((c1, c2) -> {
int usage1 = getClassroomUsageCount(c1.getClassroomId(), scheduleState);
int usage2 = getClassroomUsageCount(c2.getClassroomId(), scheduleState);
// Sort descending (most used first)
return Integer.compare(usage2, usage1);
});
break;
case BALANCE_CLASSROOMS:
// Prefer classrooms that are least used
suitable.sort((c1, c2) -> {
int usage1 = getClassroomUsageCount(c1.getClassroomId(), scheduleState);
int usage2 = getClassroomUsageCount(c2.getClassroomId(), scheduleState);
// Sort ascending (least used first)
return Integer.compare(usage1, usage2);
});
break;
default:
// DEFAULT or others: prefer smaller classrooms that fit (efficient space usage)
suitable.sort(Comparator.comparingInt(Classroom::getCapacity));
break;
}
return suitable; return suitable;
} }
/**
* Count how many times a classroom has been used in the current schedule.
*/
private int getClassroomUsageCount(String classroomId, ScheduleState scheduleState) {
int count = 0;
for (ExamAssignment assignment : scheduleState.getAssignments().values()) {
if (assignment.isAssigned() && assignment.getClassroomId().equals(classroomId)) {
count++;
}
}
return count;
}
/**
* Helper class to represent a day/slot pair.
*/
private static class DaySlotPair {
final int day;
final int slot;
DaySlotPair(int day, int slot) {
this.day = day;
this.slot = slot;
}
}
/** /**
* Cancel the current schedule generation. * Cancel the current schedule generation.
*/ */