diff --git a/Src/Time_table_Generator/genetic_algorithm.cpp b/Src/Time_table_Generator/genetic_algorithm.cpp new file mode 100644 index 0000000..45994a9 --- /dev/null +++ b/Src/Time_table_Generator/genetic_algorithm.cpp @@ -0,0 +1,68 @@ +#include "genetic_algorithm.h" +#include +#include +#include +#include +using namespace std; + +Timetable GeneticAlgorithm::crossover(const Timetable& p1, const Timetable& p2) { + Timetable child = p1; + size_t half = p1.sessions.size() / 2; + for (size_t i = half; i < p1.sessions.size(); ++i) + child.sessions[i] = p2.sessions[i]; + return child; +} + +void GeneticAlgorithm::mutate(Timetable& t) { + if (t.rooms.empty() || t.slotsPerDay <= 0 || t.numDays <= 0) return; + + for (auto &s : t.sessions) { + if ((rand() / (double)RAND_MAX) < mutationRate) { + s.day = rand() % t.numDays; + s.slot = rand() % t.slotsPerDay; + s.room = t.rooms[rand() % t.rooms.size()].name; + } + } +} + +Timetable GeneticAlgorithm::run(const Timetable& base, bool verbose) { + if (base.rooms.empty() || base.slotsPerDay <= 0 || base.numDays <= 0) return base; + + vector population; + for (int i = 0; i < populationSize; ++i) { + Timetable t = base; + t.generateRandom(); + population.push_back(t); + } + + Timetable best = population[0]; + int bestFitness = best.computeConflicts(); + + for (int gen = 0; gen < generations; ++gen) { + sort(population.begin(), population.end(), + [](const Timetable &a, const Timetable &b){ return a.computeConflicts() < b.computeConflicts(); }); + + if (population[0].computeConflicts() < bestFitness) { + best = population[0]; + bestFitness = best.computeConflicts(); + } + + if (bestFitness == 0) break; + + vector newPop; + for (int i = 0; i < populationSize/2; ++i) { + Timetable c1 = crossover(population[i], population[rand() % populationSize]); + Timetable c2 = crossover(population[rand() % populationSize], population[i]); + mutate(c1); + mutate(c2); + newPop.push_back(c1); + newPop.push_back(c2); + } + population = newPop; + + if (verbose && gen % 10 == 0) + cout << "Generation " << gen << " | Best Conflicts: " << bestFitness << "\n"; + } + + return best; +} diff --git a/Src/Time_table_Generator/genetic_algorithm.h b/Src/Time_table_Generator/genetic_algorithm.h new file mode 100644 index 0000000..0f3162f --- /dev/null +++ b/Src/Time_table_Generator/genetic_algorithm.h @@ -0,0 +1,22 @@ +#ifndef GENETIC_ALGORITHM_H +#define GENETIC_ALGORITHM_H + +#include "timetable.h" + +class GeneticAlgorithm { +public: + int populationSize; + int generations; + double mutationRate; + + GeneticAlgorithm(int pop, int gen, double mut) + : populationSize(pop), generations(gen), mutationRate(mut) {} + + Timetable run(const Timetable& base, bool verbose = true); + +private: + Timetable crossover(const Timetable& p1, const Timetable& p2); + void mutate(Timetable& t); +}; + +#endif diff --git a/Src/Time_table_Generator/main.cpp b/Src/Time_table_Generator/main.cpp new file mode 100644 index 0000000..1f12f71 --- /dev/null +++ b/Src/Time_table_Generator/main.cpp @@ -0,0 +1,30 @@ +#include "timetable.h" +#include "genetic_algorithm.h" +#include "utils.h" +#include +using namespace std; + +int main() { + srand(time(nullptr)); + cout << "\n=== C++ Timetable Generator (Genetic Algorithm) ===\n"; + + Timetable base = getUserInput(); + + int totalRequiredSessions = 0; + for (auto &c : base.courses) totalRequiredSessions += c.sessionsPerWeek; + int totalAvailableSlots = base.numDays * base.slotsPerDay * base.rooms.size(); + + if (totalRequiredSessions > totalAvailableSlots) { + cerr << "\n❌ Scheduling impossible: Total required sessions (" + << totalRequiredSessions << ") exceed total available slots (" + << totalAvailableSlots << ").\n"; + cerr << "Try reducing sessions or increasing rooms/days/slots.\n"; + return 1; + } + + GeneticAlgorithm ga(30, 100, 0.1); + Timetable optimized = ga.run(base, true); + + cout << "\nFinal Best Timetable (Conflicts: " << optimized.computeConflicts() << ")\n"; + optimized.print(); +} diff --git a/Src/Time_table_Generator/timetable.cpp b/Src/Time_table_Generator/timetable.cpp new file mode 100644 index 0000000..40f29db --- /dev/null +++ b/Src/Time_table_Generator/timetable.cpp @@ -0,0 +1,65 @@ +#include "timetable.h" +#include +#include +using namespace std; + +void Timetable::generateRandom() { + if (rooms.empty() || slotsPerDay <= 0 || numDays <= 0) return; + + sessions.clear(); + srand(time(nullptr)); + for (auto &course : courses) { + for (int i = 0; i < course.sessionsPerWeek; ++i) { + Session s; + s.courseName = course.name; + s.instructor = course.instructor; + s.room = rooms[rand() % rooms.size()].name; + s.day = rand() % numDays; + s.slot = rand() % slotsPerDay; + sessions.push_back(s); + } + } +} + +int Timetable::computeConflicts() const { + int conflicts = 0; + for (size_t i = 0; i < sessions.size(); ++i) { + for (size_t j = i + 1; j < sessions.size(); ++j) { + const Session &a = sessions[i]; + const Session &b = sessions[j]; + if (a.day == b.day && a.slot == b.slot) { + if (a.room == b.room) conflicts++; + if (a.instructor == b.instructor) conflicts++; + } + } + } + for (auto &s : sessions) { + auto it = find_if(courses.begin(), courses.end(), + [&](const Course &c){ return c.name == s.courseName; }); + if (it != courses.end() && !it->allowedSlots.empty()) { + if (find(it->allowedSlots.begin(), it->allowedSlots.end(), s.slot) == it->allowedSlots.end()) + conflicts++; + } + } + return conflicts; +} + +void Timetable::print() const { + vector sorted = sessions; + sort(sorted.begin(), sorted.end(), [](const Session &a, const Session &b){ + if (a.day != b.day) return a.day < b.day; + return a.slot < b.slot; + }); + + cout << "\n===== Optimized Timetable =====\n"; + for (auto &s : sorted) { + string slotLabel = (s.slot >= 0 && s.slot < (int)slotLabels.size() && !slotLabels[s.slot].empty()) + ? slotLabels[s.slot] + : ("Slot " + to_string(s.slot + 1)); + cout << "Day " << s.day+1 << ", " << slotLabel + << " | " << s.courseName + << " | Instructor: " << s.instructor + << " | Room: " << s.room << "\n"; + } + cout << "===============================\n"; +} diff --git a/Src/Time_table_Generator/timetable.exe b/Src/Time_table_Generator/timetable.exe new file mode 100644 index 0000000..939ad7d Binary files /dev/null and b/Src/Time_table_Generator/timetable.exe differ diff --git a/Src/Time_table_Generator/timetable.h b/Src/Time_table_Generator/timetable.h new file mode 100644 index 0000000..5c74d11 --- /dev/null +++ b/Src/Time_table_Generator/timetable.h @@ -0,0 +1,45 @@ +#ifndef TIMETABLE_H +#define TIMETABLE_H + +#include +using namespace std; + +struct Course { + string name; + string instructor; + int sessionsPerWeek; + vector allowedSlots; // optional preferred slots (0-based) +}; + +struct Room { + string name; +}; + +struct Session { + string courseName; + string instructor; + string room; + int day; + int slot; +}; + +class Timetable { +public: + int numDays; + int slotsPerDay; + vector slotLabels; + vector rooms; + vector courses; + vector sessions; + + Timetable() {} + Timetable(int d, int s, const vector& r, const vector& c, + const vector& labels) + : numDays(d), slotsPerDay(s), rooms(r), courses(c), slotLabels(labels) {} + + void generateRandom(); + int computeConflicts() const; + void print() const; +}; + +#endif diff --git a/Src/Time_table_Generator/utils.h b/Src/Time_table_Generator/utils.h new file mode 100644 index 0000000..807dcbb --- /dev/null +++ b/Src/Time_table_Generator/utils.h @@ -0,0 +1,115 @@ +#ifndef UTILS_H +#define UTILS_H + +#include "timetable.h" +#include +#include +#include + +constexpr int MAX_DAYS = 7; +constexpr int MAX_SLOTS = 24; +constexpr int MAX_ROOMS = 50; +constexpr int MAX_COURSES = 100; + +// Robust numeric input +inline int getSafeInt(const std::string &prompt, int minVal, int maxVal) { + while (true) { + std::cout << prompt; + std::string line; + std::getline(std::cin, line); + + line.erase(0, line.find_first_not_of(" \t\n\r")); + line.erase(line.find_last_not_of(" \t\n\r") + 1); + + if (line.empty()) { + std::cout << "Input cannot be empty! Please enter a number.\n"; + continue; + } + + bool allDigits = true; + for (char c : line) { + if (!isdigit(c)) { allDigits = false; break; } + } + if (!allDigits) { + std::cout << "Invalid input! Enter a numeric integer.\n"; + continue; + } + + try { + int val = std::stoi(line); + if (val < minVal || val > maxVal) { + std::cout << "Invalid input! Enter integer between " << minVal << " and " << maxVal << ".\n"; + continue; + } + return val; + } catch (...) { + std::cout << "Invalid input! Could not convert to number.\n"; + } + } +} + +// Non-empty string input +inline std::string getNonEmptyString(const std::string &prompt) { + std::string s; + while (true) { + std::cout << prompt; + std::getline(std::cin, s); + if (!s.empty()) return s; + std::cout << "Input cannot be empty! Please enter a valid value.\n"; + } +} + +// Validate slot label (HH:MM-HH:MM or single uppercase letter) +inline bool isValidSlotLabel(const std::string &s) { + std::regex timePattern(R"(\d{1,2}:\d{2}-\d{1,2}:\d{2})"); + std::regex letterPattern(R"([A-Z])"); + return std::regex_match(s, timePattern) || std::regex_match(s, letterPattern); +} + +inline std::string getSlotLabel(int i) { + std::string label; + while (true) { + std::cout << "Slot " << i + 1 << " label (HH:MM-HH:MM or A-Z): "; + std::getline(std::cin, label); + if (label.empty()) label = "Slot " + std::to_string(i + 1); + if (isValidSlotLabel(label)) break; + std::cout << "Invalid format! Use HH:MM-HH:MM (09:00-10:00) or single letter (A-Z).\n"; + } + return label; +} + +inline Timetable getUserInput() { + int days = getSafeInt("Enter number of days in week (1-7): ", 1, MAX_DAYS); + int slots = getSafeInt("Enter slots per day (1-24): ", 1, MAX_SLOTS); + + std::vector slotLabels(slots); + std::cout << "\nEnter label or time for each slot:\n"; + for (int i = 0; i < slots; ++i) slotLabels[i] = getSlotLabel(i); + + int numRooms = getSafeInt("\nEnter number of rooms (1-50): ", 1, MAX_ROOMS); + std::vector rooms(numRooms); + for (int i = 0; i < numRooms; ++i) rooms[i].name = getNonEmptyString("Room " + std::to_string(i + 1) + " name: "); + + int numCourses = getSafeInt("\nEnter number of courses (1-100): ", 1, MAX_COURSES); + std::vector courses(numCourses); + + int totalAvailableSlots = days * slots * numRooms; + + for (int i = 0; i < numCourses; ++i) { + std::cout << "\nCourse " << i + 1 << " details:\n"; + courses[i].name = getNonEmptyString("Course name: "); + courses[i].instructor = getNonEmptyString("Instructor name: "); + courses[i].sessionsPerWeek = getSafeInt("Sessions per week: ", 1, totalAvailableSlots); + + int prefCount = getSafeInt("Number of preferred slots (0 for none): ", 0, slots); + courses[i].allowedSlots.clear(); + for (int j = 0; j < prefCount; ++j) { + int slot = getSafeInt("Preferred slot index (1-" + std::to_string(slots) + "): ", 1, slots); + courses[i].allowedSlots.push_back(slot - 1); + } + } + + return Timetable(days, slots, rooms, courses, slotLabels); +} + +#endif