Skip to content

Commit

Permalink
Update chunkby implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
inputfalken committed Sep 23, 2018
1 parent 4c9eaeb commit 6581fe6
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 73 deletions.
51 changes: 51 additions & 0 deletions src/Lemonad.Enumerable/ChunkBy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;

namespace Lemonad.Enumerable {
public static partial class Enumerable {
/// <summary>
/// Chunks the elements of a sequence according to a amount and creates a result value from each group and its max capacity.
/// </summary>
/// <param name="source">An <see cref="IEnumerable{T}"/> whose elements to chunk.</param>
/// <param name="size">The max capacity for each chunk.</param>
/// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam>
/// <returns>An <see cref="IEnumerable{T}"/> with chunks.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="source"/> is null.</exception>
/// <exception cref="ArgumentException">When <paramref name="size"/> is below 1.</exception>
public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int size) {
if (source == null) {
throw new ArgumentNullException(nameof(source));
}

if (size < 1) {
throw new ArgumentException($"Parameter: '{nameof(size)}' ({size}) can not be below 1.");
}

return CreateChunks(source, size);
}

private static IEnumerable<IEnumerable<TSource>> CreateChunks<TSource>(IEnumerable<TSource> source, int size) {
var index = 0;
var elements = new TSource[size];
var count = 0;

foreach (var element in source) {
elements[count] = element;
checked {
index++;
}

count = index % size;
if (count == 0) {
yield return elements;
elements = new TSource[size];
}
}

// Avoids returning empty chunks.
if (count > 0) {
yield return new ArraySegment<TSource>(elements, 0, count);
}
}
}
}
63 changes: 20 additions & 43 deletions src/Lemonad.Enumerable/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,74 +1,51 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Lemonad.Enumerable {
public static partial class Enumerable {
/// <summary>
/// Chunks the elements of a sequence according to a amount and creates a result value from each group and its max capacity.
/// </summary>
/// <param name="source">An <see cref="IEnumerable{T}"/> whose elements to chunk.</param>
/// <param name="count">The max capacity for each <see cref="IChunk{T}"/>.</param>
/// <param name="size">The max capacity for each chunk.</param>
/// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam>
/// <returns>The type of the elements <paramref name="source"/>.</returns>
/// <returns>An <see cref="IEnumerable{T}"/> with chunks.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="source"/> is null.</exception>
/// <exception cref="ArgumentException">When <paramref name="count"/> is below 1.</exception>
public static IEnumerable<IChunk<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int count) {
/// <exception cref="ArgumentException">When <paramref name="size"/> is below 1.</exception>
public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource>(this IEnumerable<TSource> source, int size) {
if (source == null) {
throw new ArgumentNullException(nameof(source));
}

if (count < 1) {
throw new ArgumentException($"Parameter: '{nameof(count)}' ({count}) can not be below 1.");
if (size < 1) {
throw new ArgumentException($"Parameter: '{nameof(size)}' ({size}) can not be below 1.");
}

foreach (IEnumerable<TSource> enumerable in CreateChunks(source, count)) {
yield return new Chunk<TSource>(enumerable, count);
}
return CreateChunks(source, size);
}

private static IEnumerable<IEnumerable<TSource>> CreateChunks<TSource>(IEnumerable<TSource> source, int count) {
private static IEnumerable<IEnumerable<TSource>> CreateChunks<TSource>(IEnumerable<TSource> source, int size) {
var index = 0;
var elements = new Queue<TSource>();
var elements = new TSource[size];
var count = 0;

foreach (var element in source) {
elements.Enqueue(element);
elements[count] = element;
checked {
index++;
}
if (index % count == 0) {

count = index % size;
if (count == 0) {
yield return elements;
elements = new Queue<TSource>();
elements = new TSource[size];
}
}

yield return elements;
}
}

public sealed class Chunk<T> : IChunk<T> {
private readonly IEnumerable<T> _chunk;

public Chunk(IEnumerable<T> chunk, int count) {
_chunk = chunk;
MaxCount = count;
// Avoids returning empty chunks.
if (count > 0) {
yield return new ArraySegment<TSource>(elements, 0, count);
}
}

public IEnumerator<T> GetEnumerator() => _chunk.GetEnumerator();

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

/// <summary>
/// The max amount of allowed elements in <see cref="IChunk{T}"/>.
/// </summary>
public int MaxCount { get; }
}

/// <summary>
/// Represents a collection of elements that have a common max count.
/// </summary>
/// <typeparam name="T">The type of the elements.</typeparam>
public interface IChunk<out T> : IEnumerable<T> {
int MaxCount { get; }
}
}
84 changes: 54 additions & 30 deletions test/Lemonad.Enumerable.Tests/ChunkByTests.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace Lemonad.Enumerable.Tests {
public class ChunkByTests {
public class ChunkBy {
[Fact]
public void Chunk_Enumerable_Of_10_Into_Four_Chunks() {
public void Transform_Enumerable_Of_10_Into_Chunks_With_Size_3() {
const int chunkSize = 3;
var chunks = System.Linq.Enumerable.Range(0, 10).ChunkBy(chunkSize).Select(x => x).ToArray();
// Verify all chunks have equal max size.
Assert.All(chunks, x => Assert.Equal(chunkSize, x.MaxCount));
var arr = chunks.Select(x => x.ToArray()).ToArray();

Assert.Equal(4, arr.Length);

// First chunk
Assert.Equal(0, arr[0][0]);
Assert.Equal(1, arr[0][1]);
Assert.Equal(2, arr[0][2]);

// Second chunk
Assert.Equal(3, arr[1][0]);
Assert.Equal(4, arr[1][1]);
Assert.Equal(5, arr[1][2]);

// Third chunk
Assert.Equal(6, arr[2][0]);
Assert.Equal(7, arr[2][1]);
Assert.Equal(8, arr[2][2]);

// Fourth chunk
Assert.Equal(9, arr[3][0]);
var arr = System.Linq.Enumerable.Range(0, 10).ChunkBy(chunkSize);

IEnumerable<IEnumerable<int>> expected = new[] {
new[] {0, 1, 2},
new[] {3, 4, 5},
new[] {6, 7, 8},
new[] {9},
};
Assert.Equal(expected, arr);
}

[Fact]
public void Transform_Enumerable_Of_10_Into_Chunks_With_Size_10() {
const int chunkSize = 10;
var arr = System.Linq.Enumerable.Range(0, 100).ChunkBy(chunkSize);

IEnumerable<int>[] expected = {
System.Linq.Enumerable.Range(0, 10),
System.Linq.Enumerable.Range(10, 10),
System.Linq.Enumerable.Range(20, 10),
System.Linq.Enumerable.Range(30, 10),
System.Linq.Enumerable.Range(40, 10),
System.Linq.Enumerable.Range(50, 10),
System.Linq.Enumerable.Range(60, 10),
System.Linq.Enumerable.Range(70, 10),
System.Linq.Enumerable.Range(80, 10),
System.Linq.Enumerable.Range(90, 10),
};
Assert.Equal(expected, arr);
}

[Fact]
public void Transform_Enumerable_Of_100_Into_Chunks_With_Size_Eleven() {
const int chunkSize = 11;
var arr = System.Linq.Enumerable.Range(0, 100).ChunkBy(chunkSize);

IEnumerable<int>[] expected = {
System.Linq.Enumerable.Range(0, 11),
System.Linq.Enumerable.Range(11, 11),
System.Linq.Enumerable.Range(22, 11),
System.Linq.Enumerable.Range(33, 11),
System.Linq.Enumerable.Range(44, 11),
System.Linq.Enumerable.Range(55, 11),
System.Linq.Enumerable.Range(66, 11),
System.Linq.Enumerable.Range(77, 11),
System.Linq.Enumerable.Range(88, 11),
System.Linq.Enumerable.Range(99, 1),
};
Assert.Equal(expected, arr);
}

[Fact]
public void Chunk_Enumerable_Of_10_With_Negative_Integer_Throws() => Assert.Throws<ArgumentException>(
() => System.Linq.Enumerable.Range(0, 10).ChunkBy(-1).Select(x => x.ToArray()).ToArray());
public void Supplying_Negative_Size_Throws_ArgumentException() => Assert.Throws<ArgumentException>(
() => System.Linq.Enumerable.Range(0, 10).ChunkBy(-1));

[Fact]
public void Chunk_Null_Enumerable_Throws() {
public void Supplying_Null_Enumerable_Throws_ArgumentNullException() {
IEnumerable<int> enumerable = null;
Assert.Throws<ArgumentNullException>("source", () => enumerable.ChunkBy(4));
}
Expand Down

0 comments on commit 6581fe6

Please sign in to comment.