Skip to content

Commit 365adcc

Browse files
committed
Add EquiInterleave
1 parent 13c8c83 commit 365adcc

File tree

4 files changed

+297
-0
lines changed

4 files changed

+297
-0
lines changed

Diff for: MoreLinq.Test/EquiInterleaveTest.cs

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#region License and Terms
2+
// MoreLINQ - Extensions to LINQ to Objects
3+
// Copyright (c) 2010 Leopold Bushkin. All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
#endregion
17+
18+
namespace MoreLinq.Test
19+
{
20+
using System;
21+
using NUnit.Framework;
22+
23+
/// <summary>
24+
/// Verify the behavior of the Interleave operator
25+
/// </summary>
26+
[TestFixture]
27+
public class EquiInterleaveTests
28+
{
29+
/// <summary>
30+
/// Verify that EquiInterleave behaves in a lazy manner
31+
/// </summary>
32+
[Test]
33+
public void TestEquiInterleaveIsLazy()
34+
{
35+
new BreakingSequence<int>().EquiInterleave(new BreakingSequence<int>());
36+
}
37+
38+
/// <summary>
39+
/// Verify that EquiInterleave disposes those enumerators that it managed
40+
/// to open successfully
41+
/// </summary>
42+
[Test]
43+
public void TestEquiInterleaveDisposesOnError()
44+
{
45+
using (var sequenceA = TestingSequence.Of<int>())
46+
{
47+
Assert.Throws<InvalidOperationException>(() => // Expected and thrown by BreakingSequence
48+
sequenceA.EquiInterleave(new BreakingSequence<int>()).Consume());
49+
}
50+
}
51+
52+
/// <summary>
53+
/// Verify that two balanced sequences will EquiInterleave all of their elements
54+
/// </summary>
55+
[Test]
56+
public void TestEquiInterleaveTwoBalancedSequences()
57+
{
58+
const int count = 10;
59+
var sequenceA = Enumerable.Range(1, count);
60+
var sequenceB = Enumerable.Range(1, count);
61+
var result = sequenceA.EquiInterleave(sequenceB);
62+
63+
Assert.That(result, Is.EqualTo(Enumerable.Range(1, count).Select(x => new[] { x, x }).SelectMany(z => z)));
64+
}
65+
66+
/// <summary>
67+
/// Verify that EquiInterleave with two empty sequences results in an empty sequence
68+
/// </summary>
69+
[Test]
70+
public void TestEquiInterleaveTwoEmptySequences()
71+
{
72+
var sequenceA = Enumerable.Empty<int>();
73+
var sequenceB = Enumerable.Empty<int>();
74+
var result = sequenceA.EquiInterleave(sequenceB);
75+
76+
Assert.That(result, Is.EqualTo(Enumerable.Empty<int>()));
77+
}
78+
79+
/// <summary>
80+
/// Verify that EquiInterleave throw on two unbalanced sequences
81+
/// </summary>
82+
[Test]
83+
public void TestEquiInterleaveThrowOnUnbalanced()
84+
{
85+
void Code()
86+
{
87+
var sequenceA = new[] { 0, 0, 0, 0, 0, 0 };
88+
var sequenceB = new[] { 1, 1, 1, 1 };
89+
sequenceA.EquiInterleave(sequenceB).Consume();
90+
}
91+
92+
Assert.Throws<InvalidOperationException>(Code);
93+
}
94+
95+
/// <summary>
96+
/// Verify that EquiInterleave multiple empty sequences results in an empty sequence
97+
/// </summary>
98+
[Test]
99+
public void TestEquiInterleaveManyEmptySequences()
100+
{
101+
var sequenceA = Enumerable.Empty<int>();
102+
var sequenceB = Enumerable.Empty<int>();
103+
var sequenceC = Enumerable.Empty<int>();
104+
var sequenceD = Enumerable.Empty<int>();
105+
var sequenceE = Enumerable.Empty<int>();
106+
var result = sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD, sequenceE);
107+
108+
Assert.That(result, Is.Empty);
109+
}
110+
111+
/// <summary>
112+
/// Verify that EquiInterleave throw on multiple unbalanced sequences
113+
/// </summary>
114+
[Test]
115+
public void TestEquiInterleaveManyImbalanceStrategySkip()
116+
{
117+
void Code()
118+
{
119+
var sequenceA = new[] {1, 5, 8, 11, 14, 16,};
120+
var sequenceB = new[] {2, 6, 9, 12,};
121+
var sequenceC = new int[] { };
122+
var sequenceD = new[] {3};
123+
var sequenceE = new[] {4, 7, 10, 13, 15, 17,};
124+
sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD, sequenceE).Consume();
125+
}
126+
127+
Assert.Throws<InvalidOperationException>(Code);
128+
}
129+
130+
/// <summary>
131+
/// Verify that Interleave disposes of all iterators it creates.
132+
/// </summary>
133+
[Test]
134+
public void TestEquiInterleaveDisposesAllIterators()
135+
{
136+
const int count = 10;
137+
138+
using (var sequenceA = Enumerable.Range(1, count).AsTestingSequence())
139+
using (var sequenceB = Enumerable.Range(1, count).AsTestingSequence())
140+
using (var sequenceC = Enumerable.Range(1, count).AsTestingSequence())
141+
using (var sequenceD = Enumerable.Range(1, count).AsTestingSequence())
142+
{
143+
sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD).Consume();
144+
}
145+
}
146+
}
147+
}

Diff for: MoreLinq/EquiInterleave.cs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#region License and Terms
2+
// MoreLINQ - Extensions to LINQ to Objects
3+
// Copyright (c) 2019 Pierre Lando. All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
#endregion
17+
18+
namespace MoreLinq
19+
{
20+
using System;
21+
using System.Collections.Generic;
22+
using System.Linq;
23+
24+
public static partial class MoreEnumerable
25+
{
26+
/// <summary>
27+
/// Interleaves the elements of two or more sequences into a single sequence.
28+
/// If the input sequences are of different lengths, an exception is thrown.
29+
/// </summary>
30+
/// <remarks>
31+
/// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed
32+
/// by the second, then the third, and so on. So, for example:<br/>
33+
/// <code><![CDATA[
34+
/// {1,1,1}.Interleave( {2,2,2}, {3,3,3} ) => { 1,2,3,1,2,3,1,2,3 }
35+
/// ]]></code>
36+
/// This operator behaves in a deferred and streaming manner.<br/>
37+
/// As soon as a sequence shorter than the other is detected, an exception is thrown.<br/>
38+
/// The sequences are interleaved in the order that they appear in the <paramref name="otherSequences"/>
39+
/// collection, with <paramref name="sequence"/> as the first sequence.
40+
/// </remarks>
41+
/// <typeparam name="T">The type of the elements of the source sequences</typeparam>
42+
/// <param name="sequence">The first sequence in the interleave group</param>
43+
/// <param name="otherSequences">The other sequences in the interleave group</param>
44+
/// <returns>
45+
/// A sequence of interleaved elements from all of the source sequences</returns>
46+
/// <exception cref="InvalidOperationException">
47+
/// The source sequences are of different lengths.</exception>
48+
49+
public static IEnumerable<T> EquiInterleave<T>(this IEnumerable<T> sequence, params IEnumerable<T>[] otherSequences)
50+
{
51+
if (sequence == null) throw new ArgumentNullException(nameof(sequence));
52+
if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences));
53+
54+
return EquiInterleave(otherSequences.Prepend(sequence));
55+
}
56+
57+
private static IEnumerable<T> EquiInterleave<T>(IEnumerable<IEnumerable<T>> sequences)
58+
{
59+
var enumerators = new List<IEnumerator<T>>();
60+
61+
try
62+
{
63+
foreach (var sequence in sequences)
64+
{
65+
if (sequence == null)
66+
throw new ArgumentException("An item is null.", nameof(sequences));
67+
enumerators.Add(sequence.GetEnumerator());
68+
}
69+
70+
if (enumerators.Count == 0)
71+
yield break;
72+
73+
for (;;)
74+
{
75+
var (isHomogeneous, hasNext) = enumerators.Select(e => e.MoveNext()).IsHomogeneous();
76+
77+
if (isHomogeneous == false)
78+
throw new InvalidOperationException("Input sequences are of different length.");
79+
80+
if (!hasNext)
81+
break;
82+
83+
foreach (var enumerator in enumerators)
84+
yield return enumerator.Current;
85+
}
86+
}
87+
finally
88+
{
89+
foreach (var enumerator in enumerators)
90+
enumerator.Dispose();
91+
}
92+
}
93+
94+
private static (bool? isHomogeneous, T value) IsHomogeneous<T>(this IEnumerable<T> source)
95+
{
96+
var comparer = EqualityComparer<T>.Default;
97+
using var e = source.GetEnumerator();
98+
99+
if (!e.MoveNext())
100+
return (null, default);
101+
102+
var first = e.Current;
103+
while (e.MoveNext())
104+
{
105+
if (!comparer.Equals(first, e.Current))
106+
return (false, default);
107+
}
108+
109+
return (true, first);
110+
}
111+
}
112+
}

Diff for: MoreLinq/Extensions.g.cs

+33
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,39 @@ public static bool EndsWith<T>(this IEnumerable<T> first, IEnumerable<T> second,
13191319

13201320
}
13211321

1322+
/// <summary><c>EquiInterleave</c> extension.</summary>
1323+
1324+
[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
1325+
public static partial class EquiInterleaveExtension
1326+
{
1327+
/// <summary>
1328+
/// Interleaves the elements of two or more sequences into a single sequence.
1329+
/// If the input sequences are of different lengths, an exception is thrown.
1330+
/// </summary>
1331+
/// <remarks>
1332+
/// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed
1333+
/// by the second, then the third, and so on. So, for example:<br/>
1334+
/// <code><![CDATA[
1335+
/// {1,1,1}.Interleave( {2,2,2}, {3,3,3} ) => { 1,2,3,1,2,3,1,2,3 }
1336+
/// ]]></code>
1337+
/// This operator behaves in a deferred and streaming manner.<br/>
1338+
/// As soon as a sequence shorter than the other is detected, an exception is thrown.<br/>
1339+
/// The sequences are interleaved in the order that they appear in the <paramref name="otherSequences"/>
1340+
/// collection, with <paramref name="sequence"/> as the first sequence.
1341+
/// </remarks>
1342+
/// <typeparam name="T">The type of the elements of the source sequences</typeparam>
1343+
/// <param name="sequence">The first sequence in the interleave group</param>
1344+
/// <param name="otherSequences">The other sequences in the interleave group</param>
1345+
/// <returns>
1346+
/// A sequence of interleaved elements from all of the source sequences</returns>
1347+
/// <exception cref="InvalidOperationException">
1348+
/// The source sequences are of different lengths.</exception>
1349+
1350+
public static IEnumerable<T> EquiInterleave<T>(this IEnumerable<T> sequence, params IEnumerable<T>[] otherSequences)
1351+
=> MoreEnumerable.EquiInterleave(sequence, otherSequences);
1352+
1353+
}
1354+
13221355
/// <summary><c>EquiZip</c> extension.</summary>
13231356

13241357
[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]

Diff for: README.md

+5
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ sequence.
216216

217217
This method has 2 overloads.
218218

219+
### EquiInterleave
220+
221+
Interleaves the elements of two or more sequences into a single sequence.
222+
If the input sequences are of different lengths, an exception is thrown.
223+
219224
### EquiZip
220225

221226
Returns a projection of tuples, where each tuple contains the N-th

0 commit comments

Comments
 (0)