From 269a72eedbc65837dc3bc7196f282721c7c475ce Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 5 Dec 2025 05:23:49 +0700 Subject: [PATCH 1/2] Add Finder::partition() to splits data into 2 arrays: matching and non-matching --- README.md | 34 ++++++++++++++++++++- rector.php | 4 +++ src/Finder.php | 41 +++++++++++++++++++++++++ tests/FinderTest.php | 72 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ad1b93..02c33d9 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Features - [x] Verify at least times: `once()`, `twice()`, `times()` - [x] Verify exact times: `once()`, `twice()`, `times()` -- [x] Search data: `first()`, `last()`, `rows()` +- [x] Search data: `first()`, `last()`, `rows()`, `partition()` - [x] Collect data with filter and transform Installation @@ -369,6 +369,38 @@ var_dump( ); // [1] ``` +#### 4. `Finder::partition()` + +It splits data into two arrays: matching and non-matching items based on a filter. + +```php +use ArrayLookup\Finder; + +// Basic partition - split numbers into greater than 5 and not +$data = [1, 6, 3, 8, 4, 9]; +$filter = static fn($datum): bool => $datum > 5; + +[$matching, $notMatching] = Finder::partition($data, $filter); + +var_dump($matching); // [6, 8, 9] +var_dump($notMatching); // [1, 3, 4] + +// Partition with preserved keys +[$matching, $notMatching] = Finder::partition($data, $filter, preserveKey: true); + +var_dump($matching); // [1 => 6, 3 => 8, 5 => 9] +var_dump($notMatching); // [0 => 1, 2 => 3, 4 => 4] + +// Using the array key inside the filter +$data = [10, 20, 30, 40]; +$keyFilter = static fn($datum, $key): bool => $key % 2 === 0; + +[$even, $odd] = Finder::partition($data, $keyFilter, preserveKey: true); + +var_dump($even); // [0 => 10, 2 => 30] +var_dump($odd); // [1 => 20, 3 => 40] +``` + **D. Collector** --------------- diff --git a/rector.php b/rector.php index 2afeb8e..edcee7d 100644 --- a/rector.php +++ b/rector.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Rector\CodingStyle\Rector\FunctionLike\FunctionLikeToFirstClassCallableRector; use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\FunctionLike\NarrowWideUnionReturnTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanStrictReturnsRector; @@ -26,6 +27,9 @@ NarrowWideUnionReturnTypeRector::class => [ __DIR__ . '/tests/FilterTest.php', ], + FunctionLikeToFirstClassCallableRector::class => [ + __DIR__ . '/tests/FinderTest.php', + ], ]) ->withParallel() ->withRootFiles() diff --git a/src/Finder.php b/src/Finder.php index 303b8da..909b7cf 100644 --- a/src/Finder.php +++ b/src/Finder.php @@ -165,4 +165,45 @@ public static function rows( return $rows; } + + /** + * Partitions data into two arrays: [matching, notMatching] based on filter. + * + * @param array|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + * @return array{0: array, 1: array} + */ + public static function partition( + iterable $data, + callable $filter, + bool $preserveKey = false + ): array { + // filter must be a callable with bool return type + Filter::boolean($filter); + + $matching = []; + $notMatching = []; + $matchKey = 0; + $notMatchKey = 0; + + foreach ($data as $key => $datum) { + $isFound = $filter($datum, $key); + + if ($isFound) { + if ($preserveKey || ! is_numeric($key)) { + $matching[$key] = $datum; + } else { + $matching[$matchKey] = $datum; + ++$matchKey; + } + } elseif ($preserveKey || ! is_numeric($key)) { + $notMatching[$key] = $datum; + } else { + $notMatching[$notMatchKey] = $datum; + ++$notMatchKey; + } + } + + return [$matching, $notMatching]; + } } diff --git a/tests/FinderTest.php b/tests/FinderTest.php index ad11f4e..f3b0aad 100644 --- a/tests/FinderTest.php +++ b/tests/FinderTest.php @@ -382,4 +382,76 @@ public function testRowsWithLimit(): void Finder::rows($data, $filter, limit: 1) ); } + + /** + * @param array $expectedMatching + * @param array $expectedNotMatching + * @param array $data + */ + #[DataProvider('partitionDataProvider')] + public function testPartition( + iterable $data, + callable $filter, + array $expectedMatching, + array $expectedNotMatching, + bool $preserveKey = false + ): void { + [$matching, $notMatching] = Finder::partition($data, $filter, $preserveKey); + + $this->assertSame($expectedMatching, $matching); + $this->assertSame($expectedNotMatching, $notMatching); + } + + /** + * @return Iterator, array, bool}> + */ + public static function partitionDataProvider(): Iterator + { + yield 'partition numbers greater than 5' => [ + [1, 6, 3, 8, 4, 9], + static fn($datum): bool => $datum > 5, + [6, 8, 9], + [1, 3, 4], + false, + ]; + + yield 'partition numbers with preserved keys' => [ + [1, 6, 3, 8, 4, 9], + static fn($datum): bool => $datum > 5, + [1 => 6, 3 => 8, 5 => 9], + [0 => 1, 2 => 3, 4 => 4], + true, + ]; + + yield 'partition using key in filter' => [ + [10, 20, 30, 40], + static fn($datum, $key): bool => $key % 2 === 0, + [0 => 10, 2 => 30], + [1 => 20, 3 => 40], + true, + ]; + + yield 'partition associative array' => [ + ['name' => 'John', 'age' => 25, 'city' => 'NYC', 'score' => 100], + static fn($datum): bool => is_string($datum), + ['name' => 'John', 'city' => 'NYC'], + ['age' => 25, 'score' => 100], + false, + ]; + } + + public function testPartitionPreservesOriginalData(): void + { + $data = [1, 2, 3]; + $copy = $data; + + [$matching, $notMatching] = Finder::partition( + $data, + static fn($datum): bool => $datum > 1 + ); + + $this->assertSame([2, 3], $matching); + $this->assertSame([1], $notMatching); + $this->assertSame($copy, $data); + } } From 6e29b11aad1df620629e2e4245744d2b41f81c73 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 5 Dec 2025 05:30:29 +0700 Subject: [PATCH 2/2] cs fix --- rector.php | 2 +- tests/FinderTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rector.php b/rector.php index edcee7d..3ffb7b7 100644 --- a/rector.php +++ b/rector.php @@ -27,7 +27,7 @@ NarrowWideUnionReturnTypeRector::class => [ __DIR__ . '/tests/FilterTest.php', ], - FunctionLikeToFirstClassCallableRector::class => [ + FunctionLikeToFirstClassCallableRector::class => [ __DIR__ . '/tests/FinderTest.php', ], ]) diff --git a/tests/FinderTest.php b/tests/FinderTest.php index f3b0aad..1b9afa2 100644 --- a/tests/FinderTest.php +++ b/tests/FinderTest.php @@ -17,6 +17,7 @@ use stdClass; use function current; +use function is_string; use function str_contains; final class FinderTest extends TestCase