Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ p\array_map(fn ($x) => $x * 2);
- `p\iterable_any(?callable $callback = null)` — returns `true` if any item matches (or is `=== true` when callback is `null`); short-circuits
- `p\iterable_filter(callable $callback)` — yields matching items for which `$callback` returns `true`
- `p\iterable_first(iterable $iterable)` — returns first item or `null` (**consumes one element**)
- `p\iterable_flatten(callable $callable)` — lazily flattens the an iterable of iterables
- `p\iterable_map(callable $callback)` — yields items mapped over `$callback`. Preserves keys.
- `p\iterable_nth(int $n)` — returns the nth item (0-based); consumes up to n+1 items; returns `null` if out of range
- `p\iterable_reduce(callable $callback, $initial = null)` — reduces an iterable to a single value
Expand Down
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
"autoload": {
"files": [
"src/pipe.php"
]
]
},
"autoload-dev": {
"psr-4": {
"Anarchitecture\\pipe\\Tests\\": "tests/"
}
},
"require-dev": {
"phpstan/phpstan": "^2.1",
Expand Down
24 changes: 23 additions & 1 deletion src/pipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,28 @@ function iterable_first(iterable $iterable): mixed {
return null;
}

/**
* Returns a unary callable that lazily flattens it's arguments.
*
* @param bool $preserve_keys
*
* @return callable(iterable<array-key, iterable<array-key, mixed>>) : Generator
*/
function iterable_flatten(bool $preserve_keys = true): callable {
return function (iterable $iterable) use ($preserve_keys) : Generator {
/** @var iterable<array-key, mixed> $value */
foreach ($iterable as $value) {
if ($preserve_keys) {
yield from $value;
} else {
foreach ($value as $inner) {
yield $inner;
}
}
}
};
}

/**
* Return unary callable for mapping over an iterable
*
Expand Down Expand Up @@ -771,7 +793,7 @@ function iterable_zip(iterable ...$right) : \Closure {
return new \IteratorIterator($iterable);
};

return static function(iterable $left) use ($right, $mapper) : \Generator {
return static function(iterable $left) use ($right, $mapper) : Generator {

$right = \array_map($mapper, $right);

Expand Down
58 changes: 58 additions & 0 deletions tests/IterableFlattenTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Anarchitecture\pipe\Tests;

use PHPUnit\Framework\TestCase;
use function Anarchitecture\pipe\collect;
use function Anarchitecture\pipe\iterable_flatten;
use function Anarchitecture\pipe\iterable_map;
use function Anarchitecture\pipe\iterable_take;

class IterableFlattenTest extends TestCase
{
public function test_it_flattens_arrays_normally(): void {
$arr = [
[1,2,3],
[4,5,6],
];

self::assertSame(collect(iterable_flatten(preserve_keys: false)($arr)), [1,2,3,4,5,6]);
}

public function test_that_it_flattens_generators(): void {
$createListOfGenerators = function () {
return [
(function() {
yield 'a';
yield 'b';
yield 'c';
})(),
(function() {
yield 'x';
yield 'y';
yield 'z';
})(),
];
};

self::assertSame(collect(iterable_flatten()($createListOfGenerators())), ['x', 'y', 'z']);
self::assertSame(collect(iterable_flatten(preserve_keys: false)($createListOfGenerators())), ['a', 'b', 'c', 'x', 'y', 'z']);
}

public function test_that_yields_nothing_for_empty_input(): void {
$generator = iterable_flatten()([]);
self::assertSame(iterator_to_array($generator), []);
}

public function test_that_it_executes_lazily(): void {

$take_amount = 12;
$result = [1,2,3,4,5]
|> iterable_map(fn($i) => $i < 5 ? [1,2,3] : self::fail('this function should not be invoked this many times'))
|> iterable_flatten()
|> iterable_take($take_amount);

// this should run without failing
self::assertSame(count(iterator_to_array($result, preserve_keys: false)), $take_amount);
}
}