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

SplitAt implementation #316

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
109 changes: 109 additions & 0 deletions MoreLinq.Test/SplitAtTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2008 Jonathan Skeet. 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 System.Collections.Generic;
using NUnit.Framework;

[TestFixture]
public class SplitAtTest
{
[Test]
public void SplitAtIsLazy2()
{
new BreakingSequence<object>().SplitAt();
}

[TestCase( 0, new int[0] , new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })]
[TestCase( 2, new[] { 1, 2 }, new[] { 3, 4, 5, 6, 7, 8, 9, 10 })]
[TestCase( 5, new[] { 1, 2, 3, 4, 5 }, new[] { 6, 7, 8, 9, 10 })]
[TestCase(10, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, new int[0] )]
[TestCase(20, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, new int[0] )]
[TestCase(-5, new int[0] , new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })]
public void SplitAt(int index, int[] expected1, int[] expected2)
{
var ns = Enumerable.Range(1, 10).ToArray();

AssertParts(ns.AsTestingList() , input => input.SplitAt(index).Fold((xs, ys) => (xs, ys)));
AssertParts(ns.AsTestingSequence(), input => input.SplitAt(index).Fold((xs, ys) => (xs, ys)));
AssertParts(ns.AsTestingList() , input => input.SplitAt(index).Fold((xs, ys) => (xs, ys)));
AssertParts(ns.AsTestingSequence(), input => input.SplitAt(index).Fold((xs, ys) => (xs, ys)));

void AssertParts<T>(T input, Func<IEnumerable<int>, (IEnumerable<int>, IEnumerable<int>)> splitter)
where T : IEnumerable<int>, IDisposable
{
using (input)
{
var (part1, part2) = splitter(input);
part1.AssertSequenceEqual(expected1);
part2.AssertSequenceEqual(expected2);
}
}
}

[Ignore("TODO")]
[TestCase( 0)]
[TestCase(-1)]
public void SplitAtWithIndexZeroOrLessReturnsSourceAsSecond(int index)
{
Assert.That(index, Is.LessThanOrEqualTo(0));

var ns = Enumerable.Range(1, 10).ToArray();

AssertParts(ns.AsTestingList(),
input => input.SplitAt(index)
.Fold((xs, ys) => (xs, ys)));

void AssertParts<T>(T input, Func<IEnumerable<int>, (IEnumerable<int>, IEnumerable<int>)> splitter)
where T : IEnumerable<int>, IDisposable
{
using (input)
{
var (part1, part2) = splitter(input);
Assert.That(part1, Is.Empty);
Assert.That(part2, Is.SameAs(input));
}
}
}

[Ignore("TODO")]
[TestCase(10)]
[TestCase(11)]
[TestCase(20)]
public void SplitAtWithIndexGreaterOrEqualToSourceLengthReturnsSourceAsFirst(int index)
{
var ns = Enumerable.Range(1, 10).ToArray();

AssertParts(ns.AsTestingList(),
input => input.SplitAt(index)
.Fold((xs, ys) => (xs, ys)));

void AssertParts<T>(T input, Func<IEnumerable<int>, (IEnumerable<int>, IEnumerable<int>)> splitter)
where T : IEnumerable<int>, IDisposable
{
using (input)
{
var (part1, part2) = splitter(input);
Assert.That(part1, Is.SameAs(input));
Assert.That(part2, Is.Empty);
}
}
}
}
}
85 changes: 85 additions & 0 deletions MoreLinq.Test/TestingList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#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.Test
{
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;

static class TestingList
{
public static TestingList<T> Of<T>(params T[] elements) =>
new TestingList<T>(elements);

public static TestingList<T> AsTestingList<T>(this IList<T> source) =>
source != null
? new TestingList<T>(source)
: throw new ArgumentNullException(nameof(source));

}

/// <summary>
/// List that asserts whether its iterator has been disposed
/// when it is disposed itself and also whether GetEnumerator() is
/// called exactly once or not.
/// </summary>

sealed class TestingList<T> : IList<T>, IDisposable
{
readonly IList<T> _list;
bool? _disposed;

public TestingList(IList<T> list) => _list = list;

void IDisposable.Dispose()
{
if (_disposed == null)
return;
Assert.That(_disposed, Is.True, "Expected enumerator to be disposed.");
_disposed = null;
}

public IEnumerator<T> GetEnumerator()
{
Assert.That(_disposed, Is.Null, "LINQ operators should not enumerate a sequence more than once.");
_disposed = false;
var e = new DisposeNotificationEnumerator<T>(_list.GetEnumerator());
e.Disposed += delegate { _disposed = true; };
return e;
}

public int Count => _list.Count;
public T this[int index] { get => _list[index]; set => _list[index] = value; }

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public bool IsReadOnly => _list.IsReadOnly;
public int IndexOf(T item) => _list.IndexOf(item);

public void Add(T item) => throw UnexpectedBehaviorError();
public void Clear() => throw UnexpectedBehaviorError();
public bool Remove(T item) => throw UnexpectedBehaviorError();
public void Insert(int index, T item) => throw UnexpectedBehaviorError();
public void RemoveAt(int index) => throw UnexpectedBehaviorError();

static Exception UnexpectedBehaviorError() => new Exception("LINQ operators should not modify the source.");
}
}
43 changes: 22 additions & 21 deletions MoreLinq.Test/TestingSequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void AssertDisposed()
public IEnumerator<T> GetEnumerator()
{
Assert.That(_sequence, Is.Not.Null, "LINQ operators should not enumerate a sequence more than once.");
var enumerator = new DisposeTestingSequenceEnumerator(_sequence.GetEnumerator());
var enumerator = new DisposeNotificationEnumerator<T>(_sequence.GetEnumerator());
_disposed = false;
enumerator.Disposed += delegate { _disposed = true; };
enumerator.MoveNextCalled += delegate { MoveNextCallCount++; };
Expand All @@ -75,31 +75,32 @@ public IEnumerator<T> GetEnumerator()

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

sealed class DisposeTestingSequenceEnumerator : IEnumerator<T>
{
readonly IEnumerator<T> _sequence;
}

sealed class DisposeNotificationEnumerator<T> : IEnumerator<T>
{
readonly IEnumerator<T> _sequence;

public event EventHandler Disposed;
public event EventHandler MoveNextCalled;
public event EventHandler Disposed;
public event EventHandler MoveNextCalled;

public DisposeTestingSequenceEnumerator(IEnumerator<T> sequence) =>
_sequence = sequence;
public DisposeNotificationEnumerator(IEnumerator<T> sequence) =>
_sequence = sequence;

public T Current => _sequence.Current;
object IEnumerator.Current => Current;
public void Reset() => _sequence.Reset();
public T Current => _sequence.Current;
object IEnumerator.Current => Current;
public void Reset() => _sequence.Reset();

public bool MoveNext()
{
MoveNextCalled?.Invoke(this, EventArgs.Empty);
return _sequence.MoveNext();
}
public bool MoveNext()
{
MoveNextCalled?.Invoke(this, EventArgs.Empty);
return _sequence.MoveNext();
}

public void Dispose()
{
_sequence.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
_sequence.Dispose();
Disposed?.Invoke(this, EventArgs.Empty);
}
}
}
3 changes: 2 additions & 1 deletion MoreLinq/MoreLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
- Slice
- SortedMerge
- Split
- SplitAt
- StartsWith
- Subsets
- TagFirstLast
Expand Down Expand Up @@ -118,7 +119,7 @@
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageId>morelinq</PackageId>
<PackageTags>linq;extensions</PackageTags>
<PackageReleaseNotes>Adds new operators: Flatten. See also https://github.com/morelinq/MoreLINQ/wiki/API-Changes.</PackageReleaseNotes>
<PackageReleaseNotes>Adds new operators: SplitAt. See also https://github.com/morelinq/MoreLINQ/wiki/API-Changes.</PackageReleaseNotes>
<PackageProjectUrl>https://morelinq.github.io/</PackageProjectUrl>
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
Expand Down
84 changes: 84 additions & 0 deletions MoreLinq/SplitAt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2009 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.Generic;
using System.Linq;

static partial class MoreEnumerable
{
/// <summary>
/// Splits the sequence into sub-sequences at given offsets into the
/// sequence.
/// </summary>
/// <param name="source">The source sequence.</param>
/// <param name="offsets">
/// The zero-based offsets into the source sequence at which at which
/// to split the source sequence.</param>
/// <typeparam name="T">
/// The type of the element in the source sequence.</typeparam>
/// <returns>
/// A sequence of splits.</returns>
/// <remarks>
/// This method uses deferred execution semantics and streams the
/// sub-sequences, where each sub-sequence is buffered.
/// </remarks>

public static IEnumerable<IEnumerable<T>> SplitAt<T>(
this IEnumerable<T> source, params int[] offsets)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (offsets == null) throw new ArgumentNullException(nameof(offsets));

return _(); IEnumerable<IEnumerable<T>> _()
{
using (var oe = offsets.Concat(int.MaxValue).GetEnumerator())
{
oe.MoveNext();
var offset = oe.Current;

List<T> list = null;
foreach (var e in source.Index())
{
retry:
if (e.Key < offset)
{
if (list == null)
list = new List<T>();
list.Add(e.Value);
}
else
{
yield return list ?? Enumerable.Empty<T>();
offset = oe.MoveNext() ? oe.Current : -1;
list = null;
goto retry;
}
}

if (list != null)
yield return list;

while (oe.MoveNext())
yield return Enumerable.Empty<T>();
}
}
}
}
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,12 @@ Splits the source sequence by a separator.

This method has 12 overloads.

### SplitAt

Splits the sequence in two at the given index.

This method has 2 overloads.

### StartsWith

Determines whether the beginning of the first sequence is equivalent to the
Expand Down