Skip to content

Commit

Permalink
Added "SplitInBuckets" for collections
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGerr committed Feb 4, 2021
1 parent 032dda1 commit 9702c3d
Show file tree
Hide file tree
Showing 8 changed files with 459 additions and 33 deletions.
7 changes: 0 additions & 7 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA1303</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\..\shared\SkipLocalsInit.cs">
<Link>SkipLocalsInit.cs</Link>
</Compile>
</ItemGroup>

</Project>
57 changes: 31 additions & 26 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>
<ParentPropsFile>$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))</ParentPropsFile>
</PropertyGroup>

<Import Condition=" exists('$(ParentPropsFile)') " Project="$(ParentPropsFile)"/>

<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IsPackable>True</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildingByReSharper)' != 'true' AND '$(BuildingInsideVisualStudio)' != 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<None Include="./../../icon.png" Pack="true" PackagePath=""/>
<None Include="./../../LICENSE.md" Pack="true" PackagePath="$(PackageLicenseFile)"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All"/>
</ItemGroup>
<PropertyGroup>
<ParentPropsFile>$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))</ParentPropsFile>
</PropertyGroup>

<Import Condition=" exists('$(ParentPropsFile)') " Project="$(ParentPropsFile)" />

<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IsPackable>True</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildingByReSharper)' != 'true' AND '$(BuildingInsideVisualStudio)' != 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<None Include="./../../icon.png" Pack="true" PackagePath="" />
<None Include="./../../LICENSE.md" Pack="true" PackagePath="$(PackageLicenseFile)" />

<Compile Include="..\..\shared\SkipLocalsInit.cs">
<Link>SkipLocalsInit.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,40 @@ public static IReadOnlyCollection<T> ToReadOnlyCollection<T>(this IEnumerable<T>
{
return new EnumerableWrapperWithCount<T>(items, count);
}

/// <summary>
/// Splits the <paramref name="collection"/> in buckets of provided <paramref name="bucketSize"/>.
/// </summary>
/// <param name="collection">Collection to split in buckets.</param>
/// <param name="bucketSize">The size of the buckets.</param>
/// <typeparam name="T">Type of the item.</typeparam>
/// <returns>An collection of buckets.</returns>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bucketSize"/> is less than 1.</exception>
public static IEnumerable<IEnumerable<T>> SplitInBuckets<T>(
this IEnumerable<T> collection,
int bucketSize)
{
if (collection is null)
throw new ArgumentNullException(nameof(collection));
if (bucketSize < 1)
throw new ArgumentOutOfRangeException(nameof(bucketSize), "Bucket size cannot be less than 1.");

var bucket = new List<T>();

foreach (var item in collection)
{
bucket.Add(item);

if (bucket.Count == bucketSize)
{
yield return bucket;
bucket = new List<T>();
}
}

if (bucket.Count != 0)
yield return bucket;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;

// ReSharper disable once CheckNamespace
namespace Thinktecture
{
/// <summary>
/// Extensions for <see cref="IReadOnlyCollection{T}"/>.
/// </summary>
public static class ReadOnlyCollectionExtensions
{
/// <summary>
/// Splits the <paramref name="collection"/> in buckets of provided <paramref name="bucketSize"/>.
/// </summary>
/// <param name="collection">Collection to split in buckets.</param>
/// <param name="bucketSize">The size of the buckets.</param>
/// <typeparam name="T">Type of the item.</typeparam>
/// <returns>An collection of buckets.</returns>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bucketSize"/> is less than 1.</exception>
public static IEnumerable<IReadOnlyCollection<T>> SplitInBuckets<T>(
this IReadOnlyCollection<T> collection,
int bucketSize)
{
if (collection is null)
throw new ArgumentNullException(nameof(collection));
if (bucketSize < 1)
throw new ArgumentOutOfRangeException(nameof(bucketSize), "Bucket size cannot be less than 1.");

if (collection.Count == 0)
yield break;

if (collection.Count <= bucketSize)
{
yield return collection;
yield break;
}

var bucketCount = collection.Count / bucketSize;

using var enumerator = collection.GetEnumerator();

for (var i = 0; i < bucketCount; i++)
{
var bucket = new T[bucketSize];

for (var j = 0; j < bucketSize; j++)
{
enumerator.MoveNext();
bucket[j] = enumerator.Current;
}

yield return bucket;
}

var lastBucketSize = collection.Count % bucketSize;

if (lastBucketSize != 0)
{
var bucket = new T[lastBucketSize];

for (var j = 0; j < lastBucketSize; j++)
{
enumerator.MoveNext();
bucket[j] = enumerator.Current;
}

yield return bucket;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;

// ReSharper disable once CheckNamespace
namespace Thinktecture
{
/// <summary>
/// Extensions for <see cref="IReadOnlyList{T}"/>
/// </summary>
public static class ReadOnlyListExtensions
{
/// <summary>
/// Splits the <paramref name="collection"/> in buckets of provided <paramref name="bucketSize"/>.
/// </summary>
/// <param name="collection">Collection to split in buckets.</param>
/// <param name="bucketSize">The size of the buckets.</param>
/// <typeparam name="T">Type of the item.</typeparam>
/// <returns>An collection of buckets.</returns>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bucketSize"/> is less than 1.</exception>
public static IEnumerable<IReadOnlyList<T>> SplitInBuckets<T>(
this IReadOnlyList<T> collection,
int bucketSize)
{
if (collection is null)
throw new ArgumentNullException(nameof(collection));
if (bucketSize < 1)
throw new ArgumentOutOfRangeException(nameof(bucketSize), "Bucket size cannot be less than 1.");

if (collection.Count == 0)
yield break;

if (collection.Count <= bucketSize)
{
yield return collection;
yield break;
}

var bucketCount = collection.Count / bucketSize;

for (var i = 0; i < bucketCount; i++)
{
var collectionIndex = i * bucketSize;
var bucket = new T[bucketSize];

for (var j = 0; j < bucketSize; j++, collectionIndex++)
{
bucket[j] = collection[collectionIndex];
}

yield return bucket;
}

var lastBucketSize = collection.Count % bucketSize;

if (lastBucketSize != 0)
{
var collectionIndex = bucketCount * bucketSize;
var bucket = new T[lastBucketSize];

for (var j = 0; j < lastBucketSize; j++, collectionIndex++)
{
bucket[j] = collection[collectionIndex];
}

yield return bucket;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Xunit;

namespace Thinktecture.Runtime.Tests.Extensions.EnumerableExtensionsTests
{
public class SplitInBuckets
{
[Fact]
public void Should_throw_if_collection_is_null()
{
((IEnumerable<int>)null).Invoking(c => c.SplitInBuckets(5).ToList())
.Should().Throw<ArgumentNullException>()
.WithMessage("Value cannot be null. (Parameter 'collection')");
}

[Fact]
public void Should_throw_if_bucke_size_is_0()
{
Collection<int>().Invoking(c => c.SplitInBuckets(0).ToList())
.Should().Throw<ArgumentOutOfRangeException>()
.WithMessage("Bucket size cannot be less than 1. (Parameter 'bucketSize')");
}

[Fact]
public void Should_return_empty_enumerable_if_collection_is_empty()
{
Collection<int>().SplitInBuckets(5)
.Should().BeEmpty();
}

[Fact]
public void Should_return_enumerable_with_1_item_if_collection_size_is_smaller_than_bucket_size()
{
var buckets = Collection(1, 2, 3).SplitInBuckets(5).ToList();
buckets.Should().HaveCount(1);
buckets[0].Should().HaveCount(3).And.ContainInOrder(new[] { 1, 2, 3 });
}

[Fact]
public void Should_return_enumerable_with_1_item_if_collection_size_equals_the_bucket_size()
{
var buckets = Collection(1, 2, 3, 4, 5).SplitInBuckets(5).ToList();
buckets.Should().HaveCount(1);
buckets[0].Should().HaveCount(5).And.ContainInOrder(new[] { 1, 2, 3, 4, 5 });
}

[Fact]
public void Should_return_enumerable_with_2_items_if_collection_size_slightly_bigger_than_bucket_size()
{
var buckets = Collection(1, 2, 3, 4, 5, 6).SplitInBuckets(5).ToList();
buckets.Should().HaveCount(2);
buckets[0].Should().HaveCount(5).And.ContainInOrder(new[] { 1, 2, 3, 4, 5 });
buckets[1].Should().HaveCount(1).And.ContainInOrder(new[] { 6 });
}

[Fact]
public void Should_return_enumerable_with_2_items_if_collection_size_twice_as_big_as_bucket_size()
{
var buckets = Collection(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).SplitInBuckets(5).ToList();
buckets.Should().HaveCount(2);
buckets[0].Should().HaveCount(5).And.ContainInOrder(new[] { 1, 2, 3, 4, 5 });
buckets[1].Should().HaveCount(5).And.ContainInOrder(new[] { 6, 7, 8, 9, 10 });
}

[Fact]
public void Should_return_enumerable_with_3_items_if_collection_size_more_than_twice_as_big_as_bucket_size()
{
var buckets = Collection(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).SplitInBuckets(5).ToList();
buckets.Should().HaveCount(3);
buckets[0].Should().HaveCount(5).And.ContainInOrder(new[] { 1, 2, 3, 4, 5 });
buckets[1].Should().HaveCount(5).And.ContainInOrder(new[] { 6, 7, 8, 9, 10 });
buckets[2].Should().HaveCount(2).And.ContainInOrder(new[] { 12 });
}

private static IEnumerable<T> Collection<T>(params T[] values)
{
return values;
}
}
}
Loading

0 comments on commit 9702c3d

Please sign in to comment.