public bool CompareTo(LvCombination combination) { //Checking if parameters values are valid if (Combination == null || combination == null || combination.Combination == null) { throw new ArgumentException(); } //If number of groups isn't the same for both records if (Combination.Count != combination.Combination.Count) { return(false); } //Going trough each group in record 1, and checking if that group also exists in record 2 var tempCombination = new List <List <Student> >(combination.Combination); for (int i = 0; i < Combination.Count; i++) { var group = tempCombination.FirstOrDefault(r => Combination[i].Count == r.Count && Combination[i].Except(r).Any() == false); if (group == null) { return(false); } tempCombination.Remove(group); } return(true); }
private LvCombination GetBestLvCombination(List <LvCombination> combinations, int currentLvIndex, IProgress <double> progressPercentage, IProgress <TimeSpan> progressTimeLeft) { LvCombination bestCombination = null; bool firstCombination = true; int bestCombinationMaxSittingCount = 0; int bestCombinationStudentSittingDiff = 0; int bestCombinationMinMaxSum = 0; int bestCombinationMinSittingCount = 0; int bestCombinationMinAndMaxSittingCountCount = 0; int bestCombinationGroupRepetitionCount = 0; int loopCount = 0; var timeEstimator = new TimeLeftEstimator(); var combinationsLoopStopwatch = new Stopwatch(); combinationsLoopStopwatch.Start(); foreach (var combination in combinations) { _cancellationSource?.Token.ThrowIfCancellationRequested(); UpdateStudentHistory(combination, true); var studentSittingHistoryValues = GetStudentSittingHistoryValues().ToList(); bool isBestCombination = firstCombination || IsBestCombination(combination, studentSittingHistoryValues, bestCombinationMaxSittingCount, bestCombinationMinSittingCount, bestCombinationStudentSittingDiff, bestCombinationMinMaxSum, bestCombinationMinAndMaxSittingCountCount, bestCombinationGroupRepetitionCount); //Updating variables if this is the best combination so far if (isBestCombination) { //Variables are being populated using new data since some of the variables in this foreach loop might not have been updated (since that depends on what criteria the best combination was selected) bestCombination = combination; if (studentSittingHistoryValues.Count != 0) { bestCombinationMaxSittingCount = studentSittingHistoryValues.Max(); bestCombinationMinSittingCount = studentSittingHistoryValues.Min(); bestCombinationStudentSittingDiff = GetStudentSittingDiff(combination); bestCombinationMinMaxSum = GetMinMaxValues(combination).Sum(); bestCombinationGroupRepetitionCount = GetGroupRepetitionCount(combination); bestCombinationMinAndMaxSittingCountCount = GetMinAndMaxSittingCountCount(studentSittingHistoryValues); firstCombination = false; } } UpdateStudentHistory(combination, false); loopCount++; if (loopCount % 1000 == 0 || loopCount == combinations.Count) { combinationsLoopStopwatch.Stop(); timeEstimator?.AddTime(new TimeSpan(combinationsLoopStopwatch.ElapsedTicks)); var timeLeft = timeEstimator.GetTimeLeft((combinations.Count - loopCount) / 1000 + (_lvCount - currentLvIndex - 1) * combinations.Count / 1000); progressTimeLeft?.Report(timeLeft); progressPercentage?.Report((double)((currentLvIndex + (double)loopCount / combinations.Count) / _lvCount)); combinationsLoopStopwatch.Restart(); } } return(bestCombination); }
private int GetGroupRepetitionCount(LvCombination combination) { int count = 0; foreach (var group in combination.Combination) { foreach (var _combination in ShuffleResult) { foreach (var _group in _combination.Combination) { if (group.Count == _group.Count && group.Except(_group).Any() == false) { count++; } } } } return(count); }
private IEnumerable <int> GetMinMaxValues(LvCombination combination) { foreach (var group in combination.Combination) { foreach (var student in group) { int minMax = 0; if (student.StudentSittingHistory.Count != 0) { minMax = student.StudentSittingHistory.Values.Max(); if (student.StudentSittingHistory.Count == Students.Count - 1) { minMax -= student.StudentSittingHistory.Values.Min(); } } yield return(minMax); } } }
private static void UpdateStudentHistory(LvCombination combination, bool increment) { foreach (var groupCombination in combination.Combination) { foreach (var student in groupCombination) { foreach (var student2 in groupCombination) { if (student == student2) { continue; } if (student.StudentSittingHistory.ContainsKey(student2) == false) { student.StudentSittingHistory.Add(student2, 0); } student.StudentSittingHistory[student2] += (increment) ? 1 : -1; } } } }
private static int GetStudentSittingDiff(LvCombination combination) { int minCount = 0; int maxCount = 0; bool firstStudent = true; foreach (var groupCombination in combination.Combination) { foreach (var student in groupCombination) { int sittingCount = student.StudentSittingHistory.Values.Sum(); if (sittingCount > maxCount) { maxCount = sittingCount; } if (sittingCount < minCount || firstStudent) { minCount = sittingCount; firstStudent = false; } } } return(maxCount - minCount); }
/// <summary> /// This method returns all student sitting combinations for a lv /// </summary> /// <param name="takeOffset">Offset of the number which will be taken. For example, if every second combination is taken and offset is 3, following combinations will be returned: 5th, 7th, 9th, 11th...</param> /// <param name="takeEvery">Specifies which combinations will be returned. If there are 1000 combinations and <paramref name="maxCombinationCount"/> is 100, this parameter will be 10. That means that 10th, 20th, 30th etc. combinations will be returned</param> /// <returns></returns> private IEnumerable <LvCombination> GetLvCombinations(List <int> groupSizes, List <Student> students, double takeEvery = 0, double takeOffset = 0) { bool allGroupsAreSameSize = groupSizes.Distinct().Count() == 1; var sameGroupSizes = groupSizes.Where(s => s == groupSizes[0]).ToList(); //Setting takeEvery value if this is the first time this method was called if (takeEvery == 0 && takeOffset == 0) { takeEvery = (_maxCombinationCount <= 0) ? -1 : (double)new LvCombinationCountCalculator(groupSizes, groupSizes.Sum()).GetLvCombinationCount() / _maxCombinationCount; takeEvery = (takeEvery <= 1) ? -1 : takeEvery; } //Going trough each combination for the first group and getting all possible combinations for other groups Student currentFirstCombinationNumber = null; var availableStudents = new List <Student>(students); foreach (var firstGroupCombination in GetLvCombinationsForFirstGroup(groupSizes, students)) { //Returning first group combination if there is only 1 number group if (groupSizes.Count == 1) { //Skipping the combination if necessary if (takeOffset > 0) { takeOffset--; continue; } takeOffset += takeEvery - 1; yield return(new LvCombination(firstGroupCombination.Combination)); continue; } //Removing some available students if the first student in the combination changed if (sameGroupSizes.Count > 1) { var firstCombinationNumber = firstGroupCombination.Combination[0][0]; if (firstCombinationNumber != currentFirstCombinationNumber) { currentFirstCombinationNumber = firstCombinationNumber; availableStudents.Remove(firstCombinationNumber); } } //Skipping the combination if all combinations in this group would be skipped ulong combinationCount = GetCombinationCount(groupSizes, availableStudents); if (takeOffset - combinationCount > 0) { takeOffset -= combinationCount; continue; } //Getting all possible combinations for groups that have the same size as the first group if (sameGroupSizes.Count > 1) { var tempAvailableStudents = availableStudents.Except(firstGroupCombination.Combination[0]).ToList(); var tempGroupSizes = new List <int>(sameGroupSizes); tempGroupSizes.RemoveAt(0); var sameSizeCombinations = GetLvCombinations(tempGroupSizes, tempAvailableStudents, takeEvery, takeOffset); foreach (var sameSizeCombination in sameSizeCombinations) { var lvCombination = new LvCombination(new List <List <Student> >(firstGroupCombination.Combination)); lvCombination.Combination.AddRange(sameSizeCombination.Combination); //Returning the combination if all groups have the same size if (allGroupsAreSameSize) { takeOffset += takeEvery; yield return(lvCombination); continue; } //Getting all possible combinations for the groups that don't have the same size as the first group tempAvailableStudents = new List <Student>(students); tempAvailableStudents.RemoveAll(i => firstGroupCombination.Combination[0].Contains(i)); sameSizeCombination.Combination.ForEach(g => tempAvailableStudents.RemoveAll(i => g.Contains(i))); tempGroupSizes = groupSizes.Except(sameGroupSizes).ToList(); foreach (var otherGroupsCombination in GetLvCombinations(tempGroupSizes, tempAvailableStudents, takeEvery, takeOffset)) { //Combining all of the group combinations into 1 combination otherGroupsCombination.Combination.InsertRange(0, lvCombination.Combination); takeOffset += takeEvery; yield return(otherGroupsCombination); } } } else { //Getting number combinations for other groups var tempAvailableStudents = availableStudents.Except(firstGroupCombination.Combination[0]).ToList(); var tempGroupSizes = groupSizes.Where(s => s != groupSizes[0]).ToList(); foreach (var c in GetLvCombinations(tempGroupSizes, tempAvailableStudents, takeEvery, takeOffset)) { //Combining all of the group combinations into 1 combination c.Combination.Insert(0, firstGroupCombination.Combination[0]); takeOffset += takeEvery; yield return(c); } } takeOffset -= combinationCount; } }
/// <summary> /// This method is used to create best student sitting combinations based on the fields in this class /// </summary> /// <param name="progressPercentage">Completed percentage for the shuffle step which is being executed at that moment</param> /// <param name="progressText">Text containing data about shuffle step which is being executed at that moment</param> /// <param name="progressTimeLeft">Estimated time left for shuffle step which is being executed at that moment to finish</param> /// <returns></returns> public void Shuffle(IProgress <double> progressPercentage = null, IProgress <string> progressText = null, IProgress <TimeSpan> progressTimeLeft = null) { /*BEST COMBINATION ALGORITHM EXPLANATION: * An algorithm which calculates best combination takes 6 parameters into consideration: * * 1st (most important) parameter is maxSittingCount: * max amount of times a student sat with with another student. * Lower amount is better. * * 2nd parameter is minSittingCount: * min amount of times a student sat with another student. * Higher is better. * * 3rd parameter is studentSittingDiff: * (highest sum of student sitting values for a student) - (lowest sum of student sitting value for a student (doesn't need to be the same student)). * Lower number is better since that forces the students to sit in groups of various sizes, instead of alwaays sitting in the biggest/smalled group. * * 4th parameter is minMaxSum: * sum of (max amount of times a student sat with another student) - (min amount of times that same student student sat with another student) for all students. * Lower is better. * * 5th parameter is minAndMaxSittingCountCount: * count of student sitting count values (student sitting count values - amount of times students sat with each other) where student sitting count is min or max. * Lower is better since it means that the deviation from average student sitting count is lower. * * 6th (least important) parameter is groupRepetitionCount: * Sum of amount of times each group in the combination was used in previous combinations * * Algorithm is pretty much doing * lvCombinations.OrderBy(1st parameter).OrderBy(2nd parameter).OrderBy(3nd parameter).OrderBy(4th parameter).First(), * but doing it manually since progress could otherwise only be updated once the best combination is found (which can take a lot of time)*/ //Setup foreach (var student in Students) { student.StudentSittingHistory.Clear(); } _shuffleResult.Clear(); progressPercentage?.Report(0); progressText?.Report("Računanje svih kombinacija sjedenja"); progressTimeLeft?.Report(new TimeSpan(0)); ulong combinationCount = new LvCombinationCountCalculator(_groups.Select(g => g.Size).ToList(), _students.Count).GetLvCombinationCount(); if (combinationCount > (ulong)MaxCombinationCount) { combinationCount = (ulong)MaxCombinationCount; } //Going trough each LV var combinations = GetLvCombinations(progressPercentage, progressTimeLeft, combinationCount); progressPercentage?.Report(0); progressText?.Report("Rasporeda se stvara"); progressTimeLeft?.Report(new TimeSpan(0)); for (int lv = 0; lv < _lvCount; lv++) { //Getting best student sitting combination for current lv LvCombination bestCombination = GetBestLvCombination(combinations, lv, progressPercentage, progressTimeLeft); //Updating shuffle result and progress UpdateStudentHistory(bestCombination, true); _shuffleResult.Add(bestCombination); //If every student sat with other students the same amount of times, there is no need to do further calculations since other lv's would just be repeats of current student sitting combinations if (GetStudentSittingHistoryValues().Distinct().Count() <= 1) { break; } } progressTimeLeft?.Report(new TimeSpan(0)); //DEBUGGING OUTPUT: used for testing purposes Debug_PrintResult(); }
private bool IsBestCombination(LvCombination combination, List <int> studentSittingHistoryValues, int bestCombinationMaxSittingCount, int bestCombinationMinSittingCount, int bestCombinationStudentSittingDiff, int bestCombinationMinMaxSum, int bestCombinationMinAndMaxSittingCountCount, int bestCombinationGroupRepetitionCount) { //Checking if max sitting count is better int maxSittingCount = studentSittingHistoryValues.Max(); if (maxSittingCount < bestCombinationMaxSittingCount) { return(true); } if (maxSittingCount > bestCombinationMaxSittingCount) { return(false); } //Checking if min sitting count is better int minSittingCount = studentSittingHistoryValues.Min(); if (minSittingCount > bestCombinationMinSittingCount) { return(true); } else if (minSittingCount < bestCombinationMinSittingCount) { return(false); } //Checking if min student sitting diff is better int studentSittingDiff = GetStudentSittingDiff(combination); if (studentSittingDiff < bestCombinationStudentSittingDiff) { return(true); } else if (studentSittingDiff > bestCombinationStudentSittingDiff) { return(false); } //Checking if minMaxDiff is better int minMaxSum = GetMinMaxValues(combination).Sum(); if (minMaxSum < bestCombinationMinMaxSum) { return(true); } else if (minMaxSum > bestCombinationMinMaxSum) { return(false); } //Checking if minMinAndMax sitting count is better or if min sitting count is higher or if max sitting count is lower int minAndMaxSittingCountCount = GetMinAndMaxSittingCountCount(studentSittingHistoryValues); if (bestCombinationMinAndMaxSittingCountCount < minAndMaxSittingCountCount) { return(true); } else if (bestCombinationMinAndMaxSittingCountCount > minAndMaxSittingCountCount) { return(false); } //Checking is group repetition count is better int groupRepetitionCount = GetGroupRepetitionCount(combination); if (groupRepetitionCount < bestCombinationGroupRepetitionCount) { return(true); } return(false); }