From 51b572a1b52885f87531a55e2ae4086bf1660e18 Mon Sep 17 00:00:00 2001 From: mingyeong Date: Thu, 11 Dec 2025 12:27:43 +0900 Subject: [PATCH 1/4] Feat: Implement Schedular logic --- .../java/com/unitime/algorthm/Scheduler.java | 53 +++++++++++++++++++ src/test/java/com/unitime/SchedulerTest.java | 0 2 files changed, 53 insertions(+) create mode 100644 src/test/java/com/unitime/SchedulerTest.java diff --git a/src/main/java/com/unitime/algorthm/Scheduler.java b/src/main/java/com/unitime/algorthm/Scheduler.java index e69de29..16ddadf 100644 --- a/src/main/java/com/unitime/algorthm/Scheduler.java +++ b/src/main/java/com/unitime/algorthm/Scheduler.java @@ -0,0 +1,53 @@ +package com.unitime.solver; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TimeTableSolver { + + public List> solve(List mandatoryList, List optionList, int goalCredit) { + + List> suggestionList = new ArrayList<>(); + + for (int i = 0; i < 5; i++) { + + List oneTable = new ArrayList<>(); + int currentTotalCredit = 0; + + for (Course m : mandatoryList) { + oneTable.add(m); + currentTotalCredit += m.getCredit(); + } + + List shuffledOptions = new ArrayList<>(optionList); + Collections.shuffle(shuffledOptions); + + for (Course o : shuffledOptions) { + if (currentTotalCredit + o.getCredit() > goalCredit) { + continue; + } + + if (!isTimeOverlap(oneTable, o)) { + oneTable.add(o); + currentTotalCredit += o.getCredit(); + } + } + + suggestionList.add(oneTable); + } + + return suggestionList; + } + + + private boolean isTimeOverlap(List currentTable, Course newCourse) { + for (Course existing : currentTable) { + 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/SchedulerTest.java b/src/test/java/com/unitime/SchedulerTest.java new file mode 100644 index 0000000..e69de29 From 298a37b364915bf8ef2305bfed8bde7b111ae72a Mon Sep 17 00:00:00 2001 From: mingyeong Date: Thu, 11 Dec 2025 13:49:25 +0900 Subject: [PATCH 2/4] Feat: Implement Scheduler logic and unit test --- .../java/com/unitime/algorthm/Scheduler.java | 12 +++- src/test/java/com/unitime/SchedulerTest.java | 0 .../com/unitime/algorthm/SchedulerTest.java | 69 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) delete mode 100644 src/test/java/com/unitime/SchedulerTest.java create mode 100644 src/test/java/com/unitime/algorthm/SchedulerTest.java diff --git a/src/main/java/com/unitime/algorthm/Scheduler.java b/src/main/java/com/unitime/algorthm/Scheduler.java index 16ddadf..b4c5915 100644 --- a/src/main/java/com/unitime/algorthm/Scheduler.java +++ b/src/main/java/com/unitime/algorthm/Scheduler.java @@ -1,12 +1,14 @@ -package com.unitime.solver; +package com.unitime.algorthm; import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class TimeTableSolver { +import com.unitime.feature.Course; - public List> solve(List mandatoryList, List optionList, int goalCredit) { +public class Scheduler { + + public List> schedule(List mandatoryList, List optionList, int goalCredit) { List> suggestionList = new ArrayList<>(); @@ -43,6 +45,10 @@ public List> solve(List mandatoryList, List optionL 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; diff --git a/src/test/java/com/unitime/SchedulerTest.java b/src/test/java/com/unitime/SchedulerTest.java deleted file mode 100644 index e69de29..0000000 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..b9aa8f0 --- /dev/null +++ b/src/test/java/com/unitime/algorthm/SchedulerTest.java @@ -0,0 +1,69 @@ +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 { + + @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); + } + + @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); + } + + @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 From f957adfa52543500fd2234a86c954aa66fde7f25 Mon Sep 17 00:00:00 2001 From: mingyeong Date: Thu, 11 Dec 2025 16:14:25 +0900 Subject: [PATCH 3/4] Refactor: Change algorithm from Shuffle to Recursion based on review --- .../java/com/unitime/algorthm/Scheduler.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/unitime/algorthm/Scheduler.java b/src/main/java/com/unitime/algorthm/Scheduler.java index b4c5915..33fac9d 100644 --- a/src/main/java/com/unitime/algorthm/Scheduler.java +++ b/src/main/java/com/unitime/algorthm/Scheduler.java @@ -1,56 +1,60 @@ package com.unitime.algorthm; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import com.unitime.feature.Course; public class Scheduler { + private List> allSchedules; + public List> schedule(List mandatoryList, List optionList, int goalCredit) { - List> suggestionList = new ArrayList<>(); + allSchedules = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - - List oneTable = new ArrayList<>(); - int currentTotalCredit = 0; + List currentSchedule = new ArrayList<>(); - for (Course m : mandatoryList) { - oneTable.add(m); - currentTotalCredit += m.getCredit(); - } + int currentCredit = 0; + for (Course m : mandatoryList) { + currentSchedule.add(m); + currentCredit += m.getCredit(); + } + + findCombinations(0, currentSchedule, currentCredit, optionList, goalCredit); + + return allSchedules; + } + + private void findCombinations(int index, List currentSchedule, int currentCredit, List optionList, int goalCredit) { + allSchedules.add(new ArrayList<>(currentSchedule)); - List shuffledOptions = new ArrayList<>(optionList); - Collections.shuffle(shuffledOptions); + for (int i = index; i < optionList.size(); i++) { + Course candidate = optionList.get(i); - for (Course o : shuffledOptions) { - if (currentTotalCredit + o.getCredit() > goalCredit) { - continue; - } + if (currentCredit + candidate.getCredit() > goalCredit) { + continue; + } - if (!isTimeOverlap(oneTable, o)) { - oneTable.add(o); - currentTotalCredit += o.getCredit(); - } + if (isTimeOverlap(currentSchedule, candidate)) { + continue; } - suggestionList.add(oneTable); - } + currentSchedule.add(candidate); - return suggestionList; + findCombinations(i + 1, currentSchedule, currentCredit + candidate.getCredit(), optionList, goalCredit); + + 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()) { + existing.getEndTime() > newCourse.getStartTime()) { return true; } } From 818d2831f527dd8bf5215fd966f00cb2115c2919 Mon Sep 17 00:00:00 2001 From: mingyeong Date: Thu, 11 Dec 2025 19:12:23 +0900 Subject: [PATCH 4/4] Docs: Add necessary comments to Scheduler and SchedulerTest files --- .../java/com/unitime/algorthm/Scheduler.java | 31 ++++++++++++++++--- .../com/unitime/algorthm/SchedulerTest.java | 4 ++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/unitime/algorthm/Scheduler.java b/src/main/java/com/unitime/algorthm/Scheduler.java index 33fac9d..85be18e 100644 --- a/src/main/java/com/unitime/algorthm/Scheduler.java +++ b/src/main/java/com/unitime/algorthm/Scheduler.java @@ -6,9 +6,17 @@ 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<>(); @@ -16,34 +24,49 @@ public List> schedule(List mandatoryList, List opti 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); } } diff --git a/src/test/java/com/unitime/algorthm/SchedulerTest.java b/src/test/java/com/unitime/algorthm/SchedulerTest.java index b9aa8f0..b431f2c 100644 --- a/src/test/java/com/unitime/algorthm/SchedulerTest.java +++ b/src/test/java/com/unitime/algorthm/SchedulerTest.java @@ -7,7 +7,7 @@ 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<>(); @@ -22,6 +22,7 @@ public void testBasicSchedule() { assertTrue(result.size() > 0); } + // Check if credit limit is respected @Test public void testTimeConflict() { List mandatory = new ArrayList<>(); @@ -44,6 +45,7 @@ public void testTimeConflict() { assertFalse(hasConflict); } + // Check if credit limit is respected @Test public void testCreditLimit() { List mandatory = new ArrayList<>();