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
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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**
---------------

Expand Down
4 changes: 4 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,6 +27,9 @@
NarrowWideUnionReturnTypeRector::class => [
__DIR__ . '/tests/FilterTest.php',
],
FunctionLikeToFirstClassCallableRector::class => [
__DIR__ . '/tests/FinderTest.php',
],
])
->withParallel()
->withRootFiles()
Expand Down
41 changes: 41 additions & 0 deletions src/Finder.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,45 @@ public static function rows(

return $rows;
}

/**
* Partitions data into two arrays: [matching, notMatching] based on filter.
*
* @param array<int|string, mixed>|Traversable<int|string, mixed> $data
* @param callable(mixed $datum, int|string|null $key): bool $filter
* @return array{0: array<int|string, mixed>, 1: array<int|string, mixed>}
*/
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];
}
}
73 changes: 73 additions & 0 deletions tests/FinderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use stdClass;

use function current;
use function is_string;
use function str_contains;

final class FinderTest extends TestCase
Expand Down Expand Up @@ -382,4 +383,76 @@ public function testRowsWithLimit(): void
Finder::rows($data, $filter, limit: 1)
);
}

/**
* @param array<int|string, mixed> $expectedMatching
* @param array<int|string, mixed> $expectedNotMatching
* @param array<int|string, int|string> $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<string, array{iterable, callable, array<int|string, mixed>, array<int|string, mixed>, 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);
}
}
Loading