Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EquiInterleave #700

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions MoreLinq.Test/EquiInterleaveTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2010 Leopold Bushkin. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq.Test
{
using System;
using NUnit.Framework;

/// <summary>
/// Verify the behavior of the Interleave operator
/// </summary>
[TestFixture]
public class EquiInterleaveTests
{
/// <summary>
/// Verify that EquiInterleave behaves in a lazy manner
/// </summary>
[Test]
public void TestEquiInterleaveIsLazy()
{
new BreakingSequence<int>().EquiInterleave(new BreakingSequence<int>());
}

/// <summary>
/// Verify that EquiInterleave disposes those enumerators that it managed
/// to open successfully
/// </summary>
[Test]
public void TestEquiInterleaveDisposesOnError()
{
using (var sequenceA = TestingSequence.Of<int>())
{
Assert.Throws<InvalidOperationException>(() => // Expected and thrown by BreakingSequence
sequenceA.EquiInterleave(new BreakingSequence<int>()).Consume());
}
}

/// <summary>
/// Verify that two balanced sequences will EquiInterleave all of their elements
/// </summary>
[Test]
public void TestEquiInterleaveTwoBalancedSequences()
{
const int count = 10;
var sequenceA = Enumerable.Range(1, count);
var sequenceB = Enumerable.Range(1, count);
var result = sequenceA.EquiInterleave(sequenceB);

Assert.That(result, Is.EqualTo(Enumerable.Range(1, count).Select(x => new[] { x, x }).SelectMany(z => z)));
}

/// <summary>
/// Verify that EquiInterleave with two empty sequences results in an empty sequence
/// </summary>
[Test]
public void TestEquiInterleaveTwoEmptySequences()
{
var sequenceA = Enumerable.Empty<int>();
var sequenceB = Enumerable.Empty<int>();
var result = sequenceA.EquiInterleave(sequenceB);

Assert.That(result, Is.EqualTo(Enumerable.Empty<int>()));
}

/// <summary>
/// Verify that EquiInterleave throw on two unbalanced sequences
/// </summary>
[Test]
public void TestEquiInterleaveThrowOnUnbalanced()
{
void Code()
{
var sequenceA = new[] { 0, 0, 0, 0, 0, 0 };
var sequenceB = new[] { 1, 1, 1, 1 };
sequenceA.EquiInterleave(sequenceB).Consume();
}

Assert.Throws<InvalidOperationException>(Code);
}

/// <summary>
/// Verify that EquiInterleave multiple empty sequences results in an empty sequence
/// </summary>
[Test]
public void TestEquiInterleaveManyEmptySequences()
{
var sequenceA = Enumerable.Empty<int>();
var sequenceB = Enumerable.Empty<int>();
var sequenceC = Enumerable.Empty<int>();
var sequenceD = Enumerable.Empty<int>();
var sequenceE = Enumerable.Empty<int>();
var result = sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD, sequenceE);

Assert.That(result, Is.Empty);
}

/// <summary>
/// Verify that EquiInterleave throw on multiple unbalanced sequences
/// </summary>
[Test]
public void TestEquiInterleaveManyImbalanceStrategySkip()
{
void Code()
{
var sequenceA = new[] {1, 5, 8, 11, 14, 16,};
var sequenceB = new[] {2, 6, 9, 12,};
var sequenceC = new int[] { };
var sequenceD = new[] {3};
var sequenceE = new[] {4, 7, 10, 13, 15, 17,};
sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD, sequenceE).Consume();
}

Assert.Throws<InvalidOperationException>(Code);
}

/// <summary>
/// Verify that Interleave disposes of all iterators it creates.
/// </summary>
[Test]
public void TestEquiInterleaveDisposesAllIterators()
{
const int count = 10;

using (var sequenceA = Enumerable.Range(1, count).AsTestingSequence())
using (var sequenceB = Enumerable.Range(1, count).AsTestingSequence())
using (var sequenceC = Enumerable.Range(1, count).AsTestingSequence())
using (var sequenceD = Enumerable.Range(1, count).AsTestingSequence())
{
sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD).Consume();
}
}
}
}
98 changes: 98 additions & 0 deletions MoreLinq/EquiInterleave.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2019 Pierre Lando. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Linq;

public static partial class MoreEnumerable
{
/// <summary>
/// Interleaves the elements of two or more sequences into a single sequence.
/// If the input sequences are of different lengths, an exception is thrown.
/// </summary>
/// <remarks>
/// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed
/// by the second, then the third, and so on. So, for example:<br/>
/// <code><![CDATA[
/// {1,1,1}.Interleave( {2,2,2}, {3,3,3} ) => { 1,2,3,1,2,3,1,2,3 }
/// ]]></code>
/// This operator behaves in a deferred and streaming manner.<br/>
/// As soon as a sequence shorter than the other is detected, an exception is thrown.<br/>
/// The sequences are interleaved in the order that they appear in the <paramref name="otherSequences"/>
/// collection, with <paramref name="sequence"/> as the first sequence.
/// </remarks>
/// <typeparam name="T">The type of the elements of the source sequences</typeparam>
/// <param name="sequence">The first sequence in the interleave group</param>
/// <param name="otherSequences">The other sequences in the interleave group</param>
/// <returns>
/// A sequence of interleaved elements from all of the source sequences</returns>
/// <exception cref="InvalidOperationException">
/// The source sequences are of different lengths.</exception>

public static IEnumerable<T> EquiInterleave<T>(this IEnumerable<T> sequence, params IEnumerable<T>[] otherSequences)
{
if (sequence == null) throw new ArgumentNullException(nameof(sequence));
if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences));

return EquiInterleave(otherSequences.Prepend(sequence));
}

private static IEnumerable<T> EquiInterleave<T>(IEnumerable<IEnumerable<T>> sequences)
{
var enumerators = new List<IEnumerator<T>>();

try
{
foreach (var sequence in sequences)
{
if (sequence == null)
throw new ArgumentException("An item is null.", nameof(sequences));
enumerators.Add(sequence.GetEnumerator());
}

if (enumerators.Count == 0)
yield break;

bool MoveNext()
{
var hasNext = enumerators[0].MoveNext();
for (var i = 1; i < enumerators.Count; i++)
{
if (enumerators[i].MoveNext() != hasNext)
throw new InvalidOperationException("Input sequences are of different lengths.");
}

return hasNext;
}

while (MoveNext())
{
foreach (var enumerator in enumerators)
yield return enumerator.Current;
}
}
finally
{
foreach (var enumerator in enumerators)
enumerator.Dispose();
}
}
}
}
33 changes: 33 additions & 0 deletions MoreLinq/Extensions.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,39 @@ public static bool EndsWith<T>(this IEnumerable<T> first, IEnumerable<T> second,

}

/// <summary><c>EquiInterleave</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
public static partial class EquiInterleaveExtension
{
/// <summary>
/// Interleaves the elements of two or more sequences into a single sequence.
/// If the input sequences are of different lengths, an exception is thrown.
/// </summary>
/// <remarks>
/// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed
/// by the second, then the third, and so on. So, for example:<br/>
/// <code><![CDATA[
/// {1,1,1}.Interleave( {2,2,2}, {3,3,3} ) => { 1,2,3,1,2,3,1,2,3 }
/// ]]></code>
/// This operator behaves in a deferred and streaming manner.<br/>
/// As soon as a sequence shorter than the other is detected, an exception is thrown.<br/>
/// The sequences are interleaved in the order that they appear in the <paramref name="otherSequences"/>
/// collection, with <paramref name="sequence"/> as the first sequence.
/// </remarks>
/// <typeparam name="T">The type of the elements of the source sequences</typeparam>
/// <param name="sequence">The first sequence in the interleave group</param>
/// <param name="otherSequences">The other sequences in the interleave group</param>
/// <returns>
/// A sequence of interleaved elements from all of the source sequences</returns>
/// <exception cref="InvalidOperationException">
/// The source sequences are of different lengths.</exception>

public static IEnumerable<T> EquiInterleave<T>(this IEnumerable<T> sequence, params IEnumerable<T>[] otherSequences)
=> MoreEnumerable.EquiInterleave(sequence, otherSequences);

}

/// <summary><c>EquiZip</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
Expand Down
1 change: 1 addition & 0 deletions MoreLinq/MoreLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Consume
- DistinctBy
- EndsWith
- EquiInterleave
- EquiZip
- Exactly
- ExceptBy
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ sequence.

This method has 2 overloads.

### EquiInterleave

Interleaves the elements of two or more sequences into a single sequence.
If the input sequences are of different lengths, an exception is thrown.

### EquiZip

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