Skip to content

Commit

Permalink
feat(support): add methods reduce, chunk and findKey to `ArrayH…
Browse files Browse the repository at this point in the history
…elper` (#720)

Co-authored-by: Karel Faille <shaffe.fr@gmail.com>
Co-authored-by: Enzo Innocenzi <enzo@innocenzi.dev>
  • Loading branch information
3 people authored Nov 13, 2024
1 parent d599d50 commit c8a31db
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 0 deletions.
69 changes: 69 additions & 0 deletions src/Tempest/Support/src/ArrayHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,75 @@ public function __construct(
}
}

/**
* Finds a value in the array and return the corresponding key if successful.
*
* @param (Closure(TValue, TKey): bool)|mixed $value The value to search for, a Closure will find the first item that returns true.
* @param bool $strict Whether to use strict comparison.
*
* @return array-key|null The key for `$value` if found, `null` otherwise.
*/
public function findKey(mixed $value, bool $strict = false): int|string|null
{
if (! $value instanceof Closure) {
$search = array_search($value, $this->array, $strict);

return $search === false ? null : $search; // Keep empty values but convert false to null
}

foreach ($this->array as $key => $item) {
if ($value($item, $key) === true) {
return $key;
}
}

return null;
}

/**
* Chunks the array into chunks of the given size.
*
* @param int $size The size of each chunk.
* @param bool $preserveKeys Whether to preserve the keys of the original array.
*
* @return self<array-key, self>
*/
public function chunk(int $size, bool $preserveKeys = true): self
{
if ($size <= 0) {
return new self();
}

$chunks = [];
foreach (array_chunk($this->array, $size, $preserveKeys) as $chunk) {
$chunks[] = new self($chunk);
}

return new self($chunks);
}

/**
* Reduces the array to a single value using a callback.
*
* @template TReduceInitial
* @template TReduceReturnType
*
* @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
* @param TReduceInitial $initial
*
* @return TReduceReturnType
*/
public function reduce(callable $callback, mixed $initial = null): mixed
{
$result = $initial;

foreach ($this->array as $key => $value) {
$result = $callback($result, $value, $key);
}

return $result;
}

/**
* Gets a value from the array and remove it.
*
Expand Down
181 changes: 181 additions & 0 deletions src/Tempest/Support/tests/ArrayHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1381,4 +1381,185 @@ public function test_sort_keys_by_callback(): void
actual: $array->sortKeysByCallback(fn ($a, $b) => $a <=> $b)->toArray(),
);
}

public function test_basic_reduce(): void
{
$collection = arr([
'first_name' => 'John',
'last_name' => 'Doe',
'age' => 42,
]);

$this->assertSame(
actual: $collection->reduce(fn ($carry, $value) => $carry . ' ' . $value, 'Hello'),
expected: 'Hello John Doe 42',
);
}

public function test_reduce_with_existing_function(): void
{
$collection = arr([
[1, 2, 2, 3],
[2, 3, 3, 4],
[3, 1, 3, 1],
]);

$this->assertSame(
actual: $collection->reduce('max'),
expected: [3, 1, 3, 1],
);
}

public function test_empty_array_reduce(): void
{
$this->assertSame(
actual: arr()->reduce(fn ($carry, $value) => $carry . ' ' . $value, 'default'),
expected: 'default',
);
}

public function test_chunk(): void
{
$collection = arr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

$this->assertSame(
actual: $collection
->chunk(2, preserveKeys: false)
->map(fn ($chunk) => $chunk->toArray())
->toArray(),
expected: [
[1, 2],
[3, 4],
[5, 6],
[7, 8],
[9, 10],
],
);

$this->assertSame(
actual: $collection
->chunk(3, preserveKeys: false)
->map(fn ($chunk) => $chunk->toArray())
->toArray(),
expected: [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10],
],
);
}

public function test_chunk_preserve_keys(): void
{
$collection = arr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

$this->assertSame(
actual: $collection
->chunk(2)
->map(fn ($chunk) => $chunk->toArray())
->toArray(),
expected: [
[0 => 1, 1 => 2],
[2 => 3, 3 => 4],
[4 => 5, 5 => 6],
[6 => 7, 7 => 8],
[8 => 9, 9 => 10],
],
);

$this->assertSame(
actual: $collection
->chunk(3)
->map(fn ($chunk) => $chunk->toArray())
->toArray(),
expected: [
[0 => 1, 1 => 2, 2 => 3],
[3 => 4, 4 => 5, 5 => 6],
[6 => 7, 7 => 8, 8 => 9],
[9 => 10],
],
);
}

public function test_find_key_with_simple_value(): void
{
$collection = arr(['apple', 'banana', 'orange']);

$this->assertSame(1, $collection->findKey('banana'));
$this->assertSame(0, $collection->findKey('apple'));
$this->assertNull($collection->findKey('grape'));
}

public function test_find_key_with_strict_comparison(): void
{
$collection = arr([1, '1', 2, '2']);

$this->assertSame(0, $collection->findKey(1, strict: false));
$this->assertSame(0, $collection->findKey('1', strict: false));

$this->assertSame(0, $collection->findKey(1, strict: true));
$this->assertSame(1, $collection->findKey('1', strict: true));
}

public function test_find_key_with_closure(): void
{
$collection = arr([
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane'],
['id' => 3, 'name' => 'Bob'],
]);

$result = $collection->findKey(fn ($item) => $item['name'] === 'Jane');
$this->assertSame(1, $result);

$result = $collection->findKey(fn ($item, $key) => $key === 2);
$this->assertSame(2, $result);

$result = $collection->findKey(fn ($item) => $item['name'] === 'Alice');
$this->assertNull($result);
}

public function test_find_key_with_string_keys(): void
{
$collection = arr([
'first' => 'value1',
'second' => 'value2',
'third' => 'value3',
]);

$this->assertSame('second', $collection->findKey('value2'));
$this->assertNull($collection->findKey('value4'));
}

public function test_find_key_with_null_values(): void
{
$collection = arr(['a', null, 'b', '']);

$this->assertSame(1, $collection->findKey(null));
$this->assertSame(1, $collection->findKey(''));
}

public function test_find_key_with_complex_closure(): void
{
$collection = arr([
['age' => 25, 'active' => true],
['age' => 30, 'active' => false],
['age' => 35, 'active' => true],
]);

$result = $collection->findKey(function ($item) {
return $item['age'] > 28 && $item['active'] === true;
});

$this->assertSame(2, $result);
}

public function test_find_key_with_empty_array(): void
{
$collection = arr([]);

$this->assertNull($collection->findKey('anything'));
$this->assertNull($collection->findKey(fn () => true));
}
}

0 comments on commit c8a31db

Please # to comment.