mirror of
https://github.com/sabazadam/Se302.git
synced 2025-12-31 12:21:22 +00:00
Static Classroum bug fixed. Now codes can assign different classroum on scheduling.
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -9,7 +9,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="ms-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="ms-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -105,8 +105,8 @@ public class ScheduleCalendarController {
|
||||
// Set default date to next week
|
||||
startDatePicker.setValue(LocalDate.now().plusDays(7));
|
||||
|
||||
// Set default checkbox - allow back-to-back for easier scheduling
|
||||
allowBackToBackCheckBox.setSelected(true);
|
||||
// Set default checkbox - student-friendly (no back-to-back exams)
|
||||
allowBackToBackCheckBox.setSelected(false);
|
||||
|
||||
// Add listeners to update summary
|
||||
numDaysSpinner.valueProperty().addListener((obs, oldVal, newVal) -> updateSummary());
|
||||
@@ -135,16 +135,13 @@ public class ScheduleCalendarController {
|
||||
}
|
||||
|
||||
private void initializeComboBoxes() {
|
||||
// Optimization strategies
|
||||
// Optimization strategies - consolidated to 3 meaningful options
|
||||
List<String> strategies = Arrays.asList(
|
||||
"Default (Balanced)",
|
||||
"Student Friendly (Default)",
|
||||
"Minimize Days",
|
||||
"Balanced Distribution",
|
||||
"Minimize Classrooms",
|
||||
"Balance Classrooms",
|
||||
"Student Friendly");
|
||||
"Minimize Classrooms");
|
||||
strategyComboBox.setItems(FXCollections.observableArrayList(strategies));
|
||||
strategyComboBox.getSelectionModel().selectFirst();
|
||||
strategyComboBox.getSelectionModel().selectFirst(); // Default: Student Friendly
|
||||
|
||||
// Start times (8:00 - 14:00)
|
||||
List<String> times = Arrays.asList(
|
||||
@@ -236,22 +233,17 @@ public class ScheduleCalendarController {
|
||||
config.setDayStartTime(LocalTime.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])));
|
||||
}
|
||||
|
||||
// Set optimization strategy
|
||||
// Set optimization strategy (consolidated to 3 options)
|
||||
String strategyStr = strategyComboBox.getValue();
|
||||
ScheduleConfiguration.OptimizationStrategy strategy = ScheduleConfiguration.OptimizationStrategy.DEFAULT;
|
||||
ScheduleConfiguration.OptimizationStrategy strategy = ScheduleConfiguration.OptimizationStrategy.STUDENT_FRIENDLY;
|
||||
|
||||
if (strategyStr != null) {
|
||||
if (strategyStr.contains("Minimize Days")) {
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.MINIMIZE_DAYS;
|
||||
} else if (strategyStr.contains("Balanced Distribution")) {
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.BALANCED_DISTRIBUTION;
|
||||
} else if (strategyStr.contains("Minimize Classrooms")) {
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.MINIMIZE_CLASSROOMS;
|
||||
} else if (strategyStr.contains("Balance Classrooms")) {
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.BALANCE_CLASSROOMS;
|
||||
} else if (strategyStr.contains("Student Friendly")) {
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.STUDENT_FRIENDLY;
|
||||
}
|
||||
// STUDENT_FRIENDLY is default, no explicit check needed
|
||||
}
|
||||
config.setOptimizationStrategy(strategy);
|
||||
|
||||
|
||||
@@ -26,24 +26,36 @@ public class ScheduleConfiguration {
|
||||
|
||||
/**
|
||||
* Optimization strategies for the scheduling algorithm.
|
||||
*
|
||||
* <p>Active Strategies:</p>
|
||||
* <ul>
|
||||
* <li>STUDENT_FRIENDLY - Minimize gaps, enforce constraints (DEFAULT)</li>
|
||||
* <li>MINIMIZE_DAYS - Pack exams into fewest days</li>
|
||||
* <li>MINIMIZE_CLASSROOMS - Use fewest classrooms</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Deprecated strategies are maintained for backward compatibility only.</p>
|
||||
*/
|
||||
public enum OptimizationStrategy {
|
||||
/** Minimize total number of days used - pack exams into fewest days */
|
||||
/** Pack exams into fewest days possible */
|
||||
MINIMIZE_DAYS,
|
||||
|
||||
/** Spread exams evenly across days - balanced distribution */
|
||||
BALANCED_DISTRIBUTION,
|
||||
|
||||
/** Minimize number of classrooms used - reuse same classrooms */
|
||||
/** Use fewest classrooms possible */
|
||||
MINIMIZE_CLASSROOMS,
|
||||
|
||||
/** Balance classroom usage across days - even distribution */
|
||||
BALANCE_CLASSROOMS,
|
||||
|
||||
/** Minimize consecutive exams for students (bonus strategy) */
|
||||
/** Minimize gaps, enforce student-friendly constraints (DEFAULT) */
|
||||
STUDENT_FRIENDLY,
|
||||
|
||||
/** Default balanced approach */
|
||||
/** @deprecated Use STUDENT_FRIENDLY instead. Balanced distribution conflicts with minimize days. */
|
||||
@Deprecated
|
||||
BALANCED_DISTRIBUTION,
|
||||
|
||||
/** @deprecated Use MINIMIZE_CLASSROOMS instead. Balance classrooms conflicts with minimize classrooms. */
|
||||
@Deprecated
|
||||
BALANCE_CLASSROOMS,
|
||||
|
||||
/** @deprecated Use STUDENT_FRIENDLY instead. This is now the default behavior. */
|
||||
@Deprecated
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
@@ -76,9 +88,9 @@ public class ScheduleConfiguration {
|
||||
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.optimizationStrategy = OptimizationStrategy.STUDENT_FRIENDLY; // Student-friendly by default
|
||||
this.allowBackToBackExams = false; // Enforce no consecutive exams by default
|
||||
this.maxExamsPerDay = 2; // Enforce max 2 exams per day by default
|
||||
this.timeoutMs = 60000; // 60 seconds
|
||||
this.useHeuristics = true;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class ScheduleGeneratorService {
|
||||
|
||||
/**
|
||||
* Generate an exam schedule based on configuration.
|
||||
* Uses multi-restart optimization to find the best schedule among multiple attempts.
|
||||
*/
|
||||
public ScheduleResult generateSchedule(ScheduleConfiguration config) {
|
||||
cancelled.set(false);
|
||||
@@ -38,28 +39,71 @@ public class ScheduleGeneratorService {
|
||||
return ScheduleResult.failure("No classrooms available");
|
||||
}
|
||||
|
||||
// Create schedule state
|
||||
ScheduleState scheduleState = initializeScheduleState(config);
|
||||
|
||||
// Get courses ordered by MRV heuristic
|
||||
List<Course> coursesToSchedule = getCoursesOrderedByMRV();
|
||||
|
||||
// Report progress
|
||||
updateProgress(0, coursesToSchedule.size(), "Starting schedule generation...");
|
||||
// Multi-restart optimization: try multiple schedules and pick the best
|
||||
int numRestarts = 5; // Generate 5 different schedules
|
||||
ScheduleState bestSchedule = null;
|
||||
double bestScore = Double.MAX_VALUE;
|
||||
int successfulAttempts = 0;
|
||||
|
||||
// Start backtracking
|
||||
boolean success = backtrack(scheduleState, coursesToSchedule, 0, config);
|
||||
updateProgress(0, numRestarts, "Generating schedules (multi-restart optimization)...");
|
||||
|
||||
for (int attempt = 0; attempt < numRestarts; attempt++) {
|
||||
if (cancelled.get()) {
|
||||
return ScheduleResult.cancelled();
|
||||
}
|
||||
|
||||
// Create fresh schedule state for this attempt
|
||||
ScheduleState scheduleState = initializeScheduleState(config);
|
||||
|
||||
updateProgress(attempt, numRestarts,
|
||||
String.format("Attempt %d/%d: Starting backtracking...", attempt + 1, numRestarts));
|
||||
|
||||
// Randomize search order slightly for diversity
|
||||
List<Course> shuffledCourses = new ArrayList<>(coursesToSchedule);
|
||||
if (attempt > 0) {
|
||||
// Keep MRV heuristic but add small random perturbation
|
||||
shuffledCourses = perturbCourseOrder(shuffledCourses, attempt);
|
||||
}
|
||||
|
||||
// Run backtracking
|
||||
boolean success = backtrack(scheduleState, shuffledCourses, 0, config);
|
||||
|
||||
if (success) {
|
||||
successfulAttempts++;
|
||||
|
||||
// Evaluate this schedule using objective function
|
||||
double score = ScheduleObjective.evaluateSchedule(scheduleState, config, dataManager);
|
||||
|
||||
updateProgress(attempt + 1, numRestarts,
|
||||
String.format("Attempt %d/%d: Found schedule (score: %.1f, best: %.1f)",
|
||||
attempt + 1, numRestarts, score, bestScore));
|
||||
|
||||
if (score < bestScore) {
|
||||
bestScore = score;
|
||||
bestSchedule = scheduleState.copy();
|
||||
}
|
||||
} else {
|
||||
updateProgress(attempt + 1, numRestarts,
|
||||
String.format("Attempt %d/%d: No valid schedule", attempt + 1, numRestarts));
|
||||
}
|
||||
}
|
||||
|
||||
if (cancelled.get()) {
|
||||
return ScheduleResult.cancelled();
|
||||
}
|
||||
|
||||
if (success) {
|
||||
updateProgress(coursesToSchedule.size(), coursesToSchedule.size(), "Schedule generated successfully!");
|
||||
return ScheduleResult.success(scheduleState);
|
||||
if (bestSchedule != null) {
|
||||
updateProgress(numRestarts, numRestarts,
|
||||
String.format("Complete! Best schedule: score %.1f (%d/%d attempts succeeded)",
|
||||
bestScore, successfulAttempts, numRestarts));
|
||||
return ScheduleResult.success(bestSchedule);
|
||||
} else {
|
||||
return ScheduleResult
|
||||
.failure("No valid schedule found. Try increasing days/slots or relaxing constraints.");
|
||||
return ScheduleResult.failure(
|
||||
"No valid schedule found in " + numRestarts + " attempts. " +
|
||||
"Try increasing days/slots or relaxing constraints.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +113,15 @@ public class ScheduleGeneratorService {
|
||||
private ScheduleState initializeScheduleState(ScheduleConfiguration config) {
|
||||
ScheduleState state = new ScheduleState();
|
||||
|
||||
// CRITICAL: Set configuration so ScheduleState knows about days/slots
|
||||
state.setConfiguration(config);
|
||||
|
||||
// Set available classrooms
|
||||
state.setAvailableClassrooms(new ArrayList<>(dataManager.getClassrooms()));
|
||||
|
||||
// Set available time slots based on configuration
|
||||
state.setAvailableTimeSlots(config.generateTimeSlots());
|
||||
|
||||
// Initialize exam assignments for all courses (unassigned)
|
||||
for (Course course : dataManager.getCourses()) {
|
||||
ExamAssignment assignment = new ExamAssignment(course.getCourseCode());
|
||||
@@ -162,6 +212,29 @@ public class ScheduleGeneratorService {
|
||||
return courses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add small random perturbation to course order while preserving MRV bias.
|
||||
* This creates diverse schedules for multi-restart optimization.
|
||||
*
|
||||
* @param courses Original course list
|
||||
* @param seed Seed for deterministic randomization
|
||||
* @return Perturbed course list
|
||||
*/
|
||||
private List<Course> perturbCourseOrder(List<Course> courses, int seed) {
|
||||
Random random = new Random(seed * 1000L); // Deterministic seed for reproducibility
|
||||
List<Course> perturbed = new ArrayList<>(courses);
|
||||
|
||||
// Swap 2-3 random pairs (limited perturbation maintains MRV benefits)
|
||||
int swaps = 2 + random.nextInt(2);
|
||||
for (int i = 0; i < swaps && perturbed.size() > 1; i++) {
|
||||
int idx1 = random.nextInt(perturbed.size());
|
||||
int idx2 = random.nextInt(perturbed.size());
|
||||
Collections.swap(perturbed, idx1, idx2);
|
||||
}
|
||||
|
||||
return perturbed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time slots ordered by optimization strategy.
|
||||
*/
|
||||
@@ -175,24 +248,30 @@ public class ScheduleGeneratorService {
|
||||
}
|
||||
}
|
||||
|
||||
// Order based on strategy
|
||||
// Order based on strategy (deprecated strategies handled in ScheduleObjective)
|
||||
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)
|
||||
// Fill each day completely before moving to next day
|
||||
// Within each day, prefer middle slots (avoid early morning)
|
||||
// Priority: slots 1 and 2 (middle of day) over slots 0 (early) and 3 (late)
|
||||
timeSlots.sort((p1, p2) -> {
|
||||
// Primary: sort by day (fill Day 0 first, then Day 1, etc.)
|
||||
int dayCompare = Integer.compare(p1.day, p2.day);
|
||||
if (dayCompare != 0)
|
||||
return dayCompare;
|
||||
|
||||
// Secondary: within same day, prefer middle slots
|
||||
// Slot priority order: 1 (best), 2 (good), 0 (early morning), 3 (late afternoon)
|
||||
int[] slotPriority = { 2, 0, 1, 3 }; // Maps slot index to priority (lower is better)
|
||||
int priority1 = p1.slot < slotPriority.length ? slotPriority[p1.slot] : p1.slot;
|
||||
int priority2 = p2.slot < slotPriority.length ? slotPriority[p2.slot] : p2.slot;
|
||||
return Integer.compare(priority1, priority2);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -237,7 +316,7 @@ public class ScheduleGeneratorService {
|
||||
}
|
||||
}
|
||||
|
||||
// Order based on strategy
|
||||
// Order based on strategy (deprecated strategies handled in ScheduleObjective)
|
||||
switch (config.getOptimizationStrategy()) {
|
||||
case MINIMIZE_CLASSROOMS:
|
||||
// Prefer classrooms that are already in use (reuse same classrooms)
|
||||
@@ -249,19 +328,26 @@ public class ScheduleGeneratorService {
|
||||
});
|
||||
break;
|
||||
|
||||
case BALANCE_CLASSROOMS:
|
||||
// Prefer classrooms that are least used
|
||||
default:
|
||||
// DEFAULT: Use round-robin to force distribution across classrooms
|
||||
// Always prefer least-used classrooms to ensure multiple classrooms get 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));
|
||||
// Primary: prefer least-used classrooms
|
||||
int usageCompare = Integer.compare(usage1, usage2);
|
||||
if (usageCompare != 0)
|
||||
return usageCompare;
|
||||
|
||||
// Tiebreaker: prefer smaller capacity (efficient space usage)
|
||||
int capacityCompare = Integer.compare(c1.getCapacity(), c2.getCapacity());
|
||||
if (capacityCompare != 0)
|
||||
return capacityCompare;
|
||||
|
||||
// Final tiebreaker: classroom ID (deterministic)
|
||||
return c1.getClassroomId().compareTo(c2.getClassroomId());
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
267
src/main/java/org/example/se302/service/ScheduleObjective.java
Normal file
267
src/main/java/org/example/se302/service/ScheduleObjective.java
Normal file
@@ -0,0 +1,267 @@
|
||||
package org.example.se302.service;
|
||||
|
||||
import org.example.se302.model.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Calculates quality scores for exam schedules to enable optimization.
|
||||
* Lower score = better schedule.
|
||||
*
|
||||
* Each optimization strategy has its own scoring function that quantifies
|
||||
* how well a schedule meets the strategy's goals.
|
||||
*/
|
||||
public class ScheduleObjective {
|
||||
|
||||
/**
|
||||
* Evaluates a schedule based on the selected optimization strategy.
|
||||
*
|
||||
* @param state The schedule state to evaluate
|
||||
* @param config The configuration containing the optimization strategy
|
||||
* @param dataManager Data manager for accessing student and course data
|
||||
* @return Quality score (lower is better)
|
||||
*/
|
||||
public static double evaluateSchedule(
|
||||
ScheduleState state,
|
||||
ScheduleConfiguration config,
|
||||
DataManager dataManager) {
|
||||
|
||||
// Normalize strategy in case deprecated ones are used
|
||||
ScheduleConfiguration.OptimizationStrategy strategy = config.getOptimizationStrategy();
|
||||
|
||||
// Map deprecated strategies to their replacements
|
||||
switch (strategy) {
|
||||
case BALANCED_DISTRIBUTION:
|
||||
case DEFAULT:
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.STUDENT_FRIENDLY;
|
||||
break;
|
||||
case BALANCE_CLASSROOMS:
|
||||
strategy = ScheduleConfiguration.OptimizationStrategy.MINIMIZE_CLASSROOMS;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate score based on strategy
|
||||
switch (strategy) {
|
||||
case MINIMIZE_DAYS:
|
||||
return scoreMinimizeDays(state, config);
|
||||
|
||||
case MINIMIZE_CLASSROOMS:
|
||||
return scoreMinimizeClassrooms(state);
|
||||
|
||||
case STUDENT_FRIENDLY:
|
||||
return scoreStudentFriendly(state, config, dataManager);
|
||||
|
||||
default:
|
||||
return scoreStudentFriendly(state, config, dataManager);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Score for MINIMIZE_DAYS strategy.
|
||||
* Goal: Pack exams into as few days as possible.
|
||||
*
|
||||
* @param state The schedule state
|
||||
* @param config The configuration
|
||||
* @return Score (lower = fewer days used)
|
||||
*/
|
||||
private static double scoreMinimizeDays(ScheduleState state, ScheduleConfiguration config) {
|
||||
// Count number of unique days actually used
|
||||
Set<Integer> usedDays = new HashSet<>();
|
||||
for (ExamAssignment assignment : state.getAssignments().values()) {
|
||||
if (assignment.isAssigned()) {
|
||||
usedDays.add(assignment.getDay());
|
||||
}
|
||||
}
|
||||
|
||||
// Primary objective: minimize number of days
|
||||
// Penalty: 1000 points per day used
|
||||
double score = usedDays.size() * 1000.0;
|
||||
|
||||
// Secondary objective: prefer earlier days (Day 0 better than Day 4)
|
||||
// This is a tiebreaker when two schedules use the same number of days
|
||||
double avgDay = state.getAssignedCoursesList().stream()
|
||||
.mapToInt(ExamAssignment::getDay)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
score += avgDay * 10;
|
||||
|
||||
// Tertiary objective: within each day, fill earlier slots first
|
||||
double avgSlot = state.getAssignedCoursesList().stream()
|
||||
.mapToInt(ExamAssignment::getTimeSlotIndex)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
score += avgSlot * 1;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score for MINIMIZE_CLASSROOMS strategy.
|
||||
* Goal: Use as few different classrooms as possible.
|
||||
*
|
||||
* @param state The schedule state
|
||||
* @return Score (lower = fewer classrooms used)
|
||||
*/
|
||||
private static double scoreMinimizeClassrooms(ScheduleState state) {
|
||||
// Count unique classrooms used
|
||||
Set<String> usedClassrooms = new HashSet<>();
|
||||
for (ExamAssignment assignment : state.getAssignments().values()) {
|
||||
if (assignment.isAssigned() && assignment.getClassroomId() != null) {
|
||||
usedClassrooms.add(assignment.getClassroomId());
|
||||
}
|
||||
}
|
||||
|
||||
// Primary objective: minimize number of classrooms
|
||||
// Penalty: 1000 points per classroom used
|
||||
double score = usedClassrooms.size() * 1000.0;
|
||||
|
||||
// Secondary objective: prefer classrooms with lower IDs
|
||||
// (e.g., Classroom_01 better than Classroom_10)
|
||||
// This is a tiebreaker when two schedules use the same number of classrooms
|
||||
for (ExamAssignment assignment : state.getAssignments().values()) {
|
||||
if (assignment.isAssigned() && assignment.getClassroomId() != null) {
|
||||
String classroomId = assignment.getClassroomId();
|
||||
// Extract numeric part (e.g., "Classroom_01" -> 1)
|
||||
try {
|
||||
int num = Integer.parseInt(classroomId.replaceAll("[^0-9]", ""));
|
||||
score += num * 0.1; // Small penalty for higher-numbered classrooms
|
||||
} catch (NumberFormatException e) {
|
||||
// If classroom ID doesn't contain number, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Score for STUDENT_FRIENDLY strategy.
|
||||
* Goal: Minimize gaps in student schedules and create a comfortable exam experience.
|
||||
*
|
||||
* @param state The schedule state
|
||||
* @param config The configuration
|
||||
* @param dataManager Data manager for accessing student data
|
||||
* @return Score (lower = more student-friendly)
|
||||
*/
|
||||
private static double scoreStudentFriendly(
|
||||
ScheduleState state,
|
||||
ScheduleConfiguration config,
|
||||
DataManager dataManager) {
|
||||
|
||||
double score = 0.0;
|
||||
|
||||
// Calculate total gaps in student schedules
|
||||
for (Student student : dataManager.getStudents()) {
|
||||
List<ExamAssignment> studentExams = getStudentExams(student, state, dataManager);
|
||||
|
||||
if (studentExams.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sort exams by day, then by time slot
|
||||
studentExams.sort(Comparator
|
||||
.comparingInt(ExamAssignment::getDay)
|
||||
.thenComparingInt(ExamAssignment::getTimeSlotIndex));
|
||||
|
||||
// Calculate gaps (empty slots between exams on the same day)
|
||||
for (int i = 1; i < studentExams.size(); i++) {
|
||||
ExamAssignment prev = studentExams.get(i - 1);
|
||||
ExamAssignment curr = studentExams.get(i);
|
||||
|
||||
if (prev.getDay() == curr.getDay()) {
|
||||
int gap = curr.getTimeSlotIndex() - prev.getTimeSlotIndex() - 1;
|
||||
if (gap > 0) {
|
||||
// Penalty: 10 points per gap slot
|
||||
// Example: Exam at slot 0 and slot 3 = 2 gap slots = 20 points penalty
|
||||
score += gap * 10.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Penalty for early-morning exams (first slot of the day)
|
||||
// Students prefer later exam times
|
||||
for (ExamAssignment exam : studentExams) {
|
||||
if (exam.getTimeSlotIndex() == 0) {
|
||||
score += 5.0; // Small penalty for first slot (typically 8-9am)
|
||||
}
|
||||
}
|
||||
|
||||
// Penalty for late-afternoon exams (last slot of the day)
|
||||
int lastSlot = config.getSlotsPerDay() - 1;
|
||||
for (ExamAssignment exam : studentExams) {
|
||||
if (exam.getTimeSlotIndex() == lastSlot) {
|
||||
score += 3.0; // Smaller penalty for last slot
|
||||
}
|
||||
}
|
||||
|
||||
// Bonus for clustered exam days (exams on consecutive days are harder)
|
||||
// Prefer spreading exams across non-consecutive days when possible
|
||||
Set<Integer> examDays = new HashSet<>();
|
||||
for (ExamAssignment exam : studentExams) {
|
||||
examDays.add(exam.getDay());
|
||||
}
|
||||
List<Integer> sortedDays = new ArrayList<>(examDays);
|
||||
Collections.sort(sortedDays);
|
||||
|
||||
for (int i = 1; i < sortedDays.size(); i++) {
|
||||
if (sortedDays.get(i) - sortedDays.get(i - 1) == 1) {
|
||||
// Consecutive days
|
||||
score += 2.0; // Small penalty for exams on consecutive days
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary objective: balance classroom usage
|
||||
// Prefer schedules that distribute exams across classrooms
|
||||
Map<String, Integer> classroomUsage = new HashMap<>();
|
||||
for (ExamAssignment assignment : state.getAssignments().values()) {
|
||||
if (assignment.isAssigned()) {
|
||||
classroomUsage.merge(assignment.getClassroomId(), 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate standard deviation of classroom usage
|
||||
if (!classroomUsage.isEmpty()) {
|
||||
double avgUsage = classroomUsage.values().stream()
|
||||
.mapToInt(Integer::intValue)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
double variance = classroomUsage.values().stream()
|
||||
.mapToDouble(usage -> Math.pow(usage - avgUsage, 2))
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
double stdDev = Math.sqrt(variance);
|
||||
// Penalty for unbalanced classroom usage
|
||||
score += stdDev * 2.0;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all exam assignments for a specific student.
|
||||
*
|
||||
* @param student The student
|
||||
* @param state The schedule state
|
||||
* @param dataManager Data manager for course lookup
|
||||
* @return List of exam assignments for this student
|
||||
*/
|
||||
private static List<ExamAssignment> getStudentExams(
|
||||
Student student,
|
||||
ScheduleState state,
|
||||
DataManager dataManager) {
|
||||
|
||||
List<ExamAssignment> exams = new ArrayList<>();
|
||||
for (String courseCode : student.getEnrolledCourses()) {
|
||||
ExamAssignment assignment = state.getAssignment(courseCode);
|
||||
if (assignment != null && assignment.isAssigned()) {
|
||||
exams.add(assignment);
|
||||
}
|
||||
}
|
||||
return exams;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user