From eeaeb81245ea3a2c45117aa485a6a55d208d8c3c Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 26 Jul 2026 10:03:01 +0200 Subject: [PATCH 1/4] Add Sequence::keyBy() to produce lookup maps keyed by a given selector --- src/main/php/util/data/Sequence.class.php | 34 +++++++++++++++++++ .../data/unittest/SequenceKeyByTest.class.php | 34 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100755 src/test/php/util/data/unittest/SequenceKeyByTest.class.php diff --git a/src/main/php/util/data/Sequence.class.php b/src/main/php/util/data/Sequence.class.php index 26a2499..7d1ac11 100755 --- a/src/main/php/util/data/Sequence.class.php +++ b/src/main/php/util/data/Sequence.class.php @@ -15,6 +15,7 @@ * @test util.data.unittest.SequenceFilteringTest * @test util.data.unittest.SequenceFlatteningTest * @test util.data.unittest.SequenceIteratorTest + * @test util.data.unittest.SequenceKeyByTest * @test util.data.unittest.SequenceMappingTest * @test util.data.unittest.SequenceReductionTest * @test util.data.unittest.SequenceResultSetTest @@ -180,6 +181,39 @@ public function toMap($map= null) { return $return; } + /** + * Keys the results with one of the following: + * + * - `'key'`: Uses the elements' "key" field as keys, and the whole element as values. + * - `['key' => 'name']`: Uses the elements' ID as keys, and the 'name' field as values. + * - `fn($e) => yield $e['key'] => $e['name']`: Most flexible approach. + * + * @param string|[:string]|(function(var): iterable) $selector + * @return [:var] + */ + public function keyBy($selector) { + $return= []; + if (is_string($selector)) { + foreach ($this->elements as $element) { + $return[$element[$selector]]= $element; + } + } else if (is_array($selector)) { + $k= key($selector); + $v= $selector[$k]; + foreach ($this->elements as $element) { + $return[$element[$k]]= $element[$v]; + } + } else { + $mapper= Functions::$APPLY->newInstance($selector); + foreach ($this->elements as $element) { + foreach ($mapper($element) as $k => $v) { + $return[$k]= $v; + } + } + } + return $return; + } + /** * Counts all elements * diff --git a/src/test/php/util/data/unittest/SequenceKeyByTest.class.php b/src/test/php/util/data/unittest/SequenceKeyByTest.class.php new file mode 100755 index 0000000..861ed09 --- /dev/null +++ b/src/test/php/util/data/unittest/SequenceKeyByTest.class.php @@ -0,0 +1,34 @@ + 'one', 'user' => ['power' => 1000]]; + $two= ['_id' => 'two', 'user' => ['power' => 2000]]; + + Assert::equals(['one' => $one, 'two' => $two], Sequence::of([$one, $two])->keyBy('_id')); + } + + #[Test, Values([[['_id' => 'power'], ['one' => 1000, 'two' => 2000]], [['power' => '_id'], [1000 => 'one', 2000 => 'two']]])] + public function key_by_map($fields, $expected) { + $one= ['_id' => 'one', 'power' => 1000]; + $two= ['_id' => 'two', 'power' => 2000]; + + Assert::equals($expected, Sequence::of([$one, $two])->keyBy($fields)); + } + + #[Test] + public function key_by_callable() { + $one= ['_id' => 'one', 'user' => ['power' => 1000]]; + $two= ['_id' => 'two', 'user' => ['power' => 2000]]; + + Assert::equals( + ['one' => 1000, 'two' => 2000], + Sequence::of([$one, $two])->keyBy(fn($r) => [$r['_id'] => $r['user']['power']]) + ); + } +} \ No newline at end of file From 5d7dbbe97bdebc2b3cc0ab0c8967e4b607eed0a4 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 26 Jul 2026 10:16:39 +0200 Subject: [PATCH 2/4] Do not use short closures, retaining BC with PHP < 7.4 --- src/test/php/util/data/unittest/SequenceKeyByTest.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/php/util/data/unittest/SequenceKeyByTest.class.php b/src/test/php/util/data/unittest/SequenceKeyByTest.class.php index 861ed09..4b52f7f 100755 --- a/src/test/php/util/data/unittest/SequenceKeyByTest.class.php +++ b/src/test/php/util/data/unittest/SequenceKeyByTest.class.php @@ -28,7 +28,7 @@ public function key_by_callable() { Assert::equals( ['one' => 1000, 'two' => 2000], - Sequence::of([$one, $two])->keyBy(fn($r) => [$r['_id'] => $r['user']['power']]) + Sequence::of([$one, $two])->keyBy(function($r) { return [$r['_id'] => $r['user']['power']]; }) ); } } \ No newline at end of file From 6201805cb49e0cad4ee3a66098f8f5805d854f8f Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 26 Jul 2026 10:21:30 +0200 Subject: [PATCH 3/4] Add test for keyBy() with generators --- .../util/data/unittest/SequenceKeyByTest.class.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/php/util/data/unittest/SequenceKeyByTest.class.php b/src/test/php/util/data/unittest/SequenceKeyByTest.class.php index 4b52f7f..7fc9dcb 100755 --- a/src/test/php/util/data/unittest/SequenceKeyByTest.class.php +++ b/src/test/php/util/data/unittest/SequenceKeyByTest.class.php @@ -31,4 +31,15 @@ public function key_by_callable() { Sequence::of([$one, $two])->keyBy(function($r) { return [$r['_id'] => $r['user']['power']]; }) ); } + + #[Test] + public function key_by_generator() { + $one= ['_id' => 'one', 'user' => ['power' => 1000]]; + $two= ['_id' => 'two', 'user' => ['power' => 2000]]; + + Assert::equals( + ['one' => 1000, 'two' => 2000], + Sequence::of([$one, $two])->keyBy(function($r) { yield $r['_id'] => $r['user']['power']; }) + ); + } } \ No newline at end of file From 5cd9baa15846df9a07fc2106a75f84a806b21d62 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 26 Jul 2026 10:27:48 +0200 Subject: [PATCH 4/4] Add keyBy() to documentation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e969be9..32415f3 100755 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ The following operations return a single value by consuming all of the sequence: * **toArray** - will return a PHP array with zero-based keys * **toMap** - will return a PHP associative array +* **keyBy** - will key the sequence by a given selector and return as a PHP associative array * **first** - will return the first element as an `util.data.Optional` instance. A value will be present if the sequence was not empty. * **single** - like `first()`, but raises an exception if more than one element is contained in the sequence. * **count** - will return the number of elements in the sequence