diff --git a/src/Filter/Chain.php b/src/Filter/Chain.php index cf18a60..676fa06 100644 --- a/src/Filter/Chain.php +++ b/src/Filter/Chain.php @@ -4,6 +4,7 @@ use ArrayIterator; use Countable; +use Generator; use IteratorAggregate; use OutOfBoundsException; use Traversable; @@ -56,6 +57,22 @@ public function getIterator(): Traversable return new ArrayIterator($this->rules); } + /** + * Yield all rules of this and nested chains in a flat sequence + * + * @return Generator + */ + public function yieldRules(): Generator + { + foreach ($this->rules as $rule) { + if ($rule instanceof self) { + yield from $rule->yieldRules(); + } else { + yield $rule; + } + } + } + /** * Add a rule to this chain * diff --git a/src/Seq.php b/src/Seq.php index 349e870..ef7d5e2 100644 --- a/src/Seq.php +++ b/src/Seq.php @@ -3,6 +3,7 @@ namespace ipl\Stdlib; use Closure; +use Generator; /** * Collection of utilities for traversables @@ -113,4 +114,19 @@ public static function findValue(iterable $traversable, mixed $needle, bool $cas return null; } + + /** + * Apply the callback to all elements of the given traversable, while preserving keys + * + * @param iterable $traversable + * @param callable $callback + * + * @return Generator + */ + public static function map(iterable $traversable, callable $callback): Generator + { + foreach ($traversable as $key => $value) { + yield $key => $callback($value); + } + } } diff --git a/tests/FilterChainTest.php b/tests/FilterChainTest.php new file mode 100644 index 0000000..8577414 --- /dev/null +++ b/tests/FilterChainTest.php @@ -0,0 +1,30 @@ +yieldRules() as $rule) { + $flat[] = $rule; + } + + $this->assertCount(4, $flat); + $this->assertContainsOnlyInstancesOf(Filter\Rule::class, $flat); + } +} diff --git a/tests/SeqTest.php b/tests/SeqTest.php index 8f24fbf..a66da9d 100644 --- a/tests/SeqTest.php +++ b/tests/SeqTest.php @@ -129,4 +129,89 @@ public function testFindWithFunctionName() ) ); } + + public function testMapWithSequencedArray() + { + $arr = [1, 2, 3]; + + $result = []; + foreach (Seq::map($arr, fn ($v) => $v + 1) as $k => $v) { + $result[$k] = $v; + } + + $this->assertSame( + [2, 3, 4], + $result + ); + } + + public function testMapWithKeyedArray() + { + $arr = [ + 'one' => 1, + 'two' => 2, + 'three' => 3 + ]; + + $result = []; + foreach (Seq::map($arr, fn ($v) => $v + 1) as $k => $v) { + $result[$k] = $v; + } + + $this->assertSame( + [ + 'one' => 2, + 'two' => 3, + 'three' => 4 + ], + $result + ); + } + + public function testMapWithGenerators() + { + $generator = function () { + foreach ([1, 2, 3] as $v) { + yield $v; + } + }; + + $result = []; + foreach (Seq::map($generator(), fn ($v) => $v + 1) as $k => $v) { + $result[$k] = $v; + } + + $this->assertSame( + [2, 3, 4], + $result + ); + } + + public function testMapWithKeyedGenerators() + { + $generator = function () { + $arr = [ + 'one' => 1, + 'two' => 2, + 'three' => 3 + ]; + foreach ($arr as $k => $v) { + yield $k => $v; + } + }; + + $result = []; + foreach (Seq::map($generator(), fn ($v) => $v + 1) as $k => $v) { + $result[$k] = $v; + } + + $this->assertSame( + [ + 'one' => 2, + 'two' => 3, + 'three' => 4 + ], + $result + ); + } }