Skip to content

Commit 93505c9

Browse files
Updated subsets and added tests.
1 parent 1226163 commit 93505c9

File tree

5 files changed

+338
-138
lines changed

5 files changed

+338
-138
lines changed

source/Extensions.Combinations.cs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Diagnostics.Contracts;
6+
using System.Linq;
7+
8+
namespace Open.Collections
9+
{
10+
public static partial class Extensions
11+
{
12+
static IEnumerable<T[]> CombinationsCore<T>(IReadOnlyList<T> source, int length, bool distinctSet, T[]? buffer = null)
13+
{
14+
Debug.Assert(length != 0);
15+
var count = source.Count;
16+
Debug.Assert(count != 0);
17+
18+
{
19+
var value = source[0];
20+
var result = buffer ?? new T[length];
21+
for (var i = 0; i < length; i++) result[i] = value;
22+
yield return result;
23+
if (count == 1) yield break;
24+
}
25+
26+
var pool = ArrayPool<int>.Shared;
27+
var indexes = pool.Rent(length);
28+
try
29+
{
30+
31+
for (var i = 0; i < length; i++) indexes[i] = 0;
32+
33+
var lastIndex = length - 1;
34+
bool GetNext()
35+
{
36+
int i;
37+
for (i = lastIndex; i >= 0; --i)
38+
{
39+
var e = ++indexes[i];
40+
if (count == e)
41+
{
42+
if (i == 0) return false;
43+
}
44+
else
45+
{
46+
if (i == lastIndex) return true;
47+
else break;
48+
}
49+
}
50+
51+
for (++i; i < length; ++i)
52+
{
53+
if (indexes[i] == count)
54+
indexes[i] = distinctSet ? indexes[i - 1] : 0;
55+
}
56+
57+
return true;
58+
}
59+
60+
while (GetNext())
61+
{
62+
var result = buffer ?? new T[length];
63+
for (var i = 0; i < length; i++)
64+
{
65+
result[i] = source[indexes[i]];
66+
}
67+
yield return result;
68+
}
69+
}
70+
finally
71+
{
72+
pool.Return(indexes);
73+
}
74+
}
75+
76+
/// <summary>
77+
/// Enumerates all possible combinations of values.
78+
/// Results can be different permutations of another set.
79+
/// Examples:
80+
/// [0, 0], [0, 1], [1, 0], [1, 1] where [0, 1] and [1, 0] are a different permutatation of the same set.
81+
/// </summary>
82+
/// <param name="elements">The elements to draw from.</param>
83+
/// <param name="length">The length of each result.</param>
84+
public static IEnumerable<T[]> Combinations<T>(this IEnumerable<T> elements, int length, T[]? buffer = null)
85+
{
86+
if (elements is null)
87+
throw new ArgumentNullException(nameof(elements));
88+
if (length < 0)
89+
throw new ArgumentOutOfRangeException(nameof(length), length, "Cannot be less than zero.");
90+
Contract.EndContractBlock();
91+
92+
if (length == 0) return Enumerable.Empty<T[]>();
93+
var source = elements as IReadOnlyList<T> ?? elements.ToArray();
94+
return source.Count == 0 ? Enumerable.Empty<T[]>() : CombinationsCore(source, length, false, buffer);
95+
}
96+
97+
/// <summary>
98+
/// Enumerates all possible distinct set combinations.
99+
/// A set that has its items reordered is not distinct from the original.
100+
/// Examples:
101+
/// [0, 0], [0, 1], [1, 1] where [1, 0] is not included as it is not a disticnt set from [0, 1].
102+
///
103+
/// </summary>
104+
/// <param name="elements">The elements to draw from.</param>
105+
/// <param name="length">The length of each result.</param>
106+
public static IEnumerable<T[]> CombinationsDistinct<T>(this IEnumerable<T> elements, int length, T[]? buffer = null)
107+
{
108+
if (elements is null)
109+
throw new ArgumentNullException(nameof(elements));
110+
if (length < 0)
111+
throw new ArgumentOutOfRangeException(nameof(length), length, "Cannot be less than zero.");
112+
Contract.EndContractBlock();
113+
114+
if (length == 0) return Enumerable.Empty<T[]>();
115+
var source = elements as IReadOnlyList<T> ?? elements.ToArray();
116+
return source.Count == 0 ? Enumerable.Empty<T[]>() : CombinationsCore(source, length, true, buffer);
117+
}
118+
119+
[Obsolete("Deprecated in favor of using .Subsets(length) or .Combinations(length) depending on intent.")]
120+
public static IEnumerable<T[]> Combinations<T>(this IEnumerable<T> elements, int length, bool uniqueOnly)
121+
{
122+
if (elements is null)
123+
throw new ArgumentNullException(nameof(elements));
124+
if (length < 0)
125+
throw new ArgumentOutOfRangeException(nameof(length), length, "Cannot be less than zero.");
126+
Contract.EndContractBlock();
127+
128+
if (length == 0) return Enumerable.Empty<T[]>();
129+
var source = elements as IReadOnlyList<T> ?? elements.ToArray();
130+
var count = source.Count;
131+
if (count == 0) return Enumerable.Empty<T[]>();
132+
133+
return uniqueOnly ? source.Subsets(length) : CombinationsCore(source, length, true);
134+
}
135+
136+
/// <summary>
137+
/// Enumerates all possible (order retained) combinations of the source elements up to the length.
138+
/// </summary>
139+
/// <param name="elements">The elements to draw from.</param>
140+
/// <param name="length">The length of each result.</param>
141+
/// <param name="uniqueOnly">Finds all possible subsets instead of all possible combinations of values.</param>
142+
public static IEnumerable<T[]> CombinationsBuffered<T>(this IEnumerable<T> elements, int length)
143+
{
144+
if (elements is null)
145+
throw new ArgumentNullException(nameof(elements));
146+
if (length < 0)
147+
throw new ArgumentOutOfRangeException(nameof(length), length, "Cannot be less than zero.");
148+
Contract.EndContractBlock();
149+
150+
if (length == 0)
151+
{
152+
return Enumerable.Empty<T[]>();
153+
}
154+
155+
var arrayPool = ArrayPool<T>.Shared;
156+
var buffer = arrayPool.Rent(length);
157+
try
158+
{
159+
return Combinations(elements, length, buffer);
160+
}
161+
finally
162+
{
163+
arrayPool.Return(buffer, true);
164+
}
165+
}
166+
167+
}
168+
}

source/Extensions.Subsets.cs

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,72 @@
11
using System;
22
using System.Buffers;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
45

56
namespace Open.Collections
67
{
78
public static partial class Extensions
89
{
9-
1010
/// <summary>
11-
/// Enumerates the possible (ordered) subsets.
12-
/// If a buffer is supplied, the buffer is filled with the values and returned as the yielded value.
13-
/// If no buffer is supplised, a new array is created for each subset.
11+
/// Enumerates the possible (ordered) subsets of the list, limited by the provided count.
12+
/// The buffer is filled with the values and returned as the yielded value.
1413
/// </summary>
15-
public static IEnumerable<T[]> Subsets<T>(this IReadOnlyList<T> source, int count, T[]? buffer = null)
14+
/// <param name="source">The source list to derive from.</param>
15+
/// <param name="count">The maximum number of items in the result sets.</param>
16+
/// <param name="buffer">
17+
/// A buffer to use instead of returning new arrays for each iteration.
18+
/// It must be at least the length of the count.
19+
/// </param>
20+
/// <returns>An enumerable containing the resultant subsets.</returns>
21+
public static IEnumerable<T[]> Subsets<T>(this IReadOnlyList<T> source, int count, T[] buffer)
1622
{
1723
if (count < 1)
1824
throw new ArgumentOutOfRangeException(nameof(count), count, "Must greater than zero.");
1925
if (count > source.Count)
2026
throw new ArgumentOutOfRangeException(nameof(count), count, "Must be less than or equal to the length of the source set.");
27+
if (buffer is null)
28+
throw new ArgumentNullException(nameof(buffer));
29+
if (buffer.Length < count)
30+
throw new ArgumentOutOfRangeException(nameof(buffer), buffer, "Length must be greater than or equal to the provided count.");
31+
2132

2233
if (count == 1)
2334
{
24-
var result = buffer ?? new T[count];
2535
foreach (var e in source)
2636
{
27-
result[0] = e;
28-
yield return result;
37+
buffer[0] = e;
38+
yield return buffer;
2939
}
3040
yield break;
3141
}
42+
43+
var diff = source.Count - count;
3244
var pool = ArrayPool<int>.Shared;
3345
var indices = pool.Rent(count);
3446
try
3547
{
36-
for (int pos = 0, index = 0; ;)
48+
var pos = 0;
49+
var index = 0;
50+
51+
loop:
52+
while (pos < count)
53+
{
54+
indices[pos] = index;
55+
buffer[pos] = source[index];
56+
++pos;
57+
++index;
58+
}
59+
60+
yield return buffer;
61+
62+
do
3763
{
38-
var result = buffer ?? new T[count];
39-
for (; pos < count; pos++, index++)
40-
{
41-
indices[pos] = index;
42-
result[pos] = source[index];
43-
}
44-
yield return result;
45-
do
46-
{
47-
if (pos == 0) yield break;
48-
index = indices[--pos] + 1;
49-
}
50-
while (index > source.Count - count + pos);
64+
if (pos == 0) yield break;
65+
index = indices[--pos] + 1;
5166
}
67+
while (index > diff + pos);
68+
69+
goto loop;
5270
}
5371
finally
5472
{
@@ -57,22 +75,42 @@ public static IEnumerable<T[]> Subsets<T>(this IReadOnlyList<T> source, int coun
5775
}
5876

5977
/// <summary>
60-
/// Iteratively updates a buffer with the possible subsets.
78+
/// Enumerates the possible (ordered) subsets of the list, limited by the provided count.
79+
/// The yielded results are a buffer (array) that is at least the length of the provided count.
80+
/// NOTE: Do not retain the result array as it is returned to an array pool when complete.
6181
/// </summary>
62-
/// <returns>An enumerable that yields a buffer that is at least the length of the count provided.</returns>
82+
/// <param name="source">The source list to derive from.</param>
83+
/// <param name="count">The maximum number of items in the result sets.</param>
84+
/// <returns>An enumerable containing the resultant subsets as an buffer array.</returns>
6385
public static IEnumerable<T[]> SubsetsBuffered<T>(this IReadOnlyList<T> source, int count)
6486
{
6587
var pool = ArrayPool<T>.Shared;
6688
var buffer = pool.Rent(count);
6789

6890
try
6991
{
70-
return Subsets(source, count, buffer);
92+
foreach (var subset in Subsets(source, count, buffer))
93+
yield return subset;
7194
}
7295
finally
7396
{
7497
pool.Return(buffer, true);
7598
}
7699
}
100+
101+
102+
/// <summary>
103+
/// Enumerates the possible (ordered) subsets of the list, limited by the provided count.
104+
/// A new array is created for each subset.
105+
/// </summary>
106+
/// <param name="source">The source list to derive from.</param>
107+
/// <param name="count">The maximum number of items in the result sets.</param>
108+
/// <returns>An enumerable containing the resultant subsets.</returns>
109+
public static IEnumerable<T[]> Subsets<T>(this IReadOnlyList<T> source, int count)
110+
{
111+
foreach (var subset in SubsetsBuffered(source, count))
112+
yield return subset.AsCopy(count);
113+
}
114+
77115
}
78116
}

0 commit comments

Comments
 (0)