From 365adcc40a94417299fc6101e361db959f04c6cd Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 11:10:22 +0100 Subject: [PATCH 1/4] Add EquiInterleave --- MoreLinq.Test/EquiInterleaveTest.cs | 147 ++++++++++++++++++++++++++++ MoreLinq/EquiInterleave.cs | 112 +++++++++++++++++++++ MoreLinq/Extensions.g.cs | 33 +++++++ README.md | 5 + 4 files changed, 297 insertions(+) create mode 100644 MoreLinq.Test/EquiInterleaveTest.cs create mode 100644 MoreLinq/EquiInterleave.cs diff --git a/MoreLinq.Test/EquiInterleaveTest.cs b/MoreLinq.Test/EquiInterleaveTest.cs new file mode 100644 index 000000000..59b5a8402 --- /dev/null +++ b/MoreLinq.Test/EquiInterleaveTest.cs @@ -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; + + /// + /// Verify the behavior of the Interleave operator + /// + [TestFixture] + public class EquiInterleaveTests + { + /// + /// Verify that EquiInterleave behaves in a lazy manner + /// + [Test] + public void TestEquiInterleaveIsLazy() + { + new BreakingSequence().EquiInterleave(new BreakingSequence()); + } + + /// + /// Verify that EquiInterleave disposes those enumerators that it managed + /// to open successfully + /// + [Test] + public void TestEquiInterleaveDisposesOnError() + { + using (var sequenceA = TestingSequence.Of()) + { + Assert.Throws(() => // Expected and thrown by BreakingSequence + sequenceA.EquiInterleave(new BreakingSequence()).Consume()); + } + } + + /// + /// Verify that two balanced sequences will EquiInterleave all of their elements + /// + [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))); + } + + /// + /// Verify that EquiInterleave with two empty sequences results in an empty sequence + /// + [Test] + public void TestEquiInterleaveTwoEmptySequences() + { + var sequenceA = Enumerable.Empty(); + var sequenceB = Enumerable.Empty(); + var result = sequenceA.EquiInterleave(sequenceB); + + Assert.That(result, Is.EqualTo(Enumerable.Empty())); + } + + /// + /// Verify that EquiInterleave throw on two unbalanced sequences + /// + [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(Code); + } + + /// + /// Verify that EquiInterleave multiple empty sequences results in an empty sequence + /// + [Test] + public void TestEquiInterleaveManyEmptySequences() + { + var sequenceA = Enumerable.Empty(); + var sequenceB = Enumerable.Empty(); + var sequenceC = Enumerable.Empty(); + var sequenceD = Enumerable.Empty(); + var sequenceE = Enumerable.Empty(); + var result = sequenceA.EquiInterleave(sequenceB, sequenceC, sequenceD, sequenceE); + + Assert.That(result, Is.Empty); + } + + /// + /// Verify that EquiInterleave throw on multiple unbalanced sequences + /// + [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(Code); + } + + /// + /// Verify that Interleave disposes of all iterators it creates. + /// + [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(); + } + } + } +} diff --git a/MoreLinq/EquiInterleave.cs b/MoreLinq/EquiInterleave.cs new file mode 100644 index 000000000..47cf386dd --- /dev/null +++ b/MoreLinq/EquiInterleave.cs @@ -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 + { + /// + /// Interleaves the elements of two or more sequences into a single sequence. + /// If the input sequences are of different lengths, an exception is thrown. + /// + /// + /// 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:
+ /// { 1,2,3,1,2,3,1,2,3 } + /// ]]> + /// This operator behaves in a deferred and streaming manner.
+ /// As soon as a sequence shorter than the other is detected, an exception is thrown.
+ /// The sequences are interleaved in the order that they appear in the + /// collection, with as the first sequence. + ///
+ /// The type of the elements of the source sequences + /// The first sequence in the interleave group + /// The other sequences in the interleave group + /// + /// A sequence of interleaved elements from all of the source sequences + /// + /// The source sequences are of different lengths. + + public static IEnumerable EquiInterleave(this IEnumerable sequence, params IEnumerable[] 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 EquiInterleave(IEnumerable> sequences) + { + var enumerators = new List>(); + + 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(this IEnumerable source) + { + var comparer = EqualityComparer.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); + } + } +} diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 0b1211058..2d6911415 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -1319,6 +1319,39 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second, } + /// EquiInterleave extension. + + [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] + public static partial class EquiInterleaveExtension + { + /// + /// Interleaves the elements of two or more sequences into a single sequence. + /// If the input sequences are of different lengths, an exception is thrown. + /// + /// + /// 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:
+ /// { 1,2,3,1,2,3,1,2,3 } + /// ]]> + /// This operator behaves in a deferred and streaming manner.
+ /// As soon as a sequence shorter than the other is detected, an exception is thrown.
+ /// The sequences are interleaved in the order that they appear in the + /// collection, with as the first sequence. + ///
+ /// The type of the elements of the source sequences + /// The first sequence in the interleave group + /// The other sequences in the interleave group + /// + /// A sequence of interleaved elements from all of the source sequences + /// + /// The source sequences are of different lengths. + + public static IEnumerable EquiInterleave(this IEnumerable sequence, params IEnumerable[] otherSequences) + => MoreEnumerable.EquiInterleave(sequence, otherSequences); + + } + /// EquiZip extension. [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] diff --git a/README.md b/README.md index 29661bedf..3cc517510 100644 --- a/README.md +++ b/README.md @@ -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 From 80a455aa8da9c99c2c67447a6e8b98e11712c056 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 13:01:54 +0100 Subject: [PATCH 2/4] Update MoreLinq/EquiInterleave.cs typo --- MoreLinq/EquiInterleave.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/EquiInterleave.cs b/MoreLinq/EquiInterleave.cs index 47cf386dd..ce4e7d5f3 100644 --- a/MoreLinq/EquiInterleave.cs +++ b/MoreLinq/EquiInterleave.cs @@ -75,7 +75,7 @@ private static IEnumerable EquiInterleave(IEnumerable> sequ var (isHomogeneous, hasNext) = enumerators.Select(e => e.MoveNext()).IsHomogeneous(); if (isHomogeneous == false) - throw new InvalidOperationException("Input sequences are of different length."); + throw new InvalidOperationException("Input sequences are of different lengths."); if (!hasNext) break; From c7af39fb898393c3789c69dab0348ca4be067711 Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 13:24:07 +0100 Subject: [PATCH 3/4] Update MoreLinq.csproj Add EquiInterleave in project description --- MoreLinq/MoreLinq.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index dbe55aa50..9f918388c 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -28,6 +28,7 @@ - Consume - DistinctBy - EndsWith + - EquiInterleave - EquiZip - Exactly - ExceptBy From 1693011a5f554f4b4b898449f420b681975b9c95 Mon Sep 17 00:00:00 2001 From: Orace Date: Fri, 15 Nov 2019 11:20:17 +0100 Subject: [PATCH 4/4] Removed IsHomogeneous and added MoveNext local method. --- MoreLinq/EquiInterleave.cs | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/MoreLinq/EquiInterleave.cs b/MoreLinq/EquiInterleave.cs index ce4e7d5f3..99f4999b4 100644 --- a/MoreLinq/EquiInterleave.cs +++ b/MoreLinq/EquiInterleave.cs @@ -70,16 +70,20 @@ private static IEnumerable EquiInterleave(IEnumerable> sequ if (enumerators.Count == 0) yield break; - for (;;) + bool MoveNext() { - var (isHomogeneous, hasNext) = enumerators.Select(e => e.MoveNext()).IsHomogeneous(); + 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."); + } - if (isHomogeneous == false) - throw new InvalidOperationException("Input sequences are of different lengths."); - - if (!hasNext) - break; + return hasNext; + } + while (MoveNext()) + { foreach (var enumerator in enumerators) yield return enumerator.Current; } @@ -90,23 +94,5 @@ private static IEnumerable EquiInterleave(IEnumerable> sequ enumerator.Dispose(); } } - - private static (bool? isHomogeneous, T value) IsHomogeneous(this IEnumerable source) - { - var comparer = EqualityComparer.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); - } } }