Skip to content

Commit 900c226

Browse files
committed
fix(timetable): handle impossible schedules, validate inputs, improve conflict logic
1 parent 34d54c8 commit 900c226

File tree

6 files changed

+152
-84
lines changed

6 files changed

+152
-84
lines changed

Src/Time_table_Generator/genetic_algorithm.cpp

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
#include "genetic_algorithm.h"
2+
#include <algorithm>
3+
#include <cstdlib>
4+
#include <ctime>
5+
#include <iostream>
6+
using namespace std;
27

38
Timetable GeneticAlgorithm::crossover(const Timetable& p1, const Timetable& p2) {
49
Timetable child = p1;
5-
int half = p1.sessions.size() / 2;
10+
size_t half = p1.sessions.size() / 2;
611
for (size_t i = half; i < p1.sessions.size(); ++i)
712
child.sessions[i] = p2.sessions[i];
813
return child;
914
}
1015

1116
void GeneticAlgorithm::mutate(Timetable& t) {
12-
int n = t.sessions.size();
13-
for (int i = 0; i < n; ++i) {
17+
if (t.rooms.empty() || t.slotsPerDay <= 0 || t.numDays <= 0) return;
18+
19+
for (auto &s : t.sessions) {
1420
if ((rand() / (double)RAND_MAX) < mutationRate) {
15-
t.sessions[i].day = rand() % t.numDays;
16-
t.sessions[i].slot = rand() % t.slotsPerDay;
17-
t.sessions[i].room = t.rooms[rand() % t.rooms.size()].name;
21+
s.day = rand() % t.numDays;
22+
s.slot = rand() % t.slotsPerDay;
23+
s.room = t.rooms[rand() % t.rooms.size()].name;
1824
}
1925
}
2026
}
2127

2228
Timetable GeneticAlgorithm::run(const Timetable& base, bool verbose) {
29+
if (base.rooms.empty() || base.slotsPerDay <= 0 || base.numDays <= 0) return base;
30+
2331
vector<Timetable> population;
2432
for (int i = 0; i < populationSize; ++i) {
2533
Timetable t = base;
@@ -32,25 +40,23 @@ Timetable GeneticAlgorithm::run(const Timetable& base, bool verbose) {
3240

3341
for (int gen = 0; gen < generations; ++gen) {
3442
sort(population.begin(), population.end(),
35-
[](const Timetable &a, const Timetable &b) {
36-
return a.computeConflicts() < b.computeConflicts();
37-
});
43+
[](const Timetable &a, const Timetable &b){ return a.computeConflicts() < b.computeConflicts(); });
3844

3945
if (population[0].computeConflicts() < bestFitness) {
4046
best = population[0];
4147
bestFitness = best.computeConflicts();
4248
}
4349

44-
if (bestFitness == 0) break; // Perfect timetable found
50+
if (bestFitness == 0) break;
4551

4652
vector<Timetable> newPop;
47-
for (int i = 0; i < populationSize / 2; ++i) {
48-
Timetable child1 = crossover(population[i], population[rand() % populationSize]);
49-
Timetable child2 = crossover(population[rand() % populationSize], population[i]);
50-
mutate(child1);
51-
mutate(child2);
52-
newPop.push_back(child1);
53-
newPop.push_back(child2);
53+
for (int i = 0; i < populationSize/2; ++i) {
54+
Timetable c1 = crossover(population[i], population[rand() % populationSize]);
55+
Timetable c2 = crossover(population[rand() % populationSize], population[i]);
56+
mutate(c1);
57+
mutate(c2);
58+
newPop.push_back(c1);
59+
newPop.push_back(c2);
5460
}
5561
population = newPop;
5662

Src/Time_table_Generator/main.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
#include "timetable.h"
22
#include "genetic_algorithm.h"
33
#include "utils.h"
4+
#include <iostream>
5+
using namespace std;
46

57
int main() {
68
srand(time(nullptr));
79
cout << "\n=== C++ Timetable Generator (Genetic Algorithm) ===\n";
10+
811
Timetable base = getUserInput();
912

10-
GeneticAlgorithm ga(30, 100, 0.1); // pop size, generations, mutation rate
13+
int totalRequiredSessions = 0;
14+
for (auto &c : base.courses) totalRequiredSessions += c.sessionsPerWeek;
15+
int totalAvailableSlots = base.numDays * base.slotsPerDay * base.rooms.size();
16+
17+
if (totalRequiredSessions > totalAvailableSlots) {
18+
cerr << "\n❌ Scheduling impossible: Total required sessions ("
19+
<< totalRequiredSessions << ") exceed total available slots ("
20+
<< totalAvailableSlots << ").\n";
21+
cerr << "Try reducing sessions or increasing rooms/days/slots.\n";
22+
return 1;
23+
}
24+
25+
GeneticAlgorithm ga(30, 100, 0.1);
1126
Timetable optimized = ga.run(base, true);
1227

1328
cout << "\nFinal Best Timetable (Conflicts: " << optimized.computeConflicts() << ")\n";

Src/Time_table_Generator/timetable.cpp

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,62 @@
11
#include "timetable.h"
22
#include <algorithm>
3-
#include <cstdlib>
4-
#include <ctime>
53
#include <iostream>
64
using namespace std;
75

86
void Timetable::generateRandom() {
7+
if (rooms.empty() || slotsPerDay <= 0 || numDays <= 0) return;
8+
99
sessions.clear();
1010
srand(time(nullptr));
1111
for (auto &course : courses) {
1212
for (int i = 0; i < course.sessionsPerWeek; ++i) {
13-
Session session;
14-
session.courseName = course.name;
15-
session.instructor = course.instructor;
16-
session.room = rooms[rand() % rooms.size()].name;
17-
session.day = rand() % numDays;
18-
session.slot = rand() % slotsPerDay;
19-
sessions.push_back(session);
13+
Session s;
14+
s.courseName = course.name;
15+
s.instructor = course.instructor;
16+
s.room = rooms[rand() % rooms.size()].name;
17+
s.day = rand() % numDays;
18+
s.slot = rand() % slotsPerDay;
19+
sessions.push_back(s);
2020
}
2121
}
2222
}
2323

2424
int Timetable::computeConflicts() const {
2525
int conflicts = 0;
26-
27-
// Room & instructor conflicts
2826
for (size_t i = 0; i < sessions.size(); ++i) {
2927
for (size_t j = i + 1; j < sessions.size(); ++j) {
3028
const Session &a = sessions[i];
3129
const Session &b = sessions[j];
3230
if (a.day == b.day && a.slot == b.slot) {
33-
if (a.room == b.room) conflicts++; // Room conflict
34-
if (a.instructor == b.instructor) conflicts++; // Instructor conflict
31+
if (a.room == b.room) conflicts++;
32+
if (a.instructor == b.instructor) conflicts++;
3533
}
3634
}
3735
}
38-
39-
// Preferred time-slot conflicts
4036
for (auto &s : sessions) {
4137
auto it = find_if(courses.begin(), courses.end(),
4238
[&](const Course &c){ return c.name == s.courseName; });
4339
if (it != courses.end() && !it->allowedSlots.empty()) {
44-
if (find(it->allowedSlots.begin(), it->allowedSlots.end(), s.slot) == it->allowedSlots.end()) {
45-
conflicts++; // Penalize for being outside preferred slots
46-
}
40+
if (find(it->allowedSlots.begin(), it->allowedSlots.end(), s.slot) == it->allowedSlots.end())
41+
conflicts++;
4742
}
4843
}
49-
5044
return conflicts;
5145
}
5246

5347
void Timetable::print() const {
5448
vector<Session> sorted = sessions;
55-
sort(sorted.begin(), sorted.end(), [](const Session &a, const Session &b) {
49+
sort(sorted.begin(), sorted.end(), [](const Session &a, const Session &b){
5650
if (a.day != b.day) return a.day < b.day;
5751
return a.slot < b.slot;
5852
});
5953

6054
cout << "\n===== Optimized Timetable =====\n";
6155
for (auto &s : sorted) {
6256
string slotLabel = (s.slot >= 0 && s.slot < (int)slotLabels.size() && !slotLabels[s.slot].empty())
63-
? slotLabels[s.slot]
57+
? slotLabels[s.slot]
6458
: ("Slot " + to_string(s.slot + 1));
65-
66-
cout << "Day " << s.day + 1 << ", " << slotLabel
59+
cout << "Day " << s.day+1 << ", " << slotLabel
6760
<< " | " << s.courseName
6861
<< " | Instructor: " << s.instructor
6962
<< " | Room: " << s.room << "\n";
967 KB
Binary file not shown.

Src/Time_table_Generator/timetable.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ struct Course {
88
string name;
99
string instructor;
1010
int sessionsPerWeek;
11-
vector<int> allowedSlots; // Optional preferred slots (0-based index)
11+
vector<int> allowedSlots; // optional preferred slots (0-based)
1212
};
1313

1414
struct Room {
@@ -27,7 +27,7 @@ class Timetable {
2727
public:
2828
int numDays;
2929
int slotsPerDay;
30-
vector<string> slotLabels; // Custom time labels
30+
vector<string> slotLabels;
3131
vector<Room> rooms;
3232
vector<Course> courses;
3333
vector<Session> sessions;

Src/Time_table_Generator/utils.h

Lines changed: 95 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,111 @@
22
#define UTILS_H
33

44
#include "timetable.h"
5+
#include <regex>
6+
#include <iostream>
7+
#include <string>
58

6-
Timetable getUserInput() {
7-
int days, slots, numRooms, numCourses;
8-
cout << "Enter number of days in week: ";
9-
cin >> days;
10-
cout << "Enter slots per day: ";
11-
cin >> slots;
12-
13-
cout << "\nEnter a label or time for each slot (e.g., 9:00-10:00):\n";
14-
vector<string> slotLabels(slots);
15-
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // flush buffer
16-
for (int i = 0; i < slots; ++i) {
17-
cout << "Slot " << i + 1 << " label: ";
18-
getline(cin, slotLabels[i]);
19-
if (slotLabels[i].empty()) slotLabels[i] = "Slot " + to_string(i + 1);
9+
constexpr int MAX_DAYS = 7;
10+
constexpr int MAX_SLOTS = 24;
11+
constexpr int MAX_ROOMS = 50;
12+
constexpr int MAX_COURSES = 100;
13+
14+
// Robust numeric input
15+
inline int getSafeInt(const std::string &prompt, int minVal, int maxVal) {
16+
while (true) {
17+
std::cout << prompt;
18+
std::string line;
19+
std::getline(std::cin, line);
20+
21+
line.erase(0, line.find_first_not_of(" \t\n\r"));
22+
line.erase(line.find_last_not_of(" \t\n\r") + 1);
23+
24+
if (line.empty()) {
25+
std::cout << "Input cannot be empty! Please enter a number.\n";
26+
continue;
27+
}
28+
29+
bool allDigits = true;
30+
for (char c : line) {
31+
if (!isdigit(c)) { allDigits = false; break; }
32+
}
33+
if (!allDigits) {
34+
std::cout << "Invalid input! Enter a numeric integer.\n";
35+
continue;
36+
}
37+
38+
try {
39+
int val = std::stoi(line);
40+
if (val < minVal || val > maxVal) {
41+
std::cout << "Invalid input! Enter integer between " << minVal << " and " << maxVal << ".\n";
42+
continue;
43+
}
44+
return val;
45+
} catch (...) {
46+
std::cout << "Invalid input! Could not convert to number.\n";
47+
}
48+
}
49+
}
50+
51+
// Non-empty string input
52+
inline std::string getNonEmptyString(const std::string &prompt) {
53+
std::string s;
54+
while (true) {
55+
std::cout << prompt;
56+
std::getline(std::cin, s);
57+
if (!s.empty()) return s;
58+
std::cout << "Input cannot be empty! Please enter a valid value.\n";
2059
}
60+
}
61+
62+
// Validate slot label (HH:MM-HH:MM or single uppercase letter)
63+
inline bool isValidSlotLabel(const std::string &s) {
64+
std::regex timePattern(R"(\d{1,2}:\d{2}-\d{1,2}:\d{2})");
65+
std::regex letterPattern(R"([A-Z])");
66+
return std::regex_match(s, timePattern) || std::regex_match(s, letterPattern);
67+
}
2168

22-
cout << "\nEnter number of rooms: ";
23-
cin >> numRooms;
24-
vector<Room> rooms(numRooms);
25-
for (int i = 0; i < numRooms; ++i) {
26-
cout << "Room " << i + 1 << " name: ";
27-
cin >> rooms[i].name;
69+
inline std::string getSlotLabel(int i) {
70+
std::string label;
71+
while (true) {
72+
std::cout << "Slot " << i + 1 << " label (HH:MM-HH:MM or A-Z): ";
73+
std::getline(std::cin, label);
74+
if (label.empty()) label = "Slot " + std::to_string(i + 1);
75+
if (isValidSlotLabel(label)) break;
76+
std::cout << "Invalid format! Use HH:MM-HH:MM (09:00-10:00) or single letter (A-Z).\n";
2877
}
78+
return label;
79+
}
80+
81+
inline Timetable getUserInput() {
82+
int days = getSafeInt("Enter number of days in week (1-7): ", 1, MAX_DAYS);
83+
int slots = getSafeInt("Enter slots per day (1-24): ", 1, MAX_SLOTS);
84+
85+
std::vector<std::string> slotLabels(slots);
86+
std::cout << "\nEnter label or time for each slot:\n";
87+
for (int i = 0; i < slots; ++i) slotLabels[i] = getSlotLabel(i);
88+
89+
int numRooms = getSafeInt("\nEnter number of rooms (1-50): ", 1, MAX_ROOMS);
90+
std::vector<Room> rooms(numRooms);
91+
for (int i = 0; i < numRooms; ++i) rooms[i].name = getNonEmptyString("Room " + std::to_string(i + 1) + " name: ");
92+
93+
int numCourses = getSafeInt("\nEnter number of courses (1-100): ", 1, MAX_COURSES);
94+
std::vector<Course> courses(numCourses);
95+
96+
int totalAvailableSlots = days * slots * numRooms;
2997

30-
cout << "\nEnter number of courses: ";
31-
cin >> numCourses;
32-
vector<Course> courses(numCourses);
3398
for (int i = 0; i < numCourses; ++i) {
34-
cout << "\nCourse " << i + 1 << " name: ";
35-
cin >> courses[i].name;
36-
cout << "Instructor name: ";
37-
cin >> courses[i].instructor;
38-
cout << "Sessions per week: ";
39-
cin >> courses[i].sessionsPerWeek;
40-
41-
// Preferred slots
42-
cout << "Enter number of preferred slots for this course (0 for no preference): ";
43-
int prefCount;
44-
cin >> prefCount;
45-
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // flush leftover newline
99+
std::cout << "\nCourse " << i + 1 << " details:\n";
100+
courses[i].name = getNonEmptyString("Course name: ");
101+
courses[i].instructor = getNonEmptyString("Instructor name: ");
102+
courses[i].sessionsPerWeek = getSafeInt("Sessions per week: ", 1, totalAvailableSlots);
46103

104+
int prefCount = getSafeInt("Number of preferred slots (0 for none): ", 0, slots);
47105
courses[i].allowedSlots.clear();
48106
for (int j = 0; j < prefCount; ++j) {
49-
int slot;
50-
cout << "Preferred slot index (1-" << slots << "): ";
51-
cin >> slot;
52-
courses[i].allowedSlots.push_back(slot - 1); // 0-based index
107+
int slot = getSafeInt("Preferred slot index (1-" + std::to_string(slots) + "): ", 1, slots);
108+
courses[i].allowedSlots.push_back(slot - 1);
53109
}
54-
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // flush after the loop
55-
56110
}
57111

58112
return Timetable(days, slots, rooms, courses, slotLabels);

0 commit comments

Comments
 (0)