Skip to content

Commit 85ff212

Browse files
committed
Add Pairs method to EnumerableExtensions for generating unique unordered pairs from a sequence
1 parent ebfaf92 commit 85ff212

3 files changed

Lines changed: 93 additions & 1 deletion

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace DotNetExtensions.Collections;
2+
3+
public static partial class EnumerableExtensions
4+
{
5+
/// <summary>
6+
/// Returns all unique unordered pairs from <paramref name="source"/> where each pair contains
7+
/// two distinct elements from the sequence. The order within each pair follows the original
8+
/// sequence order (first comes before second).
9+
/// </summary>
10+
/// <typeparam name="T">The element type of the sequence.</typeparam>
11+
/// <param name="source">The input sequence from which pairs are generated.</param>
12+
/// <returns>
13+
/// An <see cref="IEnumerable{T}"/> of tuples where each tuple contains two distinct elements
14+
/// from the source: (First, Second).
15+
/// </returns>
16+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="source"/> is null.</exception>
17+
public static IEnumerable<(T First, T Second)> Pairs<T>(this IEnumerable<T> source)
18+
{
19+
ArgumentNullException.ThrowIfNull(source);
20+
21+
switch (source)
22+
{
23+
case IList<T> list:
24+
{
25+
return PairsInternal(list);
26+
}
27+
case ICollection<T> coll:
28+
{
29+
var array = new T[coll.Count];
30+
coll.CopyTo(array, 0);
31+
return PairsInternal(array);
32+
}
33+
default:
34+
{
35+
var buffered = source.ToList();
36+
return PairsInternal(buffered);
37+
}
38+
}
39+
}
40+
41+
/// <summary>
42+
/// Internal implementation that enumerates all unique unordered index pairs from an indexed buffer.
43+
/// </summary>
44+
/// <typeparam name="T">The element type of the buffer.</typeparam>
45+
/// <param name="buffer">An indexed collection (e.g. <see cref="IList{T}"/>) to enumerate pairs from.</param>
46+
/// <returns>An <see cref="IEnumerable{T}"/> of tuples (First, Second) for every pair where First precedes Second.</returns>
47+
private static IEnumerable<(T First, T Second)> PairsInternal<T>(IList<T> buffer)
48+
{
49+
for (var i = 0; i < buffer.Count; i++)
50+
{
51+
for (var j = i + 1; j < buffer.Count; j++)
52+
{
53+
yield return (buffer[i], buffer[j]);
54+
}
55+
}
56+
}
57+
}

DotNetExtensions/DotNetExtensions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</PropertyGroup>
1414

1515
<PropertyGroup>
16-
<Version>5.9.0</Version>
16+
<Version>5.10.0</Version>
1717
<Authors>Diogo Medeiros</Authors>
1818
<Company>Diogo Medeiros</Company>
1919
<Description>This is a collection of extension methods for .NET Core.</Description>

DotNetExtensionsTests/Collections/EnumerableExtensionsTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,39 @@ public void Product_SourceIsEmpty_ThrowsInvalidOperationException()
169169
int[] source = [];
170170
Assert.ThrowsExactly<InvalidOperationException>(() => source.Product());
171171
}
172+
173+
[TestMethod]
174+
public void Pairs_SourceHasMultipleElements_ReturnsExpectedPairs()
175+
{
176+
AssertPairsAreExpected(DefaultSource);
177+
}
178+
179+
[TestMethod]
180+
public void Pairs_SourceIsCollection_ReturnsExpectedPairs()
181+
{
182+
AssertPairsAreExpected(DefaultSource.ToHashSet());
183+
}
184+
185+
[TestMethod]
186+
public void Pairs_SourceIsEnumerable_ReturnsExpectedPairs()
187+
{
188+
AssertPairsAreExpected(Enumerable.Range(1, 5).Select(i => i));
189+
}
190+
191+
private static void AssertPairsAreExpected(IEnumerable<int> source)
192+
{
193+
var result = source.Pairs().ToList();
194+
195+
Assert.HasCount(10, result);
196+
Assert.AreEqual((1, 2), result[0]);
197+
Assert.AreEqual((1, 3), result[1]);
198+
Assert.AreEqual((1, 4), result[2]);
199+
Assert.AreEqual((1, 5), result[3]);
200+
Assert.AreEqual((2, 3), result[4]);
201+
Assert.AreEqual((2, 4), result[5]);
202+
Assert.AreEqual((2, 5), result[6]);
203+
Assert.AreEqual((3, 4), result[7]);
204+
Assert.AreEqual((3, 5), result[8]);
205+
Assert.AreEqual((4, 5), result[9]);
206+
}
172207
}

0 commit comments

Comments
 (0)