|
18 | 18 | namespace MoreLinq
|
19 | 19 | {
|
20 | 20 | using System;
|
21 |
| - using System.Collections; |
22 | 21 | using System.Collections.Generic;
|
23 |
| - using System.Diagnostics.CodeAnalysis; |
24 | 22 | using System.Linq;
|
25 | 23 |
|
26 | 24 | public static partial class MoreEnumerable
|
27 | 25 | {
|
28 |
| - /// <summary> |
29 |
| - /// The private implementation class that produces permutations of a sequence. |
30 |
| - /// </summary> |
31 |
| - |
32 |
| - sealed class PermutationEnumerator<T> : IEnumerator<IList<T>> |
33 |
| - { |
34 |
| - // NOTE: The algorithm used to generate permutations uses the fact that any set |
35 |
| - // can be put into 1-to-1 correspondence with the set of ordinals number (0..n). |
36 |
| - // The implementation here is based on the algorithm described by Kenneth H. Rosen, |
37 |
| - // in Discrete Mathematics and Its Applications, 2nd edition, pp. 282-284. |
38 |
| - // |
39 |
| - // There are two significant changes from the original implementation. |
40 |
| - // First, the algorithm uses lazy evaluation and streaming to fit well into the |
41 |
| - // nature of most LINQ evaluations. |
42 |
| - // |
43 |
| - // Second, the algorithm has been modified to use dynamically generated nested loop |
44 |
| - // state machines, rather than an integral computation of the factorial function |
45 |
| - // to determine when to terminate. The original algorithm required a priori knowledge |
46 |
| - // of the number of iterations necessary to produce all permutations. This is a |
47 |
| - // necessary step to avoid overflowing the range of the permutation arrays used. |
48 |
| - // The number of permutation iterations is defined as the factorial of the original |
49 |
| - // set size minus 1. |
50 |
| - // |
51 |
| - // However, there's a fly in the ointment. The factorial function grows VERY rapidly. |
52 |
| - // 13! overflows the range of a Int32; while 28! overflows the range of decimal. |
53 |
| - // To overcome these limitations, the algorithm relies on the fact that the factorial |
54 |
| - // of N is equivalent to the evaluation of N-1 nested loops. Unfortunately, you can't |
55 |
| - // just code up a variable number of nested loops ... this is where .NET generators |
56 |
| - // with their elegant 'yield return' syntax come to the rescue. |
57 |
| - // |
58 |
| - // The methods of the Loop extension class (For and NestedLoops) provide the implementation |
59 |
| - // of dynamic nested loops using generators and sequence composition. In a nutshell, |
60 |
| - // the two Repeat() functions are the constructor of loops and nested loops, respectively. |
61 |
| - // The NestedLoops() function produces a composition of loops where the loop counter |
62 |
| - // for each nesting level is defined in a separate sequence passed in the call. |
63 |
| - // |
64 |
| - // For example: NestedLoops( () => DoSomething(), new[] { 6, 8 } ) |
65 |
| - // |
66 |
| - // is equivalent to: for( int i = 0; i < 6; i++ ) |
67 |
| - // for( int j = 0; j < 8; j++ ) |
68 |
| - // DoSomething(); |
69 |
| - |
70 |
| - readonly IList<T> valueSet; |
71 |
| - readonly int[] permutation; |
72 |
| - readonly IEnumerable<Action> generator; |
73 |
| - |
74 |
| - IEnumerator<Action> generatorIterator; |
75 |
| - bool hasMoreResults; |
76 |
| - |
77 |
| - IList<T>? current; |
78 |
| - |
79 |
| - public PermutationEnumerator(IEnumerable<T> valueSet) |
80 |
| - { |
81 |
| - this.valueSet = valueSet.ToArray(); |
82 |
| - this.permutation = new int[this.valueSet.Count]; |
83 |
| - // The nested loop construction below takes into account the fact that: |
84 |
| - // 1) for empty sets and sets of cardinality 1, there exists only a single permutation. |
85 |
| - // 2) for sets larger than 1 element, the number of nested loops needed is: set.Count-1 |
86 |
| - this.generator = NestedLoops(NextPermutation, Generate(2UL, n => n + 1).Take(Math.Max(0, this.valueSet.Count - 1))); |
87 |
| - Reset(); |
88 |
| - } |
89 |
| - |
90 |
| - [MemberNotNull(nameof(generatorIterator))] |
91 |
| - public void Reset() |
92 |
| - { |
93 |
| - this.current = null; |
94 |
| - this.generatorIterator?.Dispose(); |
95 |
| - // restore lexographic ordering of the permutation indexes |
96 |
| - for (var i = 0; i < this.permutation.Length; i++) |
97 |
| - this.permutation[i] = i; |
98 |
| - // start a new iteration over the nested loop generator |
99 |
| - this.generatorIterator = this.generator.GetEnumerator(); |
100 |
| - // we must advance the nested loop iterator to the initial element, |
101 |
| - // this ensures that we only ever produce N!-1 calls to NextPermutation() |
102 |
| - _ = this.generatorIterator.MoveNext(); |
103 |
| - this.hasMoreResults = true; // there's always at least one permutation: the original set itself |
104 |
| - } |
105 |
| - |
106 |
| - public IList<T> Current |
107 |
| - { |
108 |
| - get |
109 |
| - { |
110 |
| - Debug.Assert(this.current is not null); |
111 |
| - return this.current; |
112 |
| - } |
113 |
| - } |
114 |
| - |
115 |
| - object IEnumerator.Current => Current; |
116 |
| - |
117 |
| - public bool MoveNext() |
118 |
| - { |
119 |
| - this.current = PermuteValueSet(); |
120 |
| - // check if more permutation left to enumerate |
121 |
| - var prevResult = this.hasMoreResults; |
122 |
| - this.hasMoreResults = this.generatorIterator.MoveNext(); |
123 |
| - if (this.hasMoreResults) |
124 |
| - this.generatorIterator.Current(); // produce the next permutation ordering |
125 |
| - // we return prevResult rather than m_HasMoreResults because there is always |
126 |
| - // at least one permutation: the original set. Also, this provides a simple way |
127 |
| - // to deal with the disparity between sets that have only one loop level (size 0-2) |
128 |
| - // and those that have two or more (size > 2). |
129 |
| - return prevResult; |
130 |
| - } |
131 |
| - |
132 |
| - void IDisposable.Dispose() => this.generatorIterator.Dispose(); |
133 |
| - |
134 |
| - /// <summary> |
135 |
| - /// Transposes elements in the cached permutation array to produce the next permutation |
136 |
| - /// </summary> |
137 |
| - void NextPermutation() |
138 |
| - { |
139 |
| - // find the largest index j with m_Permutation[j] < m_Permutation[j+1] |
140 |
| - var j = this.permutation.Length - 2; |
141 |
| - while (this.permutation[j] > this.permutation[j + 1]) |
142 |
| - j--; |
143 |
| - |
144 |
| - // find index k such that m_Permutation[k] is the smallest integer |
145 |
| - // greater than m_Permutation[j] to the right of m_Permutation[j] |
146 |
| - var k = this.permutation.Length - 1; |
147 |
| - while (this.permutation[j] > this.permutation[k]) |
148 |
| - k--; |
149 |
| - |
150 |
| - (this.permutation[j], this.permutation[k]) = (this.permutation[k], this.permutation[j]); |
151 |
| - |
152 |
| - // move the tail of the permutation after the jth position in increasing order |
153 |
| - for (int x = this.permutation.Length - 1, y = j + 1; x > y; x--, y++) |
154 |
| - (this.permutation[x], this.permutation[y]) = (this.permutation[y], this.permutation[x]); |
155 |
| - } |
156 |
| - |
157 |
| - /// <summary> |
158 |
| - /// Creates a new list containing the values from the original |
159 |
| - /// set in their new permuted order. |
160 |
| - /// </summary> |
161 |
| - /// <remarks> |
162 |
| - /// The reason we return a new permuted value set, rather than reuse |
163 |
| - /// an existing collection, is that we have no control over what the |
164 |
| - /// consumer will do with the results produced. They could very easily |
165 |
| - /// generate and store a set of permutations and only then begin to |
166 |
| - /// process them. If we reused the same collection, the caller would |
167 |
| - /// be surprised to discover that all of the permutations looked the |
168 |
| - /// same. |
169 |
| - /// </remarks> |
170 |
| - /// <returns>Array of permuted source sequence values</returns> |
171 |
| - T[] PermuteValueSet() |
172 |
| - { |
173 |
| - var permutedSet = new T[this.permutation.Length]; |
174 |
| - for (var i = 0; i < this.permutation.Length; i++) |
175 |
| - permutedSet[i] = this.valueSet[this.permutation[i]]; |
176 |
| - return permutedSet; |
177 |
| - } |
178 |
| - } |
179 |
| - |
180 | 26 | /// <summary>
|
181 | 27 | /// Generates a sequence of lists that represent the permutations of the original sequence.
|
182 | 28 | /// </summary>
|
@@ -205,10 +51,93 @@ public static IEnumerable<IList<T>> Permutations<T>(this IEnumerable<T> sequence
|
205 | 51 |
|
206 | 52 | return _(); IEnumerable<IList<T>> _()
|
207 | 53 | {
|
208 |
| - using var iter = new PermutationEnumerator<T>(sequence); |
| 54 | + // The algorithm used to generate permutations uses the fact that any set can be put |
| 55 | + // into 1-to-1 correspondence with the set of ordinals number (0..n). The |
| 56 | + // implementation here is based on the algorithm described by Kenneth H. Rosen, in |
| 57 | + // Discrete Mathematics and Its Applications, 2nd edition, pp. 282-284. |
| 58 | + |
| 59 | + var valueSet = sequence.ToArray(); |
| 60 | + |
| 61 | + // There's always at least one permutation: a copy of original set. |
| 62 | + |
| 63 | + yield return (IList<T>)valueSet.Clone(); |
| 64 | + |
| 65 | + // For empty sets and sets of cardinality 1, there exists only a single permutation. |
| 66 | + |
| 67 | + if (valueSet.Length is 0 or 1) |
| 68 | + yield break; |
| 69 | + |
| 70 | + var permutation = new int[valueSet.Length]; |
| 71 | + |
| 72 | + // Initialize lexographic ordering of the permutation indexes. |
| 73 | + |
| 74 | + for (var i = 0; i < permutation.Length; i++) |
| 75 | + permutation[i] = i; |
| 76 | + |
| 77 | + // For sets larger than 1 element, the number of nested loops needed is one less |
| 78 | + // than the set length. Note that the factorial grows VERY rapidly such that 13! |
| 79 | + // overflows the range of an Int32 and 28! overflows the range of a Decimal. |
| 80 | + |
| 81 | + ulong factorial = valueSet.Length switch |
| 82 | + { |
| 83 | + 0 => 1, |
| 84 | + 1 => 1, |
| 85 | + 2 => 2, |
| 86 | + 3 => 6, |
| 87 | + 4 => 24, |
| 88 | + 5 => 120, |
| 89 | + 6 => 720, |
| 90 | + 7 => 5_040, |
| 91 | + 8 => 40_320, |
| 92 | + 9 => 362_880, |
| 93 | + 10 => 3_628_800, |
| 94 | + 11 => 39_916_800, |
| 95 | + 12 => 479_001_600, |
| 96 | + 13 => 6_227_020_800, |
| 97 | + 14 => 87_178_291_200, |
| 98 | + 15 => 1_307_674_368_000, |
| 99 | + 16 => 20_922_789_888_000, |
| 100 | + 17 => 355_687_428_096_000, |
| 101 | + 18 => 6_402_373_705_728_000, |
| 102 | + 19 => 121_645_100_408_832_000, |
| 103 | + 20 => 2_432_902_008_176_640_000, |
| 104 | + _ => throw new OverflowException("Too many permutations."), |
| 105 | + }; |
| 106 | + |
| 107 | + for (var n = 1UL; n < factorial; n++) |
| 108 | + { |
| 109 | + // Transposes elements in the cached permutation array to produce the next |
| 110 | + // permutation. |
| 111 | + |
| 112 | + // Find the largest index j with permutation[j] < permutation[j+1]: |
| 113 | + |
| 114 | + var j = permutation.Length - 2; |
| 115 | + while (permutation[j] > permutation[j + 1]) |
| 116 | + j--; |
| 117 | + |
| 118 | + // Find index k such that permutation[k] is the smallest integer greater than |
| 119 | + // permutation[j] to the right of permutation[j]: |
| 120 | + |
| 121 | + var k = permutation.Length - 1; |
| 122 | + while (permutation[j] > permutation[k]) |
| 123 | + k--; |
209 | 124 |
|
210 |
| - while (iter.MoveNext()) |
211 |
| - yield return iter.Current; |
| 125 | + (permutation[j], permutation[k]) = (permutation[k], permutation[j]); |
| 126 | + |
| 127 | + // Move the tail of the permutation after the j-th position in increasing order. |
| 128 | + |
| 129 | + for (int x = permutation.Length - 1, y = j + 1; x > y; x--, y++) |
| 130 | + (permutation[x], permutation[y]) = (permutation[y], permutation[x]); |
| 131 | + |
| 132 | + // Yield a new array containing the values from the original set in their new |
| 133 | + // permuted order. |
| 134 | + |
| 135 | + var permutedSet = new T[permutation.Length]; |
| 136 | + for (var i = 0; i < permutation.Length; i++) |
| 137 | + permutedSet[i] = valueSet[permutation[i]]; |
| 138 | + |
| 139 | + yield return permutedSet; |
| 140 | + } |
212 | 141 | }
|
213 | 142 | }
|
214 | 143 | }
|
|
0 commit comments