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

Flatten consecutive chaining of Concat & Prepend #319

Merged
merged 11 commits into from
Jun 30, 2017
33 changes: 33 additions & 0 deletions MoreLinq.Test/ConcatTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace MoreLinq.Test
{
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

[TestFixture]
Expand Down Expand Up @@ -95,5 +97,36 @@ public void ConcatIsLazyInHeadSequence()
new BreakingSequence<string>().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<object> 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);
}
}
}
33 changes: 33 additions & 0 deletions MoreLinq.Test/PrependTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace MoreLinq.Test
{
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

[TestFixture]
Expand Down Expand Up @@ -54,5 +56,36 @@ public void PrependIsLazyInTailSequence()
{
new BreakingSequence<string>().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<object> 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);
}
}
}
4 changes: 3 additions & 1 deletion MoreLinq/Concat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public static IEnumerable<T> Concat<T>(this T head, IEnumerable<T> tail)
public static IEnumerable<T> Concat<T>(this IEnumerable<T> head, T tail)
{
if (head == null) throw new ArgumentNullException(nameof(head));
return LinqEnumerable.Concat(head, LinqEnumerable.Repeat(tail, 1));
return head is PcNode<T> node
? node.Concat(tail)
: PcNode<T>.WithSource(head).Concat(tail);
}
}
}
125 changes: 125 additions & 0 deletions MoreLinq/PcNode.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Prepend-Concat node is a single linked-list of the discriminated union
/// of a prepend item, a concat item and the source.
/// </summary>

abstract class PcNode<T> : IEnumerable<T>
{
public static PcNode<T> WithSource(IEnumerable<T> source) => new Source(source);

public PcNode<T> Prepend(T item) => new Item(item, isPrepend: true , next: this);
public PcNode<T> Concat(T item) => new Item(item, isPrepend: false, next: this);

sealed class Item : PcNode<T>
{
public T Value { get; }
public bool IsPrepend { get; }
public int ConcatCount { get; }
public PcNode<T> Next { get; }

public Item(T item, bool isPrepend, PcNode<T> 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<T>
{
public IEnumerable<T> Value { get; }
public Source(IEnumerable<T> source) => Value = source;
}

public IEnumerator<T> 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();

}
}
5 changes: 3 additions & 2 deletions MoreLinq/Prepend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ namespace MoreLinq
{
using System;
using System.Collections.Generic;
using LinqEnumerable = System.Linq.Enumerable;

static partial class MoreEnumerable
{
Expand All @@ -45,7 +44,9 @@ static partial class MoreEnumerable
public static IEnumerable<TSource> Prepend<TSource>(this IEnumerable<TSource> source, TSource value)
{
if (source == null) throw new ArgumentNullException(nameof(source));
return LinqEnumerable.Concat(LinqEnumerable.Repeat(value, 1), source);
return source is PcNode<TSource> node
? node.Prepend(value)
: PcNode<TSource>.WithSource(source).Prepend(value);
}
}
}