diff --git a/MoreLinq.Test/PermutationsTest.cs b/MoreLinq.Test/PermutationsTest.cs index 6941bc778..6a99681a2 100644 --- a/MoreLinq.Test/PermutationsTest.cs +++ b/MoreLinq.Test/PermutationsTest.cs @@ -184,7 +184,14 @@ public void TestPermutationsAreIndependent() var permutedSets = set.Permutations(); var listPermutations = new List>(); - listPermutations.AddRange(permutedSets); + foreach (var ps in permutedSets) + { + Assert.That(ps, Is.Not.All.Negative); + listPermutations.Add(ps); + for (var i = 0; i < ps.Count; i++) + ps[i] = -1; + } + Assert.That(listPermutations, Is.Not.Empty); for (var i = 0; i < listPermutations.Count; i++) diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 83b2e90ac..81e42062e 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -4644,7 +4644,6 @@ public static TResult Partition(this IEnumerable /// Generates a sequence of lists that represent the permutations of the original sequence. /// diff --git a/MoreLinq/NestedLoops.cs b/MoreLinq/NestedLoops.cs deleted file mode 100644 index 587b368e0..000000000 --- a/MoreLinq/NestedLoops.cs +++ /dev/null @@ -1,52 +0,0 @@ -#region License and Terms -// MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2010 Leopold Bushkin. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#endregion - -namespace MoreLinq -{ - using System; - using System.Collections.Generic; - using System.Linq; - - public static partial class MoreEnumerable - { - // This extension method was developed (primarily) to support the - // implementation of the Permutations() extension methods. - - /// - /// Produces a sequence from an action based on the dynamic generation of N nested loops - /// whose iteration counts are defined by a sequence of loop counts. - /// - /// Action delegate for which to produce a nested loop sequence - /// A sequence of loop repetition counts - /// A sequence of Action representing the expansion of a set of nested loops - - static IEnumerable NestedLoops(this Action action, IEnumerable loopCounts) - { - if (action == null) throw new ArgumentNullException(nameof(action)); - if (loopCounts == null) throw new ArgumentNullException(nameof(loopCounts)); - - return _(); IEnumerable _() - { - var count = loopCounts.DefaultIfEmpty() - .Aggregate((acc, x) => checked(acc * x)); - - for (var i = 0UL; i < count; i++) - yield return action; - } - } - } -} diff --git a/MoreLinq/Permutations.cs b/MoreLinq/Permutations.cs index 3bd52ec81..2324750bb 100644 --- a/MoreLinq/Permutations.cs +++ b/MoreLinq/Permutations.cs @@ -18,165 +18,11 @@ namespace MoreLinq { using System; - using System.Collections; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Linq; public static partial class MoreEnumerable { - /// - /// The private implementation class that produces permutations of a sequence. - /// - - sealed class PermutationEnumerator : IEnumerator> - { - // NOTE: The algorithm used to generate permutations uses the fact that any set - // can be put into 1-to-1 correspondence with the set of ordinals number (0..n). - // The implementation here is based on the algorithm described by Kenneth H. Rosen, - // in Discrete Mathematics and Its Applications, 2nd edition, pp. 282-284. - // - // There are two significant changes from the original implementation. - // First, the algorithm uses lazy evaluation and streaming to fit well into the - // nature of most LINQ evaluations. - // - // Second, the algorithm has been modified to use dynamically generated nested loop - // state machines, rather than an integral computation of the factorial function - // to determine when to terminate. The original algorithm required a priori knowledge - // of the number of iterations necessary to produce all permutations. This is a - // necessary step to avoid overflowing the range of the permutation arrays used. - // The number of permutation iterations is defined as the factorial of the original - // set size minus 1. - // - // However, there's a fly in the ointment. The factorial function grows VERY rapidly. - // 13! overflows the range of a Int32; while 28! overflows the range of decimal. - // To overcome these limitations, the algorithm relies on the fact that the factorial - // of N is equivalent to the evaluation of N-1 nested loops. Unfortunately, you can't - // just code up a variable number of nested loops ... this is where .NET generators - // with their elegant 'yield return' syntax come to the rescue. - // - // The methods of the Loop extension class (For and NestedLoops) provide the implementation - // of dynamic nested loops using generators and sequence composition. In a nutshell, - // the two Repeat() functions are the constructor of loops and nested loops, respectively. - // The NestedLoops() function produces a composition of loops where the loop counter - // for each nesting level is defined in a separate sequence passed in the call. - // - // For example: NestedLoops( () => DoSomething(), new[] { 6, 8 } ) - // - // is equivalent to: for( int i = 0; i < 6; i++ ) - // for( int j = 0; j < 8; j++ ) - // DoSomething(); - - readonly IList valueSet; - readonly int[] permutation; - readonly IEnumerable generator; - - IEnumerator generatorIterator; - bool hasMoreResults; - - IList? current; - - public PermutationEnumerator(IEnumerable valueSet) - { - this.valueSet = valueSet.ToArray(); - this.permutation = new int[this.valueSet.Count]; - // The nested loop construction below takes into account the fact that: - // 1) for empty sets and sets of cardinality 1, there exists only a single permutation. - // 2) for sets larger than 1 element, the number of nested loops needed is: set.Count-1 - this.generator = NestedLoops(NextPermutation, Generate(2UL, n => n + 1).Take(Math.Max(0, this.valueSet.Count - 1))); - Reset(); - } - - [MemberNotNull(nameof(generatorIterator))] - public void Reset() - { - this.current = null; - this.generatorIterator?.Dispose(); - // restore lexographic ordering of the permutation indexes - for (var i = 0; i < this.permutation.Length; i++) - this.permutation[i] = i; - // start a new iteration over the nested loop generator - this.generatorIterator = this.generator.GetEnumerator(); - // we must advance the nested loop iterator to the initial element, - // this ensures that we only ever produce N!-1 calls to NextPermutation() - _ = this.generatorIterator.MoveNext(); - this.hasMoreResults = true; // there's always at least one permutation: the original set itself - } - - public IList Current - { - get - { - Debug.Assert(this.current is not null); - return this.current; - } - } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - this.current = PermuteValueSet(); - // check if more permutation left to enumerate - var prevResult = this.hasMoreResults; - this.hasMoreResults = this.generatorIterator.MoveNext(); - if (this.hasMoreResults) - this.generatorIterator.Current(); // produce the next permutation ordering - // we return prevResult rather than m_HasMoreResults because there is always - // at least one permutation: the original set. Also, this provides a simple way - // to deal with the disparity between sets that have only one loop level (size 0-2) - // and those that have two or more (size > 2). - return prevResult; - } - - void IDisposable.Dispose() => this.generatorIterator.Dispose(); - - /// - /// Transposes elements in the cached permutation array to produce the next permutation - /// - void NextPermutation() - { - // find the largest index j with m_Permutation[j] < m_Permutation[j+1] - var j = this.permutation.Length - 2; - while (this.permutation[j] > this.permutation[j + 1]) - j--; - - // find index k such that m_Permutation[k] is the smallest integer - // greater than m_Permutation[j] to the right of m_Permutation[j] - var k = this.permutation.Length - 1; - while (this.permutation[j] > this.permutation[k]) - k--; - - (this.permutation[j], this.permutation[k]) = (this.permutation[k], this.permutation[j]); - - // move the tail of the permutation after the jth position in increasing order - for (int x = this.permutation.Length - 1, y = j + 1; x > y; x--, y++) - (this.permutation[x], this.permutation[y]) = (this.permutation[y], this.permutation[x]); - } - - /// - /// Creates a new list containing the values from the original - /// set in their new permuted order. - /// - /// - /// The reason we return a new permuted value set, rather than reuse - /// an existing collection, is that we have no control over what the - /// consumer will do with the results produced. They could very easily - /// generate and store a set of permutations and only then begin to - /// process them. If we reused the same collection, the caller would - /// be surprised to discover that all of the permutations looked the - /// same. - /// - /// Array of permuted source sequence values - T[] PermuteValueSet() - { - var permutedSet = new T[this.permutation.Length]; - for (var i = 0; i < this.permutation.Length; i++) - permutedSet[i] = this.valueSet[this.permutation[i]]; - return permutedSet; - } - } - /// /// Generates a sequence of lists that represent the permutations of the original sequence. /// @@ -205,10 +51,93 @@ public static IEnumerable> Permutations(this IEnumerable sequence return _(); IEnumerable> _() { - using var iter = new PermutationEnumerator(sequence); + // The algorithm used to generate permutations uses the fact that any set can be put + // into 1-to-1 correspondence with the set of ordinals number (0..n). The + // implementation here is based on the algorithm described by Kenneth H. Rosen, in + // Discrete Mathematics and Its Applications, 2nd edition, pp. 282-284. + + var valueSet = sequence.ToArray(); + + // There's always at least one permutation: a copy of original set. + + yield return (IList)valueSet.Clone(); + + // For empty sets and sets of cardinality 1, there exists only a single permutation. + + if (valueSet.Length is 0 or 1) + yield break; + + var permutation = new int[valueSet.Length]; + + // Initialize lexographic ordering of the permutation indexes. + + for (var i = 0; i < permutation.Length; i++) + permutation[i] = i; + + // For sets larger than 1 element, the number of nested loops needed is one less + // than the set length. Note that the factorial grows VERY rapidly such that 13! + // overflows the range of an Int32 and 28! overflows the range of a Decimal. + + ulong factorial = valueSet.Length switch + { + 0 => 1, + 1 => 1, + 2 => 2, + 3 => 6, + 4 => 24, + 5 => 120, + 6 => 720, + 7 => 5_040, + 8 => 40_320, + 9 => 362_880, + 10 => 3_628_800, + 11 => 39_916_800, + 12 => 479_001_600, + 13 => 6_227_020_800, + 14 => 87_178_291_200, + 15 => 1_307_674_368_000, + 16 => 20_922_789_888_000, + 17 => 355_687_428_096_000, + 18 => 6_402_373_705_728_000, + 19 => 121_645_100_408_832_000, + 20 => 2_432_902_008_176_640_000, + _ => throw new OverflowException("Too many permutations."), + }; + + for (var n = 1UL; n < factorial; n++) + { + // Transposes elements in the cached permutation array to produce the next + // permutation. + + // Find the largest index j with permutation[j] < permutation[j+1]: + + var j = permutation.Length - 2; + while (permutation[j] > permutation[j + 1]) + j--; + + // Find index k such that permutation[k] is the smallest integer greater than + // permutation[j] to the right of permutation[j]: + + var k = permutation.Length - 1; + while (permutation[j] > permutation[k]) + k--; - while (iter.MoveNext()) - yield return iter.Current; + (permutation[j], permutation[k]) = (permutation[k], permutation[j]); + + // Move the tail of the permutation after the j-th position in increasing order. + + for (int x = permutation.Length - 1, y = j + 1; x > y; x--, y++) + (permutation[x], permutation[y]) = (permutation[y], permutation[x]); + + // Yield a new array containing the values from the original set in their new + // permuted order. + + var permutedSet = new T[permutation.Length]; + for (var i = 0; i < permutation.Length; i++) + permutedSet[i] = valueSet[permutation[i]]; + + yield return permutedSet; + } } } }