/// <summary> /// Given a set of values and a function that returns true when given this set, will efficiently remove items from /// this set which are not essential for making the function return true. The relative order of items is /// preserved. This method cannot generally guarantee that the result is optimal, but for some types of functions /// the result will be guaranteed optimal.</summary> /// <typeparam name="T"> /// Type of the values in the set.</typeparam> /// <param name="items"> /// The set of items to reduce.</param> /// <param name="test"> /// The function that examines the set. Must always return the same value for the same set.</param> /// <param name="breadthFirst"> /// A value selecting a breadth-first or a depth-first approach. Depth-first is best at quickly locating a single /// value which will be present in the final required set. Breadth-first is best at quickly placing a lower bound /// on the total number of individual items in the required set.</param> /// <returns> /// A hopefully smaller set of values that still causes the function to return true.</returns> public static IEnumerable <T> ReduceRequiredSet <T>(IEnumerable <T> items, Func <ReduceRequiredSetState <T>, bool> test, bool breadthFirst = false) { var state = new ReduceRequiredSetStateInternal <T>(items); while (state.AnyPartitions) { var rangeToSplit = breadthFirst ? state.LargestRange : state.SmallestRange; int mid = (rangeToSplit.Item1 + rangeToSplit.Item2) / 2; var split1 = new Range(rangeToSplit.Item1, mid); var split2 = new Range(mid + 1, rangeToSplit.Item2); state.ApplyTemporarySplit(rangeToSplit, split1); if (test(state)) { state.RemoveRange(rangeToSplit); state.AddRange(split1); continue; } state.ApplyTemporarySplit(rangeToSplit, split2); if (test(state)) { state.RemoveRange(rangeToSplit); state.AddRange(split2); continue; } state.ResetTemporarySplit(); state.RemoveRange(rangeToSplit); state.AddRange(split1); state.AddRange(split2); } state.ResetTemporarySplit(); return(state.SetToTest); }
/// <summary> /// Given a set of values and a function that returns true when given this set, will efficiently remove items from /// this set which are not essential for making the function return true. The relative order of items is /// preserved. This method cannot generally guarantee that the result is optimal, but for some types of functions /// the result will be guaranteed optimal.</summary> /// <typeparam name="T"> /// Type of the values in the set.</typeparam> /// <param name="items"> /// The set of items to reduce.</param> /// <param name="test"> /// The function that examines the set. Must always return the same value for the same set.</param> /// <param name="breadthFirst"> /// A value selecting a breadth-first or a depth-first approach. Depth-first is best at quickly locating a single /// value which will be present in the final required set. Breadth-first is best at quickly placing a lower bound /// on the total number of individual items in the required set.</param> /// <param name="skipConsistencyTest"> /// When the function is particularly slow, you might want to set this to true to disable calls which are not /// required to reduce the set and are only there to ensure that the function behaves consistently.</param> /// <returns> /// A hopefully smaller set of values that still causes the function to return true.</returns> public static IEnumerable <T> ReduceRequiredSet <T>(IEnumerable <T> items, Func <ReduceRequiredSetState <T>, bool> test, bool breadthFirst = false, bool skipConsistencyTest = false) { var state = new ReduceRequiredSetStateInternal <T>(items); if (!skipConsistencyTest) { if (!test(state)) { throw new Exception("The function does not return true for the original set."); } } while (state.AnyPartitions) { if (!skipConsistencyTest) { if (!test(state)) { throw new Exception("The function is not consistently returning the same value for the same set, or there is an internal error in this algorithm."); } } var rangeToSplit = breadthFirst ? state.LargestRange : state.SmallestRange; int mid = (rangeToSplit.from + rangeToSplit.to) / 2; var split1 = (rangeToSplit.from, to : mid); var split2 = (from : mid + 1, rangeToSplit.to); state.ApplyTemporarySplit(rangeToSplit, split1); if (test(state)) { state.SolidifyTemporarySplit(); continue; } state.ApplyTemporarySplit(rangeToSplit, split2); if (test(state)) { state.SolidifyTemporarySplit(); continue; } state.ResetTemporarySplit(); state.RemoveRange(rangeToSplit); state.AddRange(split1); state.AddRange(split2); } state.ResetTemporarySplit(); return(state.SetToTest); }