Skip to content

Commit

Permalink
Add EquiInterleave
Browse files Browse the repository at this point in the history
  • Loading branch information
Orace committed Nov 14, 2019
1 parent 13c8c83 commit 365adcc
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 0 deletions.
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();
}
}
}
}
112 changes: 112 additions & 0 deletions MoreLinq/EquiInterleave.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#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;

for (;;)
{
var (isHomogeneous, hasNext) = enumerators.Select(e => e.MoveNext()).IsHomogeneous();

if (isHomogeneous == false)
throw new InvalidOperationException("Input sequences are of different length.");

if (!hasNext)
break;

foreach (var enumerator in enumerators)
yield return enumerator.Current;
}
}
finally
{
foreach (var enumerator in enumerators)
enumerator.Dispose();
}
}

private static (bool? isHomogeneous, T value) IsHomogeneous<T>(this IEnumerable<T> source)
{
var comparer = EqualityComparer<T>.Default;
using var e = source.GetEnumerator();

if (!e.MoveNext())
return (null, default);

var first = e.Current;
while (e.MoveNext())
{
if (!comparer.Equals(first, e.Current))
return (false, default);
}

return (true, first);
}
}
}
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
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

0 comments on commit 365adcc

Please sign in to comment.