mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 12:21:22 +00:00
Reimplemented Different Optimization Strategies
This commit is contained in:
@@ -112,13 +112,16 @@ public class ScheduleGeneratorService {
|
|||||||
boolean assigned = false;
|
boolean assigned = false;
|
||||||
String failureReason = "";
|
String failureReason = "";
|
||||||
|
|
||||||
// Try each day and slot
|
// Find all valid slots for this course
|
||||||
dayLoop: for (int day = 0; day < config.getNumDays() && !assigned; day++) {
|
List<AssignmentCandidate> candidates = new ArrayList<>();
|
||||||
for (int slot = 0; slot < config.getSlotsPerDay() && !assigned; slot++) {
|
|
||||||
|
// Try each day and slot to find all valid candidates
|
||||||
|
for (int day = 0; day < config.getNumDays(); day++) {
|
||||||
|
for (int slot = 0; slot < config.getSlotsPerDay(); slot++) {
|
||||||
// Check if this slot is valid for all enrolled students
|
// Check if this slot is valid for all enrolled students
|
||||||
String slotKey = day + "-" + slot;
|
String slotKey = day + "-" + slot;
|
||||||
boolean slotValid = true;
|
boolean slotValid = true;
|
||||||
String reason = "";
|
// String reason = ""; // Reason not needed for non-blocking check
|
||||||
|
|
||||||
for (String studentId : enrolledStudents) {
|
for (String studentId : enrolledStudents) {
|
||||||
// Check constraint 1: No concurrent exams
|
// Check constraint 1: No concurrent exams
|
||||||
@@ -126,7 +129,6 @@ public class ScheduleGeneratorService {
|
|||||||
for (int[] exam : studentExamList) {
|
for (int[] exam : studentExamList) {
|
||||||
if (exam[0] == day && exam[1] == slot) {
|
if (exam[0] == day && exam[1] == slot) {
|
||||||
slotValid = false;
|
slotValid = false;
|
||||||
reason = "Student " + studentId + " already has exam at this time";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,8 +143,6 @@ public class ScheduleGeneratorService {
|
|||||||
}
|
}
|
||||||
if (examsToday >= MAX_EXAMS_PER_DAY) {
|
if (examsToday >= MAX_EXAMS_PER_DAY) {
|
||||||
slotValid = false;
|
slotValid = false;
|
||||||
reason = "Student " + studentId + " already has " + MAX_EXAMS_PER_DAY + " exams on day "
|
|
||||||
+ (day + 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +152,6 @@ public class ScheduleGeneratorService {
|
|||||||
int slotDiff = Math.abs(exam[1] - slot);
|
int slotDiff = Math.abs(exam[1] - slot);
|
||||||
if (slotDiff > 0 && slotDiff <= MIN_BREAK_SLOTS) {
|
if (slotDiff > 0 && slotDiff <= MIN_BREAK_SLOTS) {
|
||||||
slotValid = false;
|
slotValid = false;
|
||||||
reason = "Student " + studentId + " needs at least 1 hour break between exams";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +161,6 @@ public class ScheduleGeneratorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!slotValid) {
|
if (!slotValid) {
|
||||||
failureReason = reason;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,42 +180,45 @@ public class ScheduleGeneratorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedClassroom == null) {
|
if (selectedClassroom != null) {
|
||||||
failureReason = "No available classroom with sufficient capacity at day " + (day + 1) + " slot "
|
candidates.add(new AssignmentCandidate(day, slot, selectedClassroom));
|
||||||
+ (slot + 1);
|
}
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All constraints satisfied - assign the exam
|
if (candidates.isEmpty()) {
|
||||||
|
return ScheduleResult.failure(
|
||||||
|
String.format("Could not schedule course %s. No valid time slots found. " +
|
||||||
|
"Scheduled %d/%d courses before failure. " +
|
||||||
|
"Try increasing days/slots or reducing course conflicts.",
|
||||||
|
course.getCourseCode(), scheduledCount, totalCourses));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the best candidate based on optimization strategy
|
||||||
|
AssignmentCandidate bestCandidate = selectBestCandidate(candidates, config, studentExams, enrolledStudents,
|
||||||
|
slotClassrooms);
|
||||||
|
|
||||||
|
// Assign the exam
|
||||||
ExamAssignment assignment = scheduleState.getAssignment(course.getCourseCode());
|
ExamAssignment assignment = scheduleState.getAssignment(course.getCourseCode());
|
||||||
scheduleState.updateAssignment(
|
scheduleState.updateAssignment(
|
||||||
assignment.getCourseCode(),
|
assignment.getCourseCode(),
|
||||||
day,
|
bestCandidate.day,
|
||||||
slot,
|
bestCandidate.slot,
|
||||||
selectedClassroom.getClassroomId());
|
bestCandidate.classroom.getClassroomId());
|
||||||
|
|
||||||
// Update tracking structures
|
// Update tracking structures
|
||||||
for (String studentId : enrolledStudents) {
|
for (String studentId : enrolledStudents) {
|
||||||
studentExams.computeIfAbsent(studentId, k -> new ArrayList<>())
|
studentExams.computeIfAbsent(studentId, k -> new ArrayList<>())
|
||||||
.add(new int[] { day, slot });
|
.add(new int[] { bestCandidate.day, bestCandidate.slot });
|
||||||
}
|
}
|
||||||
|
|
||||||
usedClassrooms.add(selectedClassroom.getClassroomId());
|
String slotKey = bestCandidate.day + "-" + bestCandidate.slot;
|
||||||
|
Set<String> usedClassrooms = slotClassrooms.getOrDefault(slotKey, new HashSet<>());
|
||||||
|
usedClassrooms.add(bestCandidate.classroom.getClassroomId());
|
||||||
slotClassrooms.put(slotKey, usedClassrooms);
|
slotClassrooms.put(slotKey, usedClassrooms);
|
||||||
|
|
||||||
assigned = true;
|
|
||||||
scheduledCount++;
|
scheduledCount++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!assigned) {
|
|
||||||
return ScheduleResult.failure(
|
|
||||||
String.format("Could not schedule course %s. %s. " +
|
|
||||||
"Scheduled %d/%d courses before failure. " +
|
|
||||||
"Try increasing days/slots or reducing course conflicts.",
|
|
||||||
course.getCourseCode(), failureReason, scheduledCount, totalCourses));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate final schedule
|
// Validate final schedule
|
||||||
int violations = validateSchedule(scheduleState, studentExams);
|
int violations = validateSchedule(scheduleState, studentExams);
|
||||||
@@ -411,4 +412,114 @@ public class ScheduleGeneratorService {
|
|||||||
return wasCancelled;
|
return wasCancelled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the best assignment candidate based on the optimization strategy.
|
||||||
|
*/
|
||||||
|
private AssignmentCandidate selectBestCandidate(
|
||||||
|
List<AssignmentCandidate> candidates,
|
||||||
|
ScheduleConfiguration config,
|
||||||
|
Map<String, List<int[]>> studentExams,
|
||||||
|
Set<String> enrolledStudents,
|
||||||
|
Map<String, Set<String>> slotClassrooms) {
|
||||||
|
|
||||||
|
ScheduleConfiguration.OptimizationStrategy strategy = config.getOptimizationStrategy();
|
||||||
|
|
||||||
|
// Handle default/null strategy
|
||||||
|
if (strategy == null || strategy == ScheduleConfiguration.OptimizationStrategy.DEFAULT) {
|
||||||
|
strategy = ScheduleConfiguration.OptimizationStrategy.STUDENT_FRIENDLY;
|
||||||
|
} else if (strategy == ScheduleConfiguration.OptimizationStrategy.BALANCED_DISTRIBUTION) {
|
||||||
|
strategy = ScheduleConfiguration.OptimizationStrategy.STUDENT_FRIENDLY;
|
||||||
|
} else if (strategy == ScheduleConfiguration.OptimizationStrategy.BALANCE_CLASSROOMS) {
|
||||||
|
strategy = ScheduleConfiguration.OptimizationStrategy.MINIMIZE_CLASSROOMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (strategy) {
|
||||||
|
case MINIMIZE_DAYS:
|
||||||
|
// Sort by Day (primary) then Slot (secondary)
|
||||||
|
candidates.sort(Comparator.comparingInt((AssignmentCandidate c) -> c.day)
|
||||||
|
.thenComparingInt(c -> c.slot));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MINIMIZE_CLASSROOMS:
|
||||||
|
// Sort by Number of Exams in that Slot (Ascending)
|
||||||
|
// To minimize MAX concurrent classrooms, we should prefer slots with FEWER
|
||||||
|
// exams
|
||||||
|
// so we don't increase the peak usage.
|
||||||
|
candidates.sort(Comparator.comparingInt((AssignmentCandidate c) -> {
|
||||||
|
String key = c.day + "-" + c.slot;
|
||||||
|
return slotClassrooms.getOrDefault(key, Collections.emptySet()).size();
|
||||||
|
}).thenComparingInt(c -> c.day)); // Tiebreaker: earlier days
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STUDENT_FRIENDLY:
|
||||||
|
default:
|
||||||
|
// Sort by Penalty Score (Lower is better)
|
||||||
|
candidates.sort(Comparator.comparingDouble((AssignmentCandidate c) -> {
|
||||||
|
return calculateStudentFriendlyPenalty(c, studentExams, enrolledStudents);
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return best (first)
|
||||||
|
return candidates.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates penalty for student-friendly reference.
|
||||||
|
* Higher penalty = worse for students.
|
||||||
|
*/
|
||||||
|
private double calculateStudentFriendlyPenalty(
|
||||||
|
AssignmentCandidate candidate,
|
||||||
|
Map<String, List<int[]>> studentExams,
|
||||||
|
Set<String> enrolledStudents) {
|
||||||
|
|
||||||
|
double penalty = 0.0;
|
||||||
|
|
||||||
|
for (String studentId : enrolledStudents) {
|
||||||
|
List<int[]> exams = studentExams.get(studentId);
|
||||||
|
if (exams == null || exams.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (int[] exam : exams) {
|
||||||
|
// If exam is on the same day
|
||||||
|
if (exam[0] == candidate.day) {
|
||||||
|
int gap = Math.abs(exam[1] - candidate.slot) - 1;
|
||||||
|
// Gap 0 = Back to Back (if allowed). Penalty = 50
|
||||||
|
// Gap 1 = 1 slot break. Penalty = 10
|
||||||
|
// Gap 2 = 2 slot break. Penalty = 0 (Good)
|
||||||
|
if (gap == 0)
|
||||||
|
penalty += 50.0;
|
||||||
|
else if (gap == 1)
|
||||||
|
penalty += 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consecutive days penalty
|
||||||
|
if (Math.abs(exam[0] - candidate.day) == 1) {
|
||||||
|
penalty += 5.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tie-breaker: Prefer earlier days/slots slightly
|
||||||
|
penalty += candidate.day * 0.1;
|
||||||
|
penalty += candidate.slot * 0.01;
|
||||||
|
|
||||||
|
return penalty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for candidates.
|
||||||
|
*/
|
||||||
|
private static class AssignmentCandidate {
|
||||||
|
final int day;
|
||||||
|
final int slot;
|
||||||
|
final Classroom classroom;
|
||||||
|
|
||||||
|
public AssignmentCandidate(int day, int slot, Classroom classroom) {
|
||||||
|
this.day = day;
|
||||||
|
this.slot = slot;
|
||||||
|
this.classroom = classroom;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user