diff --git a/Tests/TryAtSoftware.CleanTests.Benchmark/CombinatorialMachineBenchmark.cs b/Tests/TryAtSoftware.CleanTests.Benchmark/CombinatorialMachineBenchmark.cs index 0011627..9301563 100644 --- a/Tests/TryAtSoftware.CleanTests.Benchmark/CombinatorialMachineBenchmark.cs +++ b/Tests/TryAtSoftware.CleanTests.Benchmark/CombinatorialMachineBenchmark.cs @@ -7,14 +7,15 @@ /* 28/03/2023: -| Method | Setup | Mean | Error | StdDev | Median | -|------------------------ |--------- |--------------:|-------------:|-------------:|--------------:| -| GenerateAllCombinations | Setup #1 | 29.30 us | 0.495 us | 0.989 us | 28.87 us | -| GenerateAllCombinations | Setup #2 | 12.66 us | 0.115 us | 0.108 us | 12.67 us | -| GenerateAllCombinations | Setup #3 | 217,844.91 us | 4,274.928 us | 8,337.918 us | 218,002.67 us | -| GenerateAllCombinations | Setup #4 | 195,762.60 us | 3,861.216 us | 6,011.449 us | 195,647.20 us | -| GenerateAllCombinations | Setup #5 | 325,507.60 us | 6,469.168 us | 7,449.904 us | 325,091.15 us | -| GenerateAllCombinations | Setup #6 | 1,303.72 us | 16.524 us | 13.798 us | 1,304.00 us | +| Method | Setup | Mean | Error | StdDev | +|------------------------ |--------- |--------------:|-------------:|-------------:| +| GenerateAllCombinations | Setup #1 | 30.10 us | 0.210 us | 0.196 us | +| GenerateAllCombinations | Setup #2 | 16.93 us | 0.138 us | 0.129 us | +| GenerateAllCombinations | Setup #3 | 172,957.46 us | 2,525.541 us | 2,238.826 us | +| GenerateAllCombinations | Setup #4 | 169,320.36 us | 2,768.573 us | 2,589.725 us | +| GenerateAllCombinations | Setup #5 | 310,245.02 us | 5,375.505 us | 5,028.251 us | +| GenerateAllCombinations | Setup #6 | 7,992.88 us | 70.567 us | 66.009 us | +| GenerateAllCombinations | Setup #7 | 113,752.86 us | 2,183.336 us | 2,336.145 us | */ public class CombinatorialMachineBenchmark { diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs index 1046e52..5df9268 100644 --- a/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs +++ b/Tests/TryAtSoftware.CleanTests.UnitTests/Parametrization/TestParameters.cs @@ -60,7 +60,11 @@ public static IEnumerable ConstructObservableCombinat yield return setup6; - // TODO: Add a setup for which none of the last level utilities satisfy the first level's demands. + // 1000 categories; 100 utilities in each; all utilities in the first category are incompatible with all utilities in the last category. + var setup7 = new CombinatorialMachineSetup("Setup #7"); + for (var i = 0; i < 1000; i++) setup7.WithCategory(ConstructCategoryName(i), 100); + for (var i = 1; i <= 100; i++) setup7.WithDemands(ConstructCategoryName(0), i, ConstructCategoryName(999), "q"); + yield return setup7; } private static string ConstructCategoryName(int letterIndex) diff --git a/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs b/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs index 5045b55..12e5077 100644 --- a/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs +++ b/TryAtSoftware.CleanTests.Core/CleanTestInitializationCollection.cs @@ -20,6 +20,9 @@ public IEnumerable Get(string category) return registeredUtilities.OrEmptyIfNull(); } + /// + public bool ContainsCategory(string category) => this._data.ContainsKey(category); + /// public void Register(string category, TValue value) { diff --git a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs index e9fd03d..1c182b5 100644 --- a/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs +++ b/TryAtSoftware.CleanTests.Core/Interfaces/ICleanTestInitializationCollection.cs @@ -6,6 +6,7 @@ public interface ICleanTestInitializationCollection : IEnumerable Categories { get; } IEnumerable Get(string category); + bool ContainsCategory(string category); void Register(string category, TValue value); IEnumerable GetAllValues(); } \ No newline at end of file diff --git a/TryAtSoftware.CleanTests.Core/Utilities/CombinatorialMachine.cs b/TryAtSoftware.CleanTests.Core/Utilities/CombinatorialMachine.cs index 39f25c2..e43771b 100644 --- a/TryAtSoftware.CleanTests.Core/Utilities/CombinatorialMachine.cs +++ b/TryAtSoftware.CleanTests.Core/Utilities/CombinatorialMachine.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using TryAtSoftware.CleanTests.Core.Extensions; using TryAtSoftware.CleanTests.Core.Interfaces; using TryAtSoftware.Extensions.Collections; @@ -18,9 +17,12 @@ public CombinatorialMachine(ICleanTestInitializationCollection> GenerateAllCombinations() { - var categories = this._utilities.Categories.ToArray(); + var (incompatibleUtilitiesMap, incompatibilityEnforcersByCategory, incompatibilityFactorByCategory) = this.DiscoverIncompatibleUtilities(); + var categories = this._utilities.Categories.OrderByDescending(x => incompatibilityEnforcersByCategory[x]).ThenByDescending(x => incompatibilityFactorByCategory[x]).ToArray(); - Dictionary> activeDemands = new (); + Dictionary incompatibilityFactorByUtilityId = new (); + foreach (var utility in this._utilities.GetAllValues()) incompatibilityFactorByUtilityId[utility.Id] = 0; + Dictionary slots = new (); List> resultBag = new (); @@ -37,60 +39,69 @@ void Dfs(int categoryIndex) var currentCategory = categories[categoryIndex]; - var requiredCharacteristics = activeDemands.EnsureValue(currentCategory).Keys.ToArray(); foreach (var utility in this._utilities.Get(currentCategory)) { - var canBeUsed = utility.FulfillsAllDemands(requiredCharacteristics); - if (!canBeUsed || !RegisterExternalDemands(utility, slots, activeDemands)) continue; + if (incompatibilityFactorByUtilityId[utility.Id] > 0) continue; + foreach (var iu in incompatibleUtilitiesMap[utility.Id]) incompatibilityFactorByUtilityId[iu]++; + slots[currentCategory] = utility; Dfs(categoryIndex + 1); slots.Remove(currentCategory); - CleanupExternalDemands(utility, activeDemands); + foreach (var iu in incompatibleUtilitiesMap[utility.Id]) incompatibilityFactorByUtilityId[iu]--; } } } - private static bool RegisterExternalDemands(ICleanUtilityDescriptor cleanUtilityDescriptor, IDictionary slots, IDictionary> activeDemands) + private (Dictionary> IncompatibleUtilitiesMap, Dictionary IncompatibilityEnforcersByCategory, Dictionary incompatibilityFactorByCategory) DiscoverIncompatibleUtilities() { - HashSet checkedCategories = new (); - foreach (var (category, demands) in cleanUtilityDescriptor.ExternalDemands) + Dictionary incompatibilityEnforcersByCategory = new (), incompatibilityFactorByCategory = new (); + Dictionary>> characteristicsRegister = new (); + foreach (var category in this._utilities.Categories) { - if (!slots.TryGetValue(category, out var otherUtilityToCheck)) continue; - - if (!otherUtilityToCheck.FulfillsAllDemands(demands)) return false; - checkedCategories.Add(category); + incompatibilityEnforcersByCategory[category] = 0; + incompatibilityFactorByCategory[category] = 0; + characteristicsRegister[category] = new Dictionary>(); } - foreach (var (category, demands) in cleanUtilityDescriptor.ExternalDemands) + Dictionary> incompatibleUtilitiesMap = new (); + foreach (var utility in this._utilities.GetAllValues()) { - if (checkedCategories.Contains(category)) continue; - - var activeDemandsForCategory = activeDemands.EnsureValue(category); - foreach (var demand in demands) + incompatibleUtilitiesMap[utility.Id] = new HashSet(); + + foreach (var characteristic in utility.Characteristics) { - if (!activeDemandsForCategory.ContainsKey(demand)) activeDemandsForCategory[demand] = 0; - activeDemandsForCategory[demand]++; + if (!characteristicsRegister[utility.Category].ContainsKey(characteristic)) characteristicsRegister[utility.Category][characteristic] = new HashSet(); + characteristicsRegister[utility.Category][characteristic].Add(utility.Id); } } - return true; - } - - private static void CleanupExternalDemands(ICleanUtilityDescriptor cleanUtilityDescriptor, IDictionary> activeDemands) - { - foreach (var (category, demands) in cleanUtilityDescriptor.ExternalDemands) + foreach (var utility in this._utilities.GetAllValues()) { - if (!activeDemands.TryGetValue(category, out var activeDemandsForCategory)) continue; - - foreach (var demand in demands) + foreach (var (demandCategory, demandsForCategory) in utility.ExternalDemands) { - if (!activeDemandsForCategory.TryGetValue(demand, out var demandOccurrences)) continue; - - if (demandOccurrences == 1) activeDemandsForCategory.Remove(demand); - else activeDemandsForCategory[demand] = demandOccurrences - 1; + if (!this._utilities.ContainsCategory(demandCategory)) continue; + + foreach (var demand in demandsForCategory) + { + Func? predicate = null; + if (characteristicsRegister[demandCategory].ContainsKey(demand)) predicate = x => !characteristicsRegister[demandCategory][demand].Contains(x.Id); + + foreach (var incompatibleUtilityId in this._utilities.Get(demandCategory).SafeWhere(predicate).Select(x => x.Id)) + { + if (incompatibleUtilitiesMap[utility.Id].Add(incompatibleUtilityId)) + { + incompatibilityEnforcersByCategory[utility.Category]++; + incompatibilityFactorByCategory[utility.Category]++; + } + + if (incompatibleUtilitiesMap[incompatibleUtilityId].Add(utility.Id)) incompatibilityFactorByCategory[demandCategory]++; + } + } } } + + return (incompatibleUtilitiesMap, incompatibilityEnforcersByCategory, incompatibilityFactorByCategory); } } \ No newline at end of file