/// <summary> /// This method will convert the current Splits /// to simple numered description. /// we just want numbers, not the list of cars /// because it is easier to make average on it. /// --- /// Exemple : // {Split: 1, C7: 11 cars} // {Split: 2, C7: 11 cars} // {Split: 5, C7: 11 cars} // {Split: 8, C7: 8 cars} /// </summary> /// <returns></returns> private List <MultiClassChanges> ConvertCurrentSplitsToSplitDescriptions() { List <MultiClassChanges> modes = new List <MultiClassChanges>(); // foreach splits foreach (var item in Splits) { // split descrpition using the 'MultiClassChanges' class MultiClassChanges m = new MultiClassChanges(); // set the split number. // (we will not use range here) // (or if you prefer, it will be ranges of 1) :D m.FromSplit = item.Number; m.ToSplit = item.Number; // number of classes in the split m.ClassesCount = item.GetClassesCount(); m.ClassCarsTarget = new Dictionary <int, int>(); // numbers of cars in each class // dictionnary ClassCarsTarget // KEY : class id // VALUE : cars count foreach (var classid in carClassesIds) { int classIndex = carClassesIds.IndexOf(classid); int classCarCount = item.CountClassCars(classIndex); if (classCarCount > 0) { m.ClassCarsTarget.Add(classid, classCarCount); } } // add that description to the return list of that method modes.Add(m); } // foreach splits end // return the list return(modes); }
/// <summary> /// If there is split with more cars than field Size, fix it /// </summary> /// <param name="splitsDescriptions"></param> private void SolveSplitsExceedFieldSize(List <MultiClassChanges> splitsDescriptions) { // foreach split foreach (var splitDescription in splitsDescriptions) { // while the split is in excess while (splitDescription.CountTotalTargets() > fieldSize) { // get the class with more cars var excess = (from r in splitDescription.ClassCarsTarget orderby r.Value descending select r).FirstOrDefault(); int classid = excess.Key; // find the uppest split possible containing the same class // and a free avaiable slot var splitWithSameClassAndSlotAvailable = (from r in splitsDescriptions where r.ClassCarsTarget.ContainsKey(excess.Key) && r.CountTotalTargets() < fieldSize orderby r.ClassCarsTarget[excess.Key] ascending select r).FirstOrDefault(); // yes, found it if (splitWithSameClassAndSlotAvailable != null) { // move the car to it splitWithSameClassAndSlotAvailable.ClassCarsTarget[classid]++; splitDescription.ClassCarsTarget[classid]--; } // no, not any possible else { // get the classes id, from most populated to less List <int> mostpopulatedclassed = carClassesIds.ToList(); mostpopulatedclassed.Reverse(); mostpopulatedclassed.Remove(classid); // but remove the current class // --> // we will do a pool shot in two moves // foreach most populated class bool solutionfound = false; foreach (int mostpopulatedclass in mostpopulatedclassed) { // find the split having the less cars possible, the lowest possible // containing the mostpopulatedclass // and thecontaining current class 'classid' var othersplit1 = (from r in splitsDescriptions where r.ClassCarsTarget.ContainsKey(classid) && r.ClassCarsTarget.ContainsKey(mostpopulatedclass) && r.ClassCarsTarget[mostpopulatedclass] > 0 orderby r.CountTotalTargets() ascending, r.ToSplit descending select r).FirstOrDefault(); // find split containing the mostpopulatedclass, the uppest possible // with the lowest car possible var othersplit2 = (from r in splitsDescriptions where r.ClassCarsTarget.ContainsKey(mostpopulatedclass) && r.CountTotalTargets() < fieldSize && r.ClassCarsTarget[mostpopulatedclass] > 0 orderby r.CountTotalTargets() ascending, r.ToSplit descending select r).FirstOrDefault(); // it these 2 splits are found if (othersplit1 != null && othersplit2 != null) { // in otherslit1 : change a mostpopulatedclass slot with a classid slot othersplit1.ClassCarsTarget[mostpopulatedclass]--; othersplit1.ClassCarsTarget[classid]++; // in otherslit1 : finaly add the missing car othersplit2.ClassCarsTarget[mostpopulatedclass]++; // to end solving the problem, we can now remove the car in excess to our current split splitDescription.ClassCarsTarget[classid]--; // break the 'foreach most populated class' // we don't need to try with another class // we solved the problem solutionfound = true; break; } else { // not found too... // we will try with the next mostpopulatedclass } } if (!solutionfound) { var newsplit = new MultiClassChanges() { FromSplit = Splits.Count, ToSplit = Splits.Count, ClassCarsTarget = new Dictionary <int, int>() }; for (int i = 0; i < carClassesIds.Count; i++) { // algo.TakeCars(carClassesIds[i], carClassesIds, fieldSize) //int cars = 0; //if (carClassesIds[i] == excess.Key) cars = excess.Value; newsplit.ClassCarsTarget.Add(carClassesIds[i], 0); } splitsDescriptions.Add(newsplit); SolveSplitsExceedFieldSize(splitsDescriptions); Split newSplit = new Split(Splits.Count); Splits.Add(newSplit); solutionfound = true; return; } } } } // end of foreach split }
public void Compute(List <Line> data, int fieldSize) { // Split cars per class var carsListPerClass = Tools.SplitCarsPerClass(data); // Create two dictionnary (KEY for both is the CarClass Id) // classRemainingCars : VALUE is the number of remaining cars in the class // classSplitsCount : VALUE is a list containing the number of car split per split Dictionary <int, int> classRemainingCars = new Dictionary <int, int>(); Dictionary <int, List <int> > classSplitsCount = new Dictionary <int, List <int> >(); foreach (var carClass in carsListPerClass) { classRemainingCars.Add(carClass.CarClassId, carClass.Cars.Count); classSplitsCount.Add(carClass.CarClassId, new List <int>()); } // export classes id CarClassesId = new List <int>(); foreach (var carClass in carsListPerClass) { CarClassesId.Add(carClass.CarClassId); } // MultiClassMode records describes changes on split car classes compositions // - FromSplit and ToSplit describes the range of splits // - ClassesCount describes how many car classes can be part of the splits // (exemple: 3 first for LMP1/LMP2/GTE, then 2 when it become LMP1/GTE because not enought LMP2 are available, then 1 when single class)... List <MultiClassChanges> modes = new List <MultiClassChanges>(); MultiClassChanges currentMode = new MultiClassChanges(); currentMode.FromSplit = 1; currentMode.ToSplit = 1; currentMode.ClassesCount = classRemainingCars.Count; modes.Add(currentMode); int splitCounter = 1; while (SumValues(classRemainingCars) > 0) // when cars remaings { // count classes containing remaining cars int remCarClasses = (from r in classRemainingCars where r.Value > 0 select r).Count(); foreach (var carClass in carsListPerClass) { // count cars to take in this class int takeCars = fieldSize; takeCars = TakeClassCars(fieldSize, remCarClasses, classRemainingCars, carClass.CarClassId, carsListPerClass, splitCounter); // if not enought remianing cars than wanted, take what is possible int carClassSize = Math.Min(takeCars, classRemainingCars[carClass.CarClassId]); classSplitsCount[carClass.CarClassId].Add(carClassSize); // save the number of car in the class for this split classRemainingCars[carClass.CarClassId] -= carClassSize; // decrement reminaing cars in the class } var lastClass = carsListPerClass.LastOrDefault(); //get last class, which is the class with more cars then the other if (lastClass != null) { // sum cars in this split int carsInThisSplit = 0; foreach (var carClass in carsListPerClass) { carsInThisSplit += classSplitsCount[carClass.CarClassId].Last(); } // --> // available slots ? if (carsInThisSplit < fieldSize) { int availableSlots = fieldSize - carsInThisSplit; // fill this availableSlots with last class cars to match the maximum field size.. var splitclasslist = classSplitsCount[lastClass.CarClassId]; splitclasslist[splitclasslist.Count - 1] += availableSlots; // and decremement remaining cars if this last class classRemainingCars[lastClass.CarClassId] -= availableSlots; } } // is there always the same number of car class than the previous split ? if (remCarClasses == currentMode.ClassesCount) { // yes, just update the ToSplit number currentMode.ToSplit = splitCounter; } else { // no, save a change starting from this split currentMode = new MultiClassChanges(); currentMode.FromSplit = splitCounter; currentMode.ToSplit = splitCounter; currentMode.ClassesCount = remCarClasses; modes.Add(currentMode); } splitCounter++; } // create the array of splits Splits = new List <Split>(); int maxsplit = (from r in modes select r.ToSplit).Max(); for (int i = 1; i <= maxsplit; i++) { var split = new Split(); split.Number = i; Splits.Add(split); } // reset the classRemainingCars counts classRemainingCars.Clear(); foreach (var carClass in carsListPerClass) { classRemainingCars.Add(carClass.CarClassId, carClass.Cars.Count); } // for each car class foreach (var carClass in carsListPerClass) { // for each split for (int i = 1; i <= maxsplit; i++) { var split = Splits[i - 1]; // get the split record in the array of splits // get the MultiClassMode where this split is in, the target cars count for the classes var mode = (from r in modes where i >= r.FromSplit orderby r.ToSplit descending select r).First(); int take = fieldSize / mode.ClassesCount; // save the class target cars count in this class split.SetClassTarget(carsListPerClass.IndexOf(carClass), take); // .. and decrement the remaninng cars of this class classRemainingCars[carClass.CarClassId] -= take; } } // AT THIS POINT : // on the Splits array, all Class{i}Target values are up to date with // number of cars we want // for each split, and each class. // Implement car lists foreach (var split in Splits) // foreach each split { for (int i = 0; i < 4; i++) // for each car class { int carsToAddInClass = split.GetClassTarget(i); // get the cars count we want if (carsListPerClass.Count > i) { var cars = carsListPerClass[i].PickCars(carsToAddInClass); // pick up the cars in the ordered list by iRating DESC if (cars.Count > 0) { split.SetClass(i, cars, carsListPerClass[i].CarClassId); // set the class car list } } } } // manage the rest badly, very raw method var lastSplit = new Split(); lastSplit.Number = Splits.Last().Number + 1; bool includeLastSplit = false; for (int i = 0; i < 4; i++) // for each car class { int carsToAddInClass = Splits.Last().GetClassTarget(i); // get the cars count we want if (carsListPerClass.Count > i) { var cars = carsListPerClass[i].PickCars(carsToAddInClass); // pick up the cars in the ordered list by iRating DESC if (cars.Count > 0) { lastSplit.SetClass(i, cars, carsListPerClass[i].CarClassId); // set the class car list } } if (lastSplit.TotalCarsCount > 0) { includeLastSplit = true; } } if (includeLastSplit) { Splits.Add(lastSplit); } // done // :-) }
public void Compute(List <Line> data, int fieldSize) { this.fieldSize = fieldSize; // Split cars per class var carsListPerClass = Tools.SplitCarsPerClass(data); // Create two dictionnary (KEY for both is the CarClass Id) // classRemainingCars : VALUE is the number of remaining cars in the class // classSplitsCount : VALUE is a list containing the number of car split per split Dictionary <int, int> classRemainingCars = new Dictionary <int, int>(); Dictionary <int, List <int> > classSplitsCount = new Dictionary <int, List <int> >(); foreach (var carClass in carsListPerClass) { classRemainingCars.Add(carClass.CarClassId, carClass.Cars.Count); classSplitsCount.Add(carClass.CarClassId, new List <int>()); } // export classes id CarClassesId = new List <int>(); foreach (var carClass in carsListPerClass) { CarClassesId.Add(carClass.CarClassId); } InitData(CarClassesId, data); // MultiClassMode records describes changes on split car classes compositions // - FromSplit and ToSplit describes the range of splits // - ClassesCount describes how many car classes can be part of the splits // (exemple: 3 first for LMP1/LMP2/GTE, then 2 when it become LMP1/GTE because not enought LMP2 are available, then 1 when single class)... List <MultiClassChanges> modes = new List <MultiClassChanges>(); MultiClassChanges currentMode = new MultiClassChanges(); currentMode.FromSplit = 1; currentMode.ToSplit = 1; currentMode.ClassesCount = classRemainingCars.Count; modes.Add(currentMode); int splitCounter = 1; while (SumValues(classRemainingCars) > 0) // when cars remaings { // count classes containing remaining cars int remCarClasses = (from r in classRemainingCars where r.Value > 0 select r).Count(); Dictionary <int, int> classRemainingCarsBeforeChange = new Dictionary <int, int>(); foreach (var item in classRemainingCars) { classRemainingCarsBeforeChange.Add(item.Key, item.Value); } foreach (var carClass in carsListPerClass) { // count cars to take in this class int takeCars = fieldSize; takeCars = TakeClassCars(fieldSize, remCarClasses, classRemainingCarsBeforeChange, carClass.CarClassId, carsListPerClass, splitCounter); // if not enought remianing cars than wanted, take what is possible int carClassSize = Math.Min(takeCars, classRemainingCars[carClass.CarClassId]); classSplitsCount[carClass.CarClassId].Add(carClassSize); // save the number of car in the class for this split classRemainingCars[carClass.CarClassId] -= carClassSize; // decrement reminaing cars in the class } for (int i = 0; i < carsListPerClass.Count; i++) // do a pass per class { // sum cars in this split int carsInThisSplit = 0; foreach (var carClass in carsListPerClass) { carsInThisSplit += classSplitsCount[carClass.CarClassId].Last(); } int availableSlots = fieldSize - carsInThisSplit; // --> var lastClass = carsListPerClass.LastOrDefault(); //get last class, which is the class with more cars then the other foreach (var item in classRemainingCars) // if there is a better class, containing less remaning cars than available slots ? { if (item.Value < availableSlots && item.Value > 0 && item.Key != lastClass.CarClassId) { int classid = item.Key; lastClass = (from r in carsListPerClass where r.CarClassId == classid select r).FirstOrDefault(); if (lastClass == null) { lastClass = carsListPerClass.LastOrDefault(); } else { availableSlots = Math.Min(availableSlots, item.Value); break; } } } if (lastClass != null) { // available slots ? if (carsInThisSplit < fieldSize) { // fill this availableSlots with last class cars to match the maximum field size.. var splitclasslist = classSplitsCount[lastClass.CarClassId]; splitclasslist[splitclasslist.Count - 1] += availableSlots; // and decremement remaining cars if this last class classRemainingCars[lastClass.CarClassId] -= availableSlots; } } } // iis there always the same number of car class than the previous split ? if (remCarClasses == currentMode.ClassesCount) { // yes, just update the ToSplit number currentMode.ToSplit = splitCounter; } else { // no, save a change starting from this split currentMode = new MultiClassChanges(); currentMode.FromSplit = splitCounter; currentMode.ToSplit = splitCounter; currentMode.ClassesCount = remCarClasses; modes.Add(currentMode); } splitCounter++; } // for each MultiClassMode, for each change of simultaneous car classes if you prefer // ... (when 3 classes, when 2 classes, when 1 class) foreach (var field in modes) { // create a dictionnary where KEY is the car class id // and value is the number of cars field.ClassCarsTarget = new Dictionary <int, int>(); foreach (var carClass in carsListPerClass) { // sum all cars of this class in this "MultiClassMode" int sum = 0; List <int> splits = classSplitsCount[carClass.CarClassId]; for (int i = field.FromSplit - 1; i < field.ToSplit; i++) { sum += splits[i]; } // count how many splits are in this "MultiClassMode" int splitsCount = field.ToSplit - field.FromSplit + 1; // calculate the average value, because we want all split having the same number of cars // it will be our Target int targetClassAverage = sum / splitsCount; // save in the table the Target the this MultiClassMode: // car class id -> target (avergate value) field.ClassCarsTarget.Add(carClass.CarClassId, targetClassAverage); } } // create the array of splits Splits = new List <Split>(); int maxsplit = (from r in modes select r.ToSplit).Max(); for (int i = 1; i <= maxsplit; i++) { var split = new Split(); split.Number = i; Splits.Add(split); } // reset the classRemainingCars counts classRemainingCars.Clear(); foreach (var carClass in carsListPerClass) { classRemainingCars.Add(carClass.CarClassId, carClass.Cars.Count); } // for each car class foreach (var carClass in carsListPerClass) { // for each split for (int i = 1; i <= maxsplit; i++) { var split = Splits[i - 1]; // get the split record in the array of splits // get the MultiClassMode where this split is in, the target cars count for the classes var mode = (from r in modes where i >= r.FromSplit orderby r.ToSplit descending select r).First(); int take = mode.ClassCarsTarget[carClass.CarClassId]; // save the class target cars count in this class split.SetClassTarget(carsListPerClass.IndexOf(carClass), take); // .. and decrement the remaninng cars of this class classRemainingCars[carClass.CarClassId] -= take; } } // OPTIMIZATIONS // the important points of this algorithm Optimize(fieldSize, classRemainingCars); // AT THIS POINT : // on the Splits array, all Class{i}Target values are updated with // number of cars we want // for each split, and each class. // Implement car lists foreach (var split in Splits) // foreach each split { for (int i = 0; i < 4; i++) // for each car class { int carsToAddInClass = split.GetClassTarget(i); // get the cars count we want if (carsListPerClass.Count > i) { var cars = carsListPerClass[i].PickCars(carsToAddInClass); // pick up the cars in the ordered list by iRating DESC if (cars.Count > 0) { split.SetClass(i, cars, carsListPerClass[i].CarClassId); // set the class car list } } } } // done // :-) }