using EgwCoreLib.Lux.Core.RestPayload; namespace EgwCoreLib.Lux.Core.MachineCalc { public class Utils { #region Public Methods /// /// Calcolo intersezione delle sole combinazioni calcolabili /// /// /// public static List CalculateIntersections(List machineResult) { var results = new List(); if (machineResult == null || !machineResult.Any()) return results; int numParts = machineResult.FirstOrDefault()?.NumParts ?? 0; // 1. Mappiamo ogni Tag alla lista di macchine che possono lavorarlo // Key: Tag (string), Value: Insieme di nomi macchine (HashSet per comparazione veloce) var tagToMachines = new Dictionary>(); foreach (var machine in machineResult) { var machinableTags = machine.PartList .Where(p => p.CalcResult == Enums.PartVerificationResult.MACHINABLE); foreach (var part in machinableTags) { if (!tagToMachines.ContainsKey(part.Tag)) tagToMachines[part.Tag] = new HashSet(); tagToMachines[part.Tag].Add(machine.Name); } } // 2. Raggruppiamo i Tag che condividono lo STESSO identico set di macchine // Usiamo una stringa joinata come chiave per il raggruppamento (es: "MachineA|MachineB") var groups = tagToMachines.GroupBy( kvp => string.Join("|", kvp.Value.OrderBy(m => m)), // Chiave univoca per la combinazione di macchine kvp => kvp.Key // Il Tag ); // 3. Creiamo i MachineTagDTO per ogni gruppo trovato foreach (var group in groups) { var tagsInGroup = group.ToList(); var machineList = group.Key.Split('|').ToList(); // Calcoliamo i tempi (Min/Max) per questo specifico set di macchine var timesPerMachine = machineList.Select(mName => { var machineData = machineResult.First(m => m.Name == mName); return machineData.PartList .Where(p => tagsInGroup.Contains(p.Tag)) .Sum(p => p.Time); }).ToList(); results.Add(new MachineTagDTO { TotParts = numParts, Machines = machineList, Tags = tagsInGroup, MinTime = timesPerMachine.Any() ? timesPerMachine.Min() : 0, MaxTime = timesPerMachine.Any() ? timesPerMachine.Max() : 0 }); } return results; } /// /// Calcolo intersezioni anche dell'insieme nullo (= UNWORKABLE) /// /// /// public static List CalculateIntersectionsWithEmpty(List machineResult) { var results = new List(); if (machineResult == null || !machineResult.Any()) return results; int numParts = machineResult.First().NumParts; // 1. Prendiamo l'elenco completo di TUTTI i tag esistenti var allTags = machineResult.SelectMany(m => m.PartList).Select(p => p.Tag).Distinct(); var tagToMachines = allTags.ToDictionary(tag => tag, _ => new HashSet()); // 2. Popoliamo le macchine solo per i pezzi MACHINABLE foreach (var machine in machineResult) { var machinableTags = machine.PartList .Where(p => p.CalcResult == Enums.PartVerificationResult.MACHINABLE); foreach (var part in machinableTags) { tagToMachines[part.Tag].Add(machine.Name); } } // 3. Raggruppiamo var groups = tagToMachines.GroupBy( kvp => string.Join("|", kvp.Value.OrderBy(m => m)), kvp => kvp.Key ); foreach (var group in groups) { var tagsInGroup = group.ToList(); // Se la chiave è vuota, significa che il set di macchine è vuoto var machineList = string.IsNullOrEmpty(group.Key) ? new List() : group.Key.Split('|').ToList(); var timesPerMachine = machineList.Select(mName => { return machineResult.First(m => m.Name == mName).PartList .Where(p => tagsInGroup.Contains(p.Tag)) .Sum(p => p.Time); }).ToList(); results.Add(new MachineTagDTO { TotParts = numParts, Machines = machineList, Tags = tagsInGroup, MinTime = timesPerMachine.Any() ? timesPerMachine.Min() : 0, MaxTime = timesPerMachine.Any() ? timesPerMachine.Max() : 0 }); } return results; } #if false /// /// Calcolo delle intersezioni Macchine/Tags(Parts) /// /// /// public static List CalculateIntersections(List machineResult) { var results = new List(); if (machineResult != null && machineResult.Count > 0) { int numParts = machineResult.FirstOrDefault()?.NumParts ?? 0; // Step 1: extract workable tags per machine var machineTags = machineResult .ToDictionary( m => m.Name, m => m.PartList .Where(p => p.CalcResult == Enums.PartVerificationResult.MACHINABLE) .ToList() ); var machineNames = machineTags.Keys.ToList(); // Step 2: generate all combinations of machines for (int size = 1; size <= machineNames.Count; size++) { foreach (var combo in GetCombinations(machineNames, size)) { var comboList = combo.ToList(); // Intersection of tags across all machines in this combo var intersectionTags = comboList .Select(name => machineTags[name].Select(p => p.Tag).ToHashSet()) .Aggregate((set1, set2) => { set1.IntersectWith(set2); return set1; }); // Compute per-machine sums of Time for these tags var sums = comboList.Select(name => machineTags[name] .Where(p => intersectionTags.Contains(p.Tag)) .Sum(p => p.Time) ).ToList(); results.Add(new MachineTagDTO { TotParts = numParts, Machines = comboList, Tags = intersectionTags.ToList(), MinTime = sums.Any() ? sums.Min() : 0, MaxTime = sums.Any() ? sums.Max() : 0 }); } } // Step 3: add unique (non-intersecting) tags per machine foreach (var name in machineNames) { var uniqueTags = machineTags[name] .Select(p => p.Tag) .Except(machineNames.Where(n => n != name) .SelectMany(n => machineTags[n].Select(p => p.Tag))) .ToList(); if (uniqueTags.Count > 0) { var sum = machineTags[name] .Where(p => uniqueTags.Contains(p.Tag)) .Sum(p => p.Time); results.Add(new MachineTagDTO { TotParts = numParts, Machines = new List { name }, Tags = uniqueTags, MinTime = sum, MaxTime = sum }); } } } return results; } #endif #if false /// /// Helper generazione combinazioni di items per una data length (Distinct Combinations) /// /// /// /// /// public static IEnumerable> GetCombinations(IEnumerable items, int length) { // Convert to a List to easily access elements by index. List itemList = items.ToList(); int n = itemList.Count; // Base case: length 0 returns a collection containing an empty collection if (length == 0) { yield return Enumerable.Empty(); yield break; } // Check if enough items are available if (length > n) { yield break; } // Call the internal recursive helper function foreach (var combination in GetCombinationsInternal(itemList, length, 0, n)) { yield return combination; } } /// /// Helper generazione permutazioni di items per una data length /// /// /// /// /// public static IEnumerable> GetPermutations(IEnumerable items, int length) { if (length == 1) return items.Select(i => new T[] { i }); return GetCombinations(items, length - 1) .SelectMany(c => items.Where(i => !c.Contains(i)), (c, i) => c.Concat(new T[] { i })); } #endif /// /// Helper calcolo Intersezione lista macchine /// /// /// /// public static IEnumerable IntersectTags(List machines, Func predicate) { return machines .Select(m => m.PartList.Where(predicate).Select(p => p.Tag).ToHashSet()) .Aggregate((set1, set2) => { set1.IntersectWith(set2); return set1; }); } #endregion Public Methods #region Private Methods #if false private static IEnumerable> GetCombinationsInternal( List items, int length, int startIndex, int n) { // Base case: combination of length 1 if (length == 1) { // Select one item from startIndex up to the end for (int i = startIndex; i < n; i++) { yield return new T[] { items[i] }; } yield break; } // Recursive step // Iterate over elements starting from startIndex for (int i = startIndex; i <= n - length; i++) { T currentItem = items[i]; // Recursively find combinations of length - 1 starting from the next index (i + 1) // This is the crucial step that prevents (a,b) and (b,a) foreach (var subCombination in GetCombinationsInternal(items, length - 1, i + 1, n)) { // Prepend the current item to the shorter combinations yield return new T[] { currentItem }.Concat(subCombination); } } } #endif #endregion Private Methods } }