diff --git a/MoreLinq.Test/ConcatTest.cs b/MoreLinq.Test/ConcatTest.cs index 0b3dc73a4..aa6cb14bd 100644 --- a/MoreLinq.Test/ConcatTest.cs +++ b/MoreLinq.Test/ConcatTest.cs @@ -17,6 +17,8 @@ namespace MoreLinq.Test { + using System.Collections.Generic; + using System.Linq; using NUnit.Framework; [TestFixture] @@ -95,5 +97,36 @@ public void ConcatIsLazyInHeadSequence() new BreakingSequence().Concat("tail"); } #endregion + + [TestCaseSource(nameof(ContactManySource))] + public void ConcatMany(int[] head, int[] tail) + { + tail.Aggregate(head.AsEnumerable(), (xs, x) => xs.Concat(x)) + .AssertSequenceEqual(head.Concat(tail)); + } + + public static IEnumerable ContactManySource => + from x in Enumerable.Range(0, 11) + from y in Enumerable.Range(1, 20 - x) + select new + { + Head = Enumerable.Range(1, x).ToArray(), + Tail = Enumerable.Range(x + 1, y).ToArray(), + } + into e + select new TestCaseData(e.Head, + e.Tail).SetName("Head = [" + string.Join(", ", e.Head) + "], " + + "Tail = [" + string.Join(", ", e.Tail) + "]"); + + [Test] + public void ConcatWithSharedSource() + { + var first = new [] { 1 }.Concat(2); + var second = first.Concat(3).Concat(4); + var third = first.Concat(4).Concat(8); + + second.AssertSequenceEqual(1, 2, 3, 4); + third.AssertSequenceEqual(1, 2, 4, 8); + } } } diff --git a/MoreLinq.Test/PrependTest.cs b/MoreLinq.Test/PrependTest.cs index 16dd55657..4f0b695d3 100644 --- a/MoreLinq.Test/PrependTest.cs +++ b/MoreLinq.Test/PrependTest.cs @@ -17,6 +17,8 @@ namespace MoreLinq.Test { + using System.Collections.Generic; + using System.Linq; using NUnit.Framework; [TestFixture] @@ -54,5 +56,36 @@ public void PrependIsLazyInTailSequence() { new BreakingSequence().Prepend("head"); } + + [TestCaseSource(nameof(PrependManySource))] + public void PrependMany(int[] head, int[] tail) + { + head.Aggregate(tail.AsEnumerable(), (xs, x) => xs.Prepend(x)) + .AssertSequenceEqual(head.Concat(tail)); + } + + public static IEnumerable PrependManySource => + from x in Enumerable.Range(0, 11) + from y in Enumerable.Range(1, 11) + select new + { + Head = Enumerable.Range(0, y).Select(n => 0 - n).ToArray(), + Tail = Enumerable.Range(1, x).ToArray(), + } + into e + select new TestCaseData(e.Head, + e.Tail).SetName("Head = [" + string.Join(", ", e.Head) + "], " + + "Tail = [" + string.Join(", ", e.Tail) + "]"); + + [Test] + public void PrependWithSharedSource() + { + var first = new [] { 1 }.Prepend(2); + var second = first.Prepend(3).Prepend(4); + var third = first.Prepend(4).Prepend(8); + + second.AssertSequenceEqual(4, 3, 2, 1); + third.AssertSequenceEqual(8, 4, 2, 1); + } } } diff --git a/MoreLinq/Concat.cs b/MoreLinq/Concat.cs index 8e1694b52..ee4dd088f 100644 --- a/MoreLinq/Concat.cs +++ b/MoreLinq/Concat.cs @@ -50,7 +50,9 @@ public static IEnumerable Concat(this T head, IEnumerable tail) public static IEnumerable Concat(this IEnumerable head, T tail) { if (head == null) throw new ArgumentNullException(nameof(head)); - return LinqEnumerable.Concat(head, LinqEnumerable.Repeat(tail, 1)); + return head is PcNode node + ? node.Concat(tail) + : PcNode.WithSource(head).Concat(tail); } } } \ No newline at end of file diff --git a/MoreLinq/PcNode.cs b/MoreLinq/PcNode.cs new file mode 100644 index 000000000..427b84700 --- /dev/null +++ b/MoreLinq/PcNode.cs @@ -0,0 +1,125 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2017 Atif Aziz. 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; + using System.Collections.Generic; + + /// + /// Prepend-Concat node is a single linked-list of the discriminated union + /// of a prepend item, a concat item and the source. + /// + + abstract class PcNode : IEnumerable + { + public static PcNode WithSource(IEnumerable source) => new Source(source); + + public PcNode Prepend(T item) => new Item(item, isPrepend: true , next: this); + public PcNode Concat(T item) => new Item(item, isPrepend: false, next: this); + + sealed class Item : PcNode + { + public T Value { get; } + public bool IsPrepend { get; } + public int ConcatCount { get; } + public PcNode Next { get; } + + public Item(T item, bool isPrepend, PcNode next) + { + if (next == null) throw new ArgumentNullException(nameof(next)); + + Value = item; + IsPrepend = isPrepend; + ConcatCount = next is Item nextItem + ? nextItem.ConcatCount + (isPrepend ? 0 : 1) + : 1; + Next = next; + } + } + + sealed class Source : PcNode + { + public IEnumerable Value { get; } + public Source(IEnumerable source) => Value = source; + } + + public IEnumerator GetEnumerator() + { + var i = 0; + T[] concats = null; // Array for > 4 concatenations + var concat1 = default(T); // Slots for up to 4 concatenations + var concat2 = default(T); + var concat3 = default(T); + var concat4 = default(T); + + var current = this; + for (; current is Item item; current = item.Next) + { + if (item.IsPrepend) + { + yield return item.Value; + } + else + { + if (concats == null) + { + if (i == 0 && item.ConcatCount > 4) + { + concats = new T[item.ConcatCount]; + } + else + { + switch (i++) + { + case 0: concat1 = item.Value; break; + case 1: concat2 = item.Value; break; + case 2: concat3 = item.Value; break; + case 3: concat4 = item.Value; break; + default: throw new IndexOutOfRangeException(); + } + continue; + } + } + + concats[i++] = item.Value; + } + } + + var source = (Source) current; + + foreach (var item in source.Value) + yield return item; + + if (concats == null) + { + if (i == 4) { yield return concat4; i--; } + if (i == 3) { yield return concat3; i--; } + if (i == 2) { yield return concat2; i--; } + if (i == 1) { yield return concat1; i--; } + yield break; + } + + for (i--; i >= 0; i--) + yield return concats[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + } +} \ No newline at end of file diff --git a/MoreLinq/Prepend.cs b/MoreLinq/Prepend.cs index 08194b5ea..3c3dad2b8 100644 --- a/MoreLinq/Prepend.cs +++ b/MoreLinq/Prepend.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using LinqEnumerable = System.Linq.Enumerable; static partial class MoreEnumerable { @@ -45,7 +44,9 @@ static partial class MoreEnumerable public static IEnumerable Prepend(this IEnumerable source, TSource value) { if (source == null) throw new ArgumentNullException(nameof(source)); - return LinqEnumerable.Concat(LinqEnumerable.Repeat(value, 1), source); + return source is PcNode node + ? node.Prepend(value) + : PcNode.WithSource(source).Prepend(value); } } } \ No newline at end of file