Skip to content

Commit a8d257b

Browse files
authored
Feat: iterable_chunk() (#50)
1 parent d0df451 commit a8d257b

File tree

5 files changed

+205
-2
lines changed

5 files changed

+205
-2
lines changed

Diff for: README.md

+28
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This package provides functions to work with [iterables](https://wiki.php.net/rf
1616
- [iterable_reduce()](#iterable_reduce)
1717
- [iterable_filter()](#iterable_filter)
1818
- [iterable_values()](#iterable_values)
19+
- [iterable_chunk()](#iterable_chunk)
1920

2021
iterable_to_array()
2122
-------------------
@@ -170,6 +171,33 @@ foreach (iterable_values($generator()) as $key => $value) {
170171
}
171172
```
172173

174+
iterable_chunk()
175+
--------------
176+
177+
Here's an `array_chunk`-like function that also works with a `Traversable`.
178+
179+
```php
180+
use function BenTools\IterableFunctions\iterable_chunk;
181+
182+
$fruits = [
183+
'banana',
184+
'apple',
185+
'strawberry',
186+
'raspberry',
187+
'pineapple',
188+
]
189+
$fruits = (fn () => yield from $fruits)()
190+
iterable_chunk($fruits, 2);
191+
192+
/*
193+
[
194+
['banana', 'apple'],
195+
['strawberry', 'raspberry'],
196+
['pineapple'],
197+
]
198+
*/
199+
```
200+
173201
Iterable fluent interface
174202
=========================
175203

Diff for: src/ChunkIterator.php

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BenTools\IterableFunctions;
6+
7+
use Iterator;
8+
use IteratorIterator;
9+
use Traversable;
10+
11+
/**
12+
* @internal
13+
*
14+
* @template TKey
15+
* @template TValue
16+
*
17+
* @implements Iterator<int, array<TKey, TValue>>
18+
*/
19+
final class ChunkIterator implements Iterator
20+
{
21+
/** @var Iterator<TKey, TValue> */
22+
private Iterator $iterator;
23+
24+
private int $chunkSize;
25+
26+
private bool $preserveKeys;
27+
28+
private int $chunkIndex = 0;
29+
30+
/** @var array<TKey, TValue> */
31+
private array $buffer = [];
32+
33+
/**
34+
* @param Traversable<TKey, TValue> $iterator
35+
*/
36+
public function __construct(
37+
Traversable $iterator,
38+
int $chunkSize,
39+
bool $preserveKeys = false,
40+
) {
41+
$this->iterator = $iterator instanceof Iterator ? $iterator : new IteratorIterator($iterator);
42+
$this->chunkSize = $chunkSize;
43+
$this->preserveKeys = $preserveKeys;
44+
}
45+
46+
public function current(): mixed
47+
{
48+
return $this->buffer;
49+
}
50+
51+
public function next(): void
52+
{
53+
$this->fill();
54+
$this->chunkIndex++;
55+
}
56+
57+
public function key(): int
58+
{
59+
return $this->chunkIndex;
60+
}
61+
62+
public function valid(): bool
63+
{
64+
if ($this->chunkIndex === 0) {
65+
$this->fill();
66+
}
67+
68+
return $this->buffer !== [];
69+
}
70+
71+
public function rewind(): void
72+
{
73+
$this->iterator->rewind();
74+
$this->chunkIndex = 0;
75+
$this->buffer = [];
76+
}
77+
78+
private function fill(): void
79+
{
80+
$this->buffer = [];
81+
$i = 0;
82+
while ($this->iterator->valid() && $i++ < $this->chunkSize) {
83+
$current = $this->iterator->current();
84+
85+
if ($this->preserveKeys) {
86+
$this->buffer[$this->iterator->key()] = $current;
87+
} else {
88+
$this->buffer[] = $current;
89+
}
90+
91+
$this->iterator->next();
92+
}
93+
}
94+
}

Diff for: src/IterableObject.php

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use IteratorIterator;
1313
use Traversable;
1414

15+
use function array_chunk;
1516
use function array_filter;
1617
use function array_map;
1718
use function iterator_to_array;
@@ -118,6 +119,16 @@ public function values(): self
118119
return new self(new WithoutKeysTraversable($this->iterable));
119120
}
120121

122+
/** @return iterable<int, array<TKey, TValue>> */
123+
public function chunk(int $size): iterable
124+
{
125+
if ($this->iterable instanceof Traversable) {
126+
return new ChunkIterator($this->iterable, $size, $this->preserveKeys);
127+
}
128+
129+
return array_chunk($this->iterable, $size, $this->preserveKeys);
130+
}
131+
121132
/** @return Traversable<TKey, TValue> */
122133
public function getIterator(): Traversable
123134
{

Diff for: src/iterable-functions.php

+17-2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,21 @@ function iterable_values(iterable $iterable): iterable
136136
return iterable($iterable)->values();
137137
}
138138

139+
/**
140+
* Split an iterable into chunks
141+
*
142+
* @param iterable<TKey, TValue> $iterable
143+
*
144+
* @return iterable<iterable<TKey, TValue>>
145+
*
146+
* @template TKey
147+
* @template TValue
148+
*/
149+
function iterable_chunk(iterable $iterable, int $size, bool $preserveKeys = false): iterable
150+
{
151+
return iterable($iterable, $preserveKeys)->chunk($size);
152+
}
153+
139154
/**
140155
* @param iterable<TKey, TValue>|null $iterable
141156
*
@@ -144,7 +159,7 @@ function iterable_values(iterable $iterable): iterable
144159
* @template TKey
145160
* @template TValue
146161
*/
147-
function iterable(?iterable $iterable): IterableObject
162+
function iterable(?iterable $iterable, bool $preserveKeys = true): IterableObject
148163
{
149-
return new IterableObject($iterable ?? new EmptyIterator());
164+
return new IterableObject($iterable ?? new EmptyIterator(), $preserveKeys);
150165
}

Diff for: tests/IterableChunkTest.php

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BenTools\IterableFunctions\Tests;
6+
7+
use function BenTools\IterableFunctions\iterable_chunk;
8+
use function expect;
9+
use function it;
10+
11+
it('chunks an iterable', function (iterable $fruits): void {
12+
$chunks = iterable_chunk($fruits, 2);
13+
$expectedChunks = [
14+
['banana', 'apple'],
15+
['strawberry', 'raspberry'],
16+
['pineapple'],
17+
];
18+
expect([...$chunks])->toEqual($expectedChunks);
19+
})->with(function () {
20+
$fruits = [
21+
'banana',
22+
'apple',
23+
'strawberry',
24+
'raspberry',
25+
'pineapple',
26+
];
27+
yield 'array' => [$fruits];
28+
yield 'traversable' => [(fn () => yield from $fruits)()];
29+
});
30+
31+
it('preserves keys', function (iterable $fruits): void {
32+
$chunks = iterable_chunk($fruits, 2, true);
33+
$expectedChunks = [
34+
[
35+
'banana' => 0,
36+
'apple' => 1,
37+
],
38+
[
39+
'strawberry' => 2,
40+
'raspberry' => 3,
41+
],
42+
['pineapple' => 4],
43+
];
44+
expect([...$chunks])->toEqual($expectedChunks);
45+
})->with(function () {
46+
$fruits = [
47+
'banana' => 0,
48+
'apple' => 1,
49+
'strawberry' => 2,
50+
'raspberry' => 3,
51+
'pineapple' => 4,
52+
];
53+
yield 'array' => [$fruits];
54+
yield 'traversable' => [(fn () => yield from $fruits)()];
55+
});

0 commit comments

Comments
 (0)