Skip to content

Commit 50015f9

Browse files
2 parents d19433c + e848cfb commit 50015f9

3 files changed

Lines changed: 209 additions & 9 deletions

File tree

source/Extensions.Subsets.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ public static IEnumerable<T[]> Subsets<T>(this IReadOnlyList<T> source, int coun
8181
/// <param name="source">The source list to derive from.</param>
8282
/// <param name="count">The maximum number of items in the result sets.</param>
8383
/// <returns>An enumerable containing the resultant subsets as an buffer array.</returns>
84-
public static IEnumerable<T[]> SubsetsBuffered<T>(this IReadOnlyList<T> source, int count)
84+
public static IEnumerable<ReadOnlyMemory<T>> SubsetsBuffered<T>(this IReadOnlyList<T> source, int count)
8585
{
8686
var pool = ArrayPool<T>.Shared;
8787
var buffer = pool.Rent(count);
88-
88+
var readBuffer = new ReadOnlyMemory<T>(buffer, 0, count);
8989
try
9090
{
91-
foreach (var subset in Subsets(source, count, buffer))
92-
yield return subset;
91+
foreach (var _ in Subsets(source, count, buffer))
92+
yield return readBuffer;
9393
}
9494
finally
9595
{
@@ -108,7 +108,7 @@ public static IEnumerable<T[]> SubsetsBuffered<T>(this IReadOnlyList<T> source,
108108
public static IEnumerable<T[]> Subsets<T>(this IReadOnlyList<T> source, int count)
109109
{
110110
foreach (var subset in SubsetsBuffered(source, count))
111-
yield return subset.AsCopy(count);
111+
yield return subset.ToArray();
112112
}
113113

114114
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Collections.Generic;
4+
5+
namespace Open.Collections
6+
{
7+
public static partial class Extensions
8+
{
9+
/// <summary>
10+
/// Progressively enumerates the possible (ordered) subsets of the list, limited by the provided count.
11+
/// The buffer is filled with the values and returned as the yielded value.
12+
/// Note: Works especially well when the source is a LazyList.
13+
/// </summary>
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[]> SubsetsProgressive<T>(this IReadOnlyList<T> source, int count, T[] buffer)
22+
{
23+
if (count < 1)
24+
throw new ArgumentOutOfRangeException(nameof(count), count, "Must greater than zero.");
25+
if (buffer is null)
26+
throw new ArgumentNullException(nameof(buffer));
27+
if (buffer.Length < count)
28+
throw new ArgumentOutOfRangeException(nameof(buffer), buffer, "Length must be greater than or equal to the provided count.");
29+
30+
if (count == 1)
31+
{
32+
foreach (var e in source)
33+
{
34+
buffer[0] = e;
35+
yield return buffer;
36+
}
37+
yield break;
38+
}
39+
40+
var pool = ArrayPool<int>.Shared;
41+
var indices = pool.Rent(count);
42+
try
43+
{
44+
using var e = source.GetEnumerator();
45+
46+
// Setup the first result and make sure there's enough for the count.
47+
var n = 0;
48+
for (; n < count; ++n)
49+
{
50+
if (!e.MoveNext()) throw new ArgumentOutOfRangeException(nameof(count), count, "Is greater than the length of the source.");
51+
buffer[n] = e.Current;
52+
indices[n] = n;
53+
}
54+
55+
// First result.
56+
yield return buffer;
57+
58+
if (!e.MoveNext()) yield break; // Only one set.
59+
60+
var lastSlot = count - 1;
61+
62+
// Second result.
63+
buffer[lastSlot] = e.Current;
64+
yield return buffer;
65+
indices[lastSlot] = n;
66+
67+
var nextToLastSlot = lastSlot - 1;
68+
69+
loop:
70+
var prevIndex = n;
71+
var pos = nextToLastSlot;
72+
73+
while (pos >= 0)
74+
{
75+
var firstRun = true;
76+
var index = indices[pos];
77+
while (index + 1 < prevIndex)
78+
{
79+
// Subsequent results.
80+
buffer[pos] = source[++index];
81+
indices[pos] = index;
82+
if (firstRun)
83+
{
84+
while (pos < nextToLastSlot && index + 1 < prevIndex)
85+
{
86+
buffer[++pos] = source[++index];
87+
indices[pos] = index;
88+
}
89+
prevIndex = indices[pos + 1];
90+
firstRun = false;
91+
}
92+
yield return buffer;
93+
}
94+
--pos;
95+
prevIndex = index;
96+
97+
98+
}
99+
100+
if (!e.MoveNext()) yield break;
101+
102+
// Update the last one.
103+
buffer[lastSlot] = e.Current;
104+
for (var i = 0; i < lastSlot; ++i)
105+
{
106+
buffer[i] = source[i];
107+
indices[i] = i;
108+
}
109+
yield return buffer;
110+
indices[lastSlot] = ++n;
111+
112+
goto loop;
113+
}
114+
finally
115+
{
116+
pool.Return(indices);
117+
}
118+
}
119+
120+
/// <summary>
121+
/// Enumerates the possible (ordered) subsets of the list, limited by the provided count.
122+
/// The yielded results are a buffer (array) that is at least the length of the provided count.
123+
/// NOTE: Do not retain the result array as it is returned to an array pool when complete.
124+
/// </summary>
125+
/// <param name="source">The source list to derive from.</param>
126+
/// <param name="count">The maximum number of items in the result sets.</param>
127+
/// <returns>An enumerable containing the resultant subsets as an buffer array.</returns>
128+
public static IEnumerable<ReadOnlyMemory<T>> SubsetsProgressiveBuffered<T>(this IReadOnlyList<T> source, int count)
129+
{
130+
var pool = ArrayPool<T>.Shared;
131+
var buffer = pool.Rent(count);
132+
var readBuffer = new ReadOnlyMemory<T>(buffer, 0, count);
133+
try
134+
{
135+
foreach (var _ in SubsetsProgressive(source, count, buffer))
136+
yield return readBuffer;
137+
}
138+
finally
139+
{
140+
pool.Return(buffer, true);
141+
}
142+
}
143+
144+
145+
/// <summary>
146+
/// Enumerates the possible (ordered) subsets of the list, limited by the provided count.
147+
/// A new array is created for each subset.
148+
/// </summary>
149+
/// <param name="source">The source list to derive from.</param>
150+
/// <param name="count">The maximum number of items in the result sets.</param>
151+
/// <returns>An enumerable containing the resultant subsets.</returns>
152+
public static IEnumerable<T[]> SubsetsProgressive<T>(this IReadOnlyList<T> source, int count)
153+
{
154+
foreach (var subset in SubsetsProgressiveBuffered(source, count))
155+
yield return subset.ToArray();
156+
}
157+
158+
}
159+
}

testing/Open.Collections.Tests/SubsetTests.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class SubsetTests
1010
static ImmutableArray<char> Set2 = ImmutableArray.Create('A', 'C', 'E');
1111
static ImmutableArray<char> Set3 = ImmutableArray.Create('A', 'B', 'C', 'D');
1212
static ImmutableArray<int> Set4 = Enumerable.Range(1, 5).ToImmutableArray();
13+
static ImmutableArray<int> Set5 = Enumerable.Range(1, 11).ToImmutableArray();
1314

1415
[Fact]
1516
public void TestSubset1_2()
@@ -21,6 +22,9 @@ public void TestSubset1_2()
2122
};
2223
var actual = Set1.Subsets(2).ToArray();
2324
Assert.Equal(expected, actual);
25+
26+
var progressive = Set1.SubsetsProgressive(2).ToArray();
27+
Assert.Equal(expected, progressive);
2428
}
2529

2630
[Fact]
@@ -33,6 +37,9 @@ public void TestSubset2_2()
3337
};
3438
var actual = Set2.Subsets(2).ToArray();
3539
Assert.Equal(expected, actual);
40+
41+
var progressive = Set2.SubsetsProgressive(2).ToArray();
42+
Assert.Equal(expected, progressive);
3643
}
3744

3845
[Fact]
@@ -41,13 +48,15 @@ public void TestSubset3_2()
4148
var expected = new char[][] {
4249
new char[] { 'A', 'B' },
4350
new char[] { 'A', 'C' },
44-
new char[] { 'A', 'D' },
4551
new char[] { 'B', 'C' },
52+
new char[] { 'A', 'D' },
4653
new char[] { 'B', 'D' },
4754
new char[] { 'C', 'D' },
4855
};
49-
var actual = Set3.Subsets(2).ToArray();
50-
Assert.Equal(expected, actual);
56+
Assert.Equal(expected.Length, Set3.Subsets(2).Count());
57+
58+
var progressive = Set3.SubsetsProgressive(2).ToArray();
59+
Assert.Equal(expected, progressive);
5160
}
5261

5362
[Fact]
@@ -61,10 +70,13 @@ public void TestSubset3_3()
6170
};
6271
var actual = Set3.Subsets(3).ToArray();
6372
Assert.Equal(expected, actual);
73+
74+
var progressive = Set3.SubsetsProgressive(3).ToArray();
75+
Assert.Equal(expected, progressive);
6476
}
6577

6678
[Fact]
67-
public void TestSubset3_4()
79+
public void TestSubset4_4()
6880
{
6981
var expected = new int[][] {
7082
new int[] { 1, 2, 3, 4 },
@@ -75,6 +87,35 @@ public void TestSubset3_4()
7587
};
7688
var actual = Set4.Subsets(4).ToArray();
7789
Assert.Equal(expected, actual);
90+
91+
var progressive = Set4.SubsetsProgressive(4).ToArray();
92+
Assert.Equal(expected, progressive);
93+
}
94+
95+
[Fact]
96+
public void TestSubset4_3()
97+
{
98+
var expected = new int[][] {
99+
new int[] { 1, 2, 3 },
100+
new int[] { 1, 2, 4 },
101+
new int[] { 1, 3, 4 },
102+
new int[] { 2, 3, 4 },
103+
new int[] { 1, 2, 5 },
104+
new int[] { 1, 3, 5 },
105+
new int[] { 1, 4, 5 },
106+
new int[] { 2, 3, 5 },
107+
new int[] { 2, 4, 5 },
108+
new int[] { 3, 4, 5 },
109+
};
110+
var actual = Set4.SubsetsProgressive(3).ToArray();
111+
Assert.Equal(expected, actual);
112+
113+
var a2 = Set5.SubsetsProgressive(3);
114+
var actual2 = a2.Take(10).ToArray();
115+
Assert.Equal(expected, actual2);
116+
117+
118+
Assert.Equal(Set5.Subsets(3).Count(), a2.Count());
78119
}
79120
}
80121
}

0 commit comments

Comments
 (0)