using System; using System.Collections.Generic; using System.Linq; namespace spplus; public class CourseCrafter { public class CourseInstance { public Sport Sport = null!; public int Remaining; public List Students = new(); } public static List<(int Semester, CourseInstance Instance)> GeneratedCourses = new(); public static void Craft() { GeneratedCourses = new(); int globalCount = 0; List<(Sport, List)> initial_sportlist = new(); List[] students_in_semester = new List[4] { new(), new(), new(), new() }; foreach (var sp in Settings.Instance.Sports) { initial_sportlist.Add((sp, new())); } foreach (Student s in Settings.Instance.Students) { foreach (var sp in s.SelectedCourseNames) { foreach (var item in initial_sportlist) { if (item.Item1.AlternativeNames.Contains(sp)) { item.Item2.Add(s.ID); break; } } } } while (!requestExit()) { Console.WriteLine($"Calculating... ({globalCount})"); foreach (var item in initial_sportlist) { if (item.Item2.Count >= item.Item1.MinStudents) { int semester = getSemesterForSport(item.Item1); if (semester <= 0) goto semeq0; var inst = new CourseInstance(); inst.Sport = item.Item1; inst.Students = new List(); // int dist = 1; for (int i = item.Item2.Count - 1; i >= 0; i--) { if (inst.Students.Count >= inst.Sport.MaxStudents) break; string stud = item.Item2[i]; if (!students_in_semester[semester - 1].Contains(stud)) { inst.Students.Add(stud); students_in_semester[semester - 1].Add(stud); item.Item2.RemoveAt(i); } } if (inst.Students.Count < inst.Sport.MinStudents) { // Rückgängig machen foreach (var s in inst.Students) { students_in_semester[semester-1].Remove(s); item.Item2.Add(s); } continue; // Kurs nicht erstellen } GeneratedCourses.Add((semester, inst)); MainWindow.Instance.TbResultLog.Text += ($"{semester} -> {inst.Students.Count}\n"); MainWindow.Instance.TbResultLog.Text += ($"{students_in_semester[0].Count} - {students_in_semester[1].Count} - {students_in_semester[2].Count} - {students_in_semester[3].Count}\n\n"); } semeq0: ; } } // Kurse auffüllen (mit restl. Leuten) foreach (var item in initial_sportlist) { if (item.Item2.Count > 0) { foreach (var ci in GeneratedCourses) { if (item.Item1.ID == ci.Instance.Sport.ID) { int semester = ci.Semester; List added = new(); foreach (string stud in item.Item2) { if (ci.Instance.Students.Count >= ci.Instance.Sport.MaxStudents) break; if (!students_in_semester[semester-1].Contains(stud)) { ci.Instance.Students.Add(stud); students_in_semester[semester-1].Add(stud); //ci.Instance.Students.Add(stud); added.Add(stud); } } // Hinzugefügte aus Initialkurs entfernen foreach (string s in added) { item.Item2.Remove(s); } } MainWindow.Instance.TbResultLog.Text += ($"{ci.Semester} -> {ci.Instance.Students.Count}\n"); MainWindow.Instance.TbResultLog.Text += ($"{students_in_semester[0].Count} - {students_in_semester[1].Count} - {students_in_semester[2].Count} - {students_in_semester[3].Count}\n\n"); } } } // Kurs umdisponieren (besser verteilen) // Kurs umdisponieren (besser verteilen) bool changed; int maxIterations = 20; int iteration = 0; do { changed = false; iteration++; // nach Sport gruppieren var sports = GeneratedCourses .GroupBy(c => c.Instance.Sport.ID); foreach (var sportGroup in sports) { var courses = sportGroup.ToList(); // paarweise vergleichen for (int i = 0; i < courses.Count; i++) { for (int j = 0; j < courses.Count; j++) { if (i == j) continue; var cA = courses[i]; var cB = courses[j]; // nur sinnvoll, wenn Unterschied if (cA.Instance.Students.Count <= cB.Instance.Students.Count + 1) continue; // Kandidaten aus A nach B verschieben for (int k = cA.Instance.Students.Count - 1; k >= 0; k--) { string stud = cA.Instance.Students[k]; // 1. Zielsemester frei? if (!isStudentFree(cB.Semester, stud)) continue; // 2. Zielkurs hat noch Platz? if (cB.Instance.Students.Count >= cB.Instance.Sport.MaxStudents) continue; // 3. Quellkurs darf nicht unter Min fallen if (cA.Instance.Students.Count - 1 < cA.Instance.Sport.MinStudents) continue; // --- MOVE durchführen --- cA.Instance.Students.RemoveAt(k); students_in_semester[cA.Semester - 1].Remove(stud); cB.Instance.Students.Add(stud); students_in_semester[cB.Semester - 1].Add(stud); changed = true; break; // nach jedem Move neu bewerten } } } } } while (changed && iteration < maxIterations); int getSemester() { //int sem = 0; if (GeneratedCourses.Count == 0) return 1; // zunächst im ersten Semester beginnen int[] semcount = new int[4] {0,0,0,0}; // Anzahl der generierten Kurse im jeweiligen Semester foreach (var inst in GeneratedCourses) { semcount[inst.Semester - 1]++; // ...füllen } for (int i = 0; i= Settings.Instance.NumCoursesPerSemester * 4) return true; int low = 0; foreach (var item in initial_sportlist) { if (item.Item2.Count < item.Item1.MinStudents) low++; } if (low >= initial_sportlist.Count) return true; if (globalCount >= 20) return true; return false; } foreach (var tuple in initial_sportlist) { MainWindow.Instance.TbResultTextout.Text += $"{tuple.Item1}: {tuple.Item2.Count} remaining\n"; } int getSemesterForSport(Sport sp) { int[] semcount = new int[4]; foreach (var inst in GeneratedCourses) semcount[inst.Semester - 1]++; int bestSem = 0; int minCourses = int.MaxValue; for (int i = 0; i < 4; i++) { if (sp.Semester[i] == 0) continue; if (semcount[i] < Settings.Instance.NumCoursesPerSemester && semcount[i] < minCourses) { minCourses = semcount[i]; bestSem = i + 1; } } return bestSem; } } public static string GenerateStatistics() { string sb = $"Generierte Kurse: {GeneratedCourses.Count}\n\n"; foreach (var genc in GeneratedCourses) { sb += $"Sem. {genc.Semester}: {genc.Instance.Sport.Name} ({genc.Instance.Students.Count} SuS)\n"; } return sb; } public static string GenerateStatisticsOld() { var settings = Settings.Instance; var students = settings.Students; if (GeneratedCourses == null || GeneratedCourses.Count == 0) return "Keine Kurse generiert."; int semesterCount = students .Where(s => s.Result != null) .Select(s => s.Result!.Count) .DefaultIfEmpty(0) .Max(); var sb = new System.Text.StringBuilder(); sb.AppendLine($"Anzahl generierter Kurse: {GeneratedCourses.Count}"); sb.AppendLine("Übersicht:"); // ===== Kursübersicht ===== var grouped = GeneratedCourses .GroupBy(g => new { g.Semester, g.Instance.Sport.Name }) .OrderBy(g => g.Key.Semester) .ThenBy(g => g.Key.Name); foreach (var group in grouped) { int counter = 1; foreach (var entry in group) { int semester = group.Key.Semester + 1; string sportName = group.Key.Name; string number = counter.ToString("D2"); int count = entry.Instance.Students.Count; sb.AppendLine( $"Semester {semester}: {sportName} {number}: {count} Schüler*innen" ); counter++; } } sb.AppendLine(); sb.AppendLine("Fehlerübersicht:"); // ===== Fehler pro Semester ===== for (int sem = 0; sem < semesterCount; sem++) { int errors = students.Count(st => st.Result != null && st.Result.Count > sem && st.Result[sem] == "Fehler"); sb.AppendLine($"Semester {sem + 1}: {errors} Fehler"); } return sb.ToString(); } }