diff --git a/src/main/java/com/unitime/algorthm/Scheduler.java b/src/main/java/com/unitime/algorthm/Scheduler.java index e69de29..85be18e 100644 --- a/src/main/java/com/unitime/algorthm/Scheduler.java +++ b/src/main/java/com/unitime/algorthm/Scheduler.java @@ -0,0 +1,86 @@ +package com.unitime.algorthm; + +import java.util.ArrayList; +import java.util.List; + +import com.unitime.feature.Course; + +public class Scheduler { + //List to store all valid timetable combinations + private List> allSchedules; + + /** + * Creates all possible schedules that fit your rules (time & credit limit) + * + * @param mandatoryList List of courses that MUST be included + * @param optionList List of optional courses that CAN be combined + * @param goalCredit Maximum total credit allowed + * @return List> All valid schedule combinations found + */ + public List> schedule(List mandatoryList, List optionList, int goalCredit) { + + allSchedules = new ArrayList<>(); + + List currentSchedule = new ArrayList<>(); + + int currentCredit = 0; + + // 1. Add mandatory courses to the schedule first + for (Course m : mandatoryList) { + currentSchedule.add(m); + currentCredit += m.getCredit(); + } + + // 2. Start the recursive search to find combinations + findCombinations(0, currentSchedule, currentCredit, optionList, goalCredit); + + return allSchedules; + } + /** + * Recursively finds all valid course combinations (Backtracking) + * @param index Start index for searching in optionList (prevents duplicates) + * @param currentSchedule List of courses currently selected in this recursion + * @param currentCredit Total credit hours accumulated in the current schedule + */ + private void findCombinations(int index, List currentSchedule, int currentCredit, List optionList, int goalCredit) { + allSchedules.add(new ArrayList<>(currentSchedule)); + + // Loop through remaining options + // Start from 'index' to prevent duplicates (e.g., A+B and B+A) + for (int i = index; i < optionList.size(); i++) { + Course candidate = optionList.get(i); + + // [Skip Rule 1] Stop if adding the course exceeds the max credit allowed + if (currentCredit + candidate.getCredit() > goalCredit) { + continue; + } + + // [Skip Rule 2] Stop if the course overlaps with the current schedule + if (isTimeOverlap(currentSchedule, candidate)) { + continue; + } + + // 1. CHOOSE: Add the course + currentSchedule.add(candidate); + + // 2. RECURSE: Go deeper to find the next course (start from i + 1) + findCombinations(i + 1, currentSchedule, currentCredit + candidate.getCredit(), optionList, goalCredit); + + // 3. BACKTRACK: Remove the course to try the next option in the loop + currentSchedule.remove(currentSchedule.size() - 1); + } + } + + private boolean isTimeOverlap(List currentTable, Course newCourse) { + for (Course existing : currentTable) { + if (existing.getDay() != newCourse.getDay()) { + continue; + } + if (existing.getStartTime() < newCourse.getEndTime() && + existing.getEndTime() > newCourse.getStartTime()) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/test/java/com/unitime/algorthm/SchedulerTest.java b/src/test/java/com/unitime/algorthm/SchedulerTest.java new file mode 100644 index 0000000..b431f2c --- /dev/null +++ b/src/test/java/com/unitime/algorthm/SchedulerTest.java @@ -0,0 +1,71 @@ +package com.unitime.algorthm; + +import static org.junit.Assert.*; +import org.junit.Test; +import java.util.ArrayList; +import java.util.List; +import com.unitime.feature.Course; + +public class SchedulerTest { + // Check if schedule is generated when basic constraints are met + @Test + public void testBasicSchedule() { + List mandatory = new ArrayList<>(); + mandatory.add(new Course("OSS", 3, 0, 600, 720, "Mon 10:00-12:00")); + + List option = new ArrayList<>(); + option.add(new Course("CS", 3, 1, 600, 720, "Tue 10:00-12:00")); + + Scheduler scheduler = new Scheduler(); + List> result = scheduler.schedule(mandatory, option, 10); + + assertTrue(result.size() > 0); + } + + // Check if credit limit is respected + @Test + public void testTimeConflict() { + List mandatory = new ArrayList<>(); + mandatory.add(new Course("OSS", 3, 0, 600, 720, "Mon 10:00-12:00")); + + List option = new ArrayList<>(); + option.add(new Course("Tennis", 2, 0, 660, 750, "Mon 11:00-12:30")); + + Scheduler scheduler = new Scheduler(); + List> result = scheduler.schedule(mandatory, option, 10); + + List firstSchedule = result.get(0); + boolean hasConflict = false; + for(Course c : firstSchedule) { + if(c.getName().contains("Tennis")) { + hasConflict = true; + } + } + + assertFalse(hasConflict); + } + + // Check if credit limit is respected + @Test + public void testCreditLimit() { + List mandatory = new ArrayList<>(); + mandatory.add(new Course("OSS", 3, 0, 600, 720, "Mon 10:00")); + + List option = new ArrayList<>(); + option.add(new Course("DS)", 3, 1, 600, 720, "Tue")); + option.add(new Course("CS", 3, 2, 600, 720, "Wed")); + option.add(new Course("BPM", 3, 3, 600, 720, "Thu")); + + int goalCredit = 5; + + Scheduler scheduler = new Scheduler(); + List> result = scheduler.schedule(mandatory, option, goalCredit); + + int totalCredit = 0; + for(Course c : result.get(0)) { + totalCredit += c.getCredit(); + } + + assertTrue(totalCredit <= goalCredit); + } +} \ No newline at end of file