Skip to content

Commit

Permalink
Flatten consecutive chaining of Concat & Prepend
Browse files Browse the repository at this point in the history
  • Loading branch information
atifaziz authored Jun 30, 2017
1 parent a986e77 commit 7d09ea3
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 3 deletions.
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);
}
}
}

0 comments on commit 7d09ea3

Please sign in to comment.