Skip to content

Commit f69a251

Browse files
sglienkevincentparrett
authored andcommitted
implemented TEnumerable.Chunk
1 parent 317758e commit f69a251

File tree

5 files changed

+270
-2
lines changed

5 files changed

+270
-2
lines changed

Source/Base/Collections/Spring.Collections.Extensions.pas

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,20 @@ TRepeatIterator<T> = class(TIterator<T>, IEnumerable<T>)
832832
constructor Create(const element: T; count: Integer);
833833
end;
834834

835+
TChunkIterator<T> = class(TIterator<TArray<T>>, IEnumerable<TArray<T>>)
836+
private
837+
fSource: IEnumerable<T>;
838+
fEnumerator: IEnumerator<T>;
839+
fSize, fCapacity: Integer;
840+
protected
841+
function Clone: TIterator<TArray<T>>; override;
842+
procedure Dispose; override;
843+
procedure Start; override;
844+
function TryMoveNext(var current: TArray<T>): Boolean; override;
845+
public
846+
constructor Create(const source: IEnumerable<T>; size: Integer);
847+
end;
848+
835849
TAnonymousIterator<T> = class(TIterator<T>, IEnumerable<T>)
836850
private
837851
fCount: Func<Integer>;
@@ -3275,6 +3289,74 @@ function TRepeatIterator<T>.TryMoveNext(var current: T): Boolean;
32753289
{$ENDREGION}
32763290

32773291

3292+
{$REGION 'TChunkIterator<T>'}
3293+
3294+
constructor TChunkIterator<T>.Create(const source: IEnumerable<T>; size: Integer);
3295+
begin
3296+
if not Assigned(source) then RaiseHelper.ArgumentNil(ExceptionArgument.source);
3297+
if size <= 0 then RaiseHelper.ArgumentOutOfRange(ExceptionArgument.size);
3298+
3299+
fSource := source;
3300+
fSize := size;
3301+
fCapacity := 4;
3302+
if fCapacity > fSize then
3303+
fCapacity := fSize;
3304+
end;
3305+
3306+
3307+
function TChunkIterator<T>.Clone: TIterator<TArray<T>>;
3308+
begin
3309+
Result := TChunkIterator<T>.Create(fSource, fSize);
3310+
end;
3311+
3312+
procedure TChunkIterator<T>.Dispose;
3313+
begin
3314+
fEnumerator := nil;
3315+
end;
3316+
3317+
procedure TChunkIterator<T>.Start;
3318+
begin
3319+
fEnumerator := fSource.GetEnumerator;
3320+
end;
3321+
3322+
function TChunkIterator<T>.TryMoveNext(var current: TArray<T>): Boolean;
3323+
var
3324+
i: Integer;
3325+
begin
3326+
if Assigned(fEnumerator) and fEnumerator.MoveNext then
3327+
begin
3328+
SetLength(current, fCapacity);
3329+
3330+
i := 0;
3331+
repeat
3332+
current[i] := fEnumerator.Current;
3333+
Inc(i);
3334+
if i >= fSize then Break
3335+
else if fEnumerator.MoveNext then
3336+
begin
3337+
if i >= fCapacity then
3338+
begin
3339+
fCapacity := GrowCapacity(fCapacity);
3340+
if fCapacity > fSize then
3341+
fCapacity := fSize;
3342+
SetLength(current, fCapacity);
3343+
end;
3344+
end
3345+
else
3346+
begin
3347+
SetLength(current, i);
3348+
Break;
3349+
end;
3350+
until False;
3351+
Result := True;
3352+
end
3353+
else
3354+
Result := False;
3355+
end;
3356+
3357+
{$ENDREGION}
3358+
3359+
32783360
{$REGION 'TAnonymousIterator<T>'}
32793361

32803362
constructor TAnonymousIterator<T>.Create(const count: Func<Integer>; //FI:W525

Source/Base/Collections/Spring.Collections.pas

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3469,6 +3469,27 @@ TEnumerable = class
34693469
const elementSelector: Func<T, TElement>;
34703470
const comparer: IEqualityComparer<TKey>): ILookup<TKey, TElement>; overload; static;
34713471

3472+
/// <summary>
3473+
/// Splits the elements of a sequence into chunks of size at most <c>size</c> .
3474+
/// </summary>
3475+
/// <param name="source">
3476+
/// An <see cref="Spring.Collections|IEnumerable&lt;T&gt;" /> whose elements to chunk.
3477+
/// </param>
3478+
/// <param name="size">
3479+
/// The maximum size of each chunk.
3480+
/// </param>
3481+
/// <returns>
3482+
/// An <see cref="Spring.Collections|IEnumerable&lt;TArray&lt;T&gt;&gt;" /> that contains the
3483+
/// elements the input sequence split into chunks of size <c>size</c>.
3484+
/// </returns>
3485+
/// <exception cref="Spring|EArgumentNullException">
3486+
/// <c>source</c> is <c>nil</c>.
3487+
/// </exception>
3488+
/// <exception cref="Spring|EArgumentOutOfRangeException">
3489+
/// <c>size</c> is below 1.
3490+
/// </exception>
3491+
class function Chunk<T>(const source: IEnumerable<T>; size: Integer): IEnumerable<TArray<T>>; static;
3492+
34723493
class function Distinct<T>(const source: IEnumerable<T>): IEnumerable<T>; overload; static;
34733494
class function Distinct<T>(const source: IEnumerable<T>;
34743495
const comparer: IEqualityComparer<T>): IEnumerable<T>; overload; static;
@@ -7413,6 +7434,12 @@ class function TEnumerable.Aggregate<TSource, TAccumulate, TResult>(
74137434
Result := resultSelector(res);
74147435
end;
74157436

7437+
class function TEnumerable.Chunk<T>(const source: IEnumerable<T>;
7438+
size: Integer): IEnumerable<TArray<T>>;
7439+
begin
7440+
Result := TChunkIterator<T>.Create(source, size);
7441+
end;
7442+
74167443
class function TEnumerable.CombinePredicates<T>(const predicate1,
74177444
predicate2: Predicate<T>): Predicate<T>;
74187445
begin

Source/Base/Spring.pas

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,7 @@ Guard = record
16141614
resultSelector,
16151615
second,
16161616
selector,
1617+
size,
16171618
sorter,
16181619
source,
16191620
sourceIndex,
@@ -8618,6 +8619,7 @@ class function RaiseHelper.GetArgumentName(argument: ExceptionArgument): string;
86188619
'resultSelector',
86198620
'second',
86208621
'selector',
8622+
'size',
86218623
'sorter',
86228624
'source',
86238625
'sourceIndex',

Tests/Source/Base/Spring.Tests.Collections.pas

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,22 @@ TMemoizeTests = class(TEnumerableTestCase)
11111111
procedure MemoizeWithMemoizedSourceReturnsSame;
11121112
end;
11131113

1114+
TTestChunk = class(TEnumerableTestCase)
1115+
published
1116+
[TestCase('0')]
1117+
[TestCase('-1')]
1118+
procedure RaisesWhenSizeIsNonPositive(size: Integer);
1119+
procedure ChunkSourceLazily;
1120+
procedure ChunkSourceRepeatCalls;
1121+
procedure ChunkSourceEvenly;
1122+
procedure ChunkSourceUnevenly;
1123+
procedure ChunkSourceSmallerThanMaxSize;
1124+
procedure EmptySourceYieldsNoChunks;
1125+
procedure RemovingFromSourceBeforeIterating;
1126+
procedure AddingToSourceBeforeIterating;
1127+
procedure DoesNotPrematurelyAllocateHugeArray;
1128+
end;
1129+
11141130
implementation
11151131

11161132
uses
@@ -5631,14 +5647,20 @@ procedure TTestTreeMultiSetChangedEvent.SetUp;
56315647
procedure TEnumerableTestCase.CheckEquals(const expected, actual: IInterface);
56325648
var
56335649
e1, e2: IEnumerator;
5650+
v1, v2: TValue;
56345651
begin
56355652
e1 := (actual as IEnumerable).GetEnumerator;
56365653
e2 := (expected as IEnumerable).GetEnumerator;
56375654

56385655
while e1.MoveNext do
56395656
begin
56405657
Check(e2.MoveNext);
5641-
Check((e1.Current = e2.Current), 'collections not equal');
5658+
v1 := e1.Current;
5659+
v2 := e2.Current;
5660+
if (v1.Kind = tkInterface) and (v1.Kind = tkInterface) then
5661+
CheckEquals(v1.AsInterface, v2.AsInterface)
5662+
else
5663+
Check(v1 = v2, 'collections not equal');
56425664
end;
56435665
Check(not e2.MoveNext, 'collections not equal');
56445666
end;
@@ -7286,4 +7308,137 @@ procedure TMemoizeTests.MemoizeWithMemoizedSourceReturnsSame;
72867308
{$ENDREGION}
72877309

72887310

7311+
{$REGION 'TTestChunk'}
7312+
7313+
procedure TTestChunk.RaisesWhenSizeIsNonPositive(size: Integer);
7314+
var
7315+
source: IEnumerable<Integer>;
7316+
begin
7317+
source := TEnumerable.From<Integer>([1]);
7318+
CheckException(EArgumentOutOfRangeException,
7319+
procedure
7320+
begin
7321+
TEnumerable.Chunk<Integer>(source, size);
7322+
end);
7323+
end;
7324+
7325+
procedure TTestChunk.ChunkSourceLazily;
7326+
var
7327+
chunks: IEnumerator<TArray<Integer>>;
7328+
begin
7329+
chunks := TEnumerable.Chunk<Integer>(FastInfiniteEnumerator<Integer>, 5).GetEnumerator;
7330+
chunks.MoveNext;
7331+
CheckEquals([0, 0, 0, 0, 0], chunks.Current);
7332+
CheckTrue(chunks.MoveNext);
7333+
end;
7334+
7335+
procedure TTestChunk.ChunkSourceRepeatCalls;
7336+
var
7337+
source: IEnumerable<Integer>;
7338+
begin
7339+
source := TEnumerable.From<Integer>([9999, 0, 888, -1, 66, -777, 1, 2, -12345]);
7340+
CheckEquals(TEnumerable.Chunk<Integer>(source, 3), TEnumerable.Chunk<Integer>(source, 3));
7341+
end;
7342+
7343+
procedure TTestChunk.ChunkSourceEvenly;
7344+
var
7345+
source: IEnumerable<Integer>;
7346+
chunks: IEnumerator<TArray<Integer>>;
7347+
begin
7348+
source := TEnumerable.From<Integer>([9999, 0, 888, -1, 66, -777, 1, 2, -12345]);
7349+
chunks := TEnumerable.Chunk<Integer>(source, 3).GetEnumerator;
7350+
chunks.MoveNext;
7351+
CheckEquals([9999, 0, 888], chunks.Current);
7352+
chunks.MoveNext;
7353+
CheckEquals([-1, 66, -777], chunks.Current);
7354+
chunks.MoveNext;
7355+
CheckEquals([1, 2, -12345], chunks.Current);
7356+
CheckFalse(chunks.MoveNext);
7357+
end;
7358+
7359+
procedure TTestChunk.ChunkSourceUnevenly;
7360+
var
7361+
source: IEnumerable<Integer>;
7362+
chunks: IEnumerator<TArray<Integer>>;
7363+
begin
7364+
source := TEnumerable.From<Integer>([9999, 0, 888, -1, 66, -777, 1, 2]);
7365+
chunks := TEnumerable.Chunk<Integer>(source, 3).GetEnumerator;
7366+
chunks.MoveNext;
7367+
CheckEquals([9999, 0, 888], chunks.Current);
7368+
chunks.MoveNext;
7369+
CheckEquals([-1, 66, -777], chunks.Current);
7370+
chunks.MoveNext;
7371+
CheckEquals([1, 2], chunks.Current);
7372+
CheckFalse(chunks.MoveNext);
7373+
end;
7374+
7375+
procedure TTestChunk.ChunkSourceSmallerThanMaxSize;
7376+
var
7377+
source: IEnumerable<Integer>;
7378+
chunks: IEnumerator<TArray<Integer>>;
7379+
begin
7380+
source := TEnumerable.From<Integer>([9999, 0]);
7381+
chunks := TEnumerable.Chunk<Integer>(source, 3).GetEnumerator;
7382+
chunks.MoveNext;
7383+
CheckEquals([9999, 0], chunks.Current);
7384+
CheckFalse(chunks.MoveNext);
7385+
end;
7386+
7387+
procedure TTestChunk.EmptySourceYieldsNoChunks;
7388+
var
7389+
source: IEnumerable<Integer>;
7390+
chunks: IEnumerator<TArray<Integer>>;
7391+
begin
7392+
source := TEnumerable.From<Integer>([]);
7393+
chunks := TEnumerable.Chunk<Integer>(source, 3).GetEnumerator;
7394+
CheckFalse(chunks.MoveNext);
7395+
end;
7396+
7397+
procedure TTestChunk.RemovingFromSourceBeforeIterating;
7398+
var
7399+
list: IList<Integer>;
7400+
chunks: IEnumerator<TArray<Integer>>;
7401+
begin
7402+
list := TCollections.CreateList<Integer>([9999, 0, 888, -1, 66, -777, 1, 2, -12345]);
7403+
chunks := TEnumerable.Chunk<Integer>(list, 3).GetEnumerator;
7404+
list.Remove(66);
7405+
chunks.MoveNext;
7406+
CheckEquals([9999, 0, 888], chunks.Current);
7407+
chunks.MoveNext;
7408+
CheckEquals([-1, -777, 1], chunks.Current);
7409+
chunks.MoveNext;
7410+
CheckEquals([2, -12345], chunks.Current);
7411+
CheckFalse(chunks.MoveNext);
7412+
end;
7413+
7414+
procedure TTestChunk.AddingToSourceBeforeIterating;
7415+
var
7416+
list: IList<Integer>;
7417+
chunks: IEnumerator<TArray<Integer>>;
7418+
begin
7419+
list := TCollections.CreateList<Integer>([9999, 0, 888, -1, 66, -777, 1, 2, -12345]);
7420+
chunks := TEnumerable.Chunk<Integer>(list, 3).GetEnumerator;
7421+
list.Add(10);
7422+
chunks.MoveNext;
7423+
CheckEquals([9999, 0, 888], chunks.Current);
7424+
chunks.MoveNext;
7425+
CheckEquals([-1, 66, -777], chunks.Current);
7426+
chunks.MoveNext;
7427+
CheckEquals([1, 2, -12345], chunks.Current);
7428+
chunks.MoveNext;
7429+
CheckEquals([10], chunks.Current);
7430+
CheckFalse(chunks.MoveNext);
7431+
end;
7432+
7433+
procedure TTestChunk.DoesNotPrematurelyAllocateHugeArray;
7434+
var
7435+
chunks: TArray<TArray<Integer>>;
7436+
begin
7437+
chunks := TEnumerable.Chunk<Integer>(TEnumerable.Range(0, 10), MaxInt).ToArray;
7438+
CheckEquals(TEnumerable.Range(0, 10).ToArray, chunks[0]);
7439+
end;
7440+
7441+
{$ENDREGION}
7442+
7443+
72897444
end.

Tests/Source/Spring.TestRegistration.pas

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ procedure RegisterTestCases;
157157
TBetweenTests.Suite,
158158
TExactlyTests.Suite,
159159

160-
TMemoizeTests.Suite
160+
TMemoizeTests.Suite,
161+
162+
TTestChunk.Suite
161163
]);
162164

163165
RegisterTests('Spring.Base.Collections.Dictionaries', [

0 commit comments

Comments
 (0)