From b5895c517393cc767d77383b239d3d8627b86518 Mon Sep 17 00:00:00 2001 From: Ruslan Date: Wed, 15 Jun 2022 15:04:56 +0300 Subject: [PATCH 1/2] Add groupMapReduce --- composer.json | 1 + .../Functions/Collection/GroupMapReduce.php | 55 +++++++++++++++++++ .../Collection/GroupMapReduceTest.php | 42 ++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 src/Fp/Functions/Collection/GroupMapReduce.php create mode 100644 tests/Runtime/Functions/Collection/GroupMapReduceTest.php diff --git a/composer.json b/composer.json index 46f10bfb..9942193e 100644 --- a/composer.json +++ b/composer.json @@ -60,6 +60,7 @@ "src/Fp/Functions/Collection/Fold.php", "src/Fp/Functions/Collection/ForAll.php", "src/Fp/Functions/Collection/GroupBy.php", + "src/Fp/Functions/Collection/GroupMapReduce.php", "src/Fp/Functions/Collection/Head.php", "src/Fp/Functions/Collection/Keys.php", "src/Fp/Functions/Collection/Last.php", diff --git a/src/Fp/Functions/Collection/GroupMapReduce.php b/src/Fp/Functions/Collection/GroupMapReduce.php new file mode 100644 index 00000000..80a82f2d --- /dev/null +++ b/src/Fp/Functions/Collection/GroupMapReduce.php @@ -0,0 +1,55 @@ +>> groupMapReduce( + * collection: [ + * ['id' => 10, 'val' => 10], + * ['id' => 10, 'val' => 15], + * ['id' => 10, 'val' => 20], + * ['id' => 20, 'val' => 10], + * ['id' => 20, 'val' => 15], + * ['id' => 30, 'val' => 20], + * ], + * group: fn(array $a) => $a['id'], + * map: fn(array $a) => [$a['val']], + * reduce: fn(array $old, array $new) => array_merge($old, $new), + * ); + * => [10 => [10, 15, 20], 20 => [10, 15], 30 => [20]] + * ``` + * + * @template K of array-key + * @template A + * @template KOut of array-key + * @template B + * + * @param iterable $collection + * @param callable(A): KOut $group + * @param callable(A): B $map + * @param callable(B, B): B $reduce + * @return array + */ +function groupMapReduce(iterable $collection, callable $group, callable $map, callable $reduce): array +{ + $grouped = []; + + foreach ($collection as $item) { + $key = $group($item); + + if (array_key_exists($key, $grouped)) { + $grouped[$key] = $reduce($grouped[$key], $map($item)); + } else { + $grouped[$key] = $map($item); + } + } + + return $grouped; +} diff --git a/tests/Runtime/Functions/Collection/GroupMapReduceTest.php b/tests/Runtime/Functions/Collection/GroupMapReduceTest.php new file mode 100644 index 00000000..edab2568 --- /dev/null +++ b/tests/Runtime/Functions/Collection/GroupMapReduceTest.php @@ -0,0 +1,42 @@ +assertEquals( + [ + 10 => [10, 15, 20], + 20 => [10, 15], + 30 => [20], + ], + groupMapReduce( + [ + ['id' => 10, 'sum' => 10], + ['id' => 10, 'sum' => 15], + ['id' => 10, 'sum' => 20], + ['id' => 20, 'sum' => 10], + ['id' => 20, 'sum' => 15], + ['id' => 30, 'sum' => 20], + ], + /** @param array{id: int, sum: int} $a */ + fn(array $a) => $a['id'], + /** @param array{id: int, sum: int} $a */ + fn(array $a) => [$a['sum']], + /** + * @param list $old + * @param list $new + */ + fn(array $old, array $new) => array_merge($old, $new), + ) + ); + } +} From 967946c3d977f394a40bfc4c1d339882fa4f324d Mon Sep 17 00:00:00 2001 From: Ruslan Date: Wed, 15 Jun 2022 15:17:07 +0300 Subject: [PATCH 2/2] More specific return type for groupMapReduce --- src/Fp/Functions/Collection/GroupMapReduce.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Fp/Functions/Collection/GroupMapReduce.php b/src/Fp/Functions/Collection/GroupMapReduce.php index 80a82f2d..1cc1d292 100644 --- a/src/Fp/Functions/Collection/GroupMapReduce.php +++ b/src/Fp/Functions/Collection/GroupMapReduce.php @@ -36,6 +36,10 @@ * @param callable(A): B $map * @param callable(B, B): B $reduce * @return array + * + * @psalm-return ($collection is non-empty-array + * ? non-empty-array + * : array) */ function groupMapReduce(iterable $collection, callable $group, callable $map, callable $reduce): array {