Skip to content

Commit

Permalink
Arrays::first() & last(): added parameter $predicate [Closes #305]
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Nov 21, 2023
1 parent 9e29544 commit 7768aae
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 25 deletions.
32 changes: 26 additions & 6 deletions src/Utils/Arrays.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,46 @@ public static function contains(array $array, mixed $value): bool


/**
* Returns the first item from the array or null if array is empty.
* Returns the first item from the array (matching the specified predicate if given) or null if there is no such item.
* @template T
* @param array<T> $array
* @return ?T
*/
public static function first(array $array): mixed
public static function first(array $array, ?callable $predicate = null): mixed
{
return $array[array_key_first($array)] ?? null;
$key = $predicate
? self::firstKey($array, $predicate)
: array_key_first($array);
return $key === null ? null : $array[$key];
}


/**
* Returns the last item from the array or null if array is empty.
* Returns the last item from the array (matching the specified predicate if given) or null if there is no such item.
* @template T
* @param array<T> $array
* @return ?T
*/
public static function last(array $array): mixed
public static function last(array $array, ?callable $predicate = null): mixed
{
return $array[array_key_last($array)] ?? null;
$key = $predicate
? self::firstKey(array_reverse($array, preserve_keys: true), $predicate)
: array_key_last($array);
return $key === null ? null : $array[$key];
}


/**
* Returns the key of first item (matching the specified predicate if given) or null if there is no such item.
*/
private static function firstKey(array $array, callable $predicate): int|string|null
{
foreach ($array as $k => $v) {
if ($predicate($v, $k, $array)) {
return $k;
}
}
return null;
}


Expand Down
39 changes: 29 additions & 10 deletions tests/Utils/Arrays.first().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,32 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';


Assert::null(Arrays::first([]));
Assert::null(Arrays::first([null]));
Assert::false(Arrays::first([false]));
Assert::same(1, Arrays::first([1, 2, 3]));


$arr = [1, 2, 3];
end($arr);
Assert::same(1, Arrays::first($arr));
Assert::same(3, current($arr));
test('no predicate', function () {
Assert::null(Arrays::first([]));
Assert::null(Arrays::first([null]));
Assert::false(Arrays::first([false]));
Assert::same(1, Arrays::first([1, 2, 3]));
});

test('internal array pointer is not affected', function () {
$arr = [1, 2, 3];
end($arr);
Assert::same(1, Arrays::first($arr));
Assert::same(3, current($arr));
});

test('with predicate', function () {
Assert::null(Arrays::first([], fn() => true));
Assert::null(Arrays::first([], fn() => false));
Assert::null(Arrays::first(['' => 'x'], fn() => false));
Assert::null(Arrays::first([null], fn() => true));
Assert::null(Arrays::first([null], fn() => false));
Assert::same(1, Arrays::first([1, 2, 3], fn() => true));
Assert::null(Arrays::first([1, 2, 3], fn() => false));
Assert::same(3, Arrays::first([1, 2, 3], fn($v) => $v > 2));
Assert::same(1, Arrays::first([1, 2, 3], fn($v) => $v < 2));
});

test('predicate arguments', function () {
Arrays::first([2 => 'x'], fn() => Assert::same(['x', 2, [2 => 'x']], func_get_args()));
});
37 changes: 28 additions & 9 deletions tests/Utils/Arrays.last().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,31 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';


Assert::null(Arrays::last([]));
Assert::null(Arrays::last([null]));
Assert::false(Arrays::last([false]));
Assert::same(3, Arrays::last([1, 2, 3]));


$arr = [1, 2, 3];
Assert::same(3, Arrays::last($arr));
Assert::same(1, current($arr));
test('no predicate', function () {
Assert::null(Arrays::last([]));
Assert::null(Arrays::last([null]));
Assert::false(Arrays::last([false]));
Assert::same(3, Arrays::last([1, 2, 3]));
});

test('internal array pointer is not affected', function () {
$arr = [1, 2, 3];
Assert::same(3, Arrays::last($arr));
Assert::same(1, current($arr));
});

test('with predicate', function () {
Assert::null(Arrays::last([], fn() => true));
Assert::null(Arrays::last([], fn() => false));
Assert::null(Arrays::last(['' => 'x'], fn() => false));
Assert::null(Arrays::last([null], fn() => true));
Assert::null(Arrays::last([null], fn() => false));
Assert::same(3, Arrays::last([1, 2, 3], fn() => true));
Assert::null(Arrays::last([1, 2, 3], fn() => false));
Assert::same(3, Arrays::last([1, 2, 3], fn($v) => $v > 2));
Assert::same(1, Arrays::last([1, 2, 3], fn($v) => $v < 2));
});

test('predicate arguments', function () {
Arrays::last([2 => 'x'], fn() => Assert::same(['x', 2, [2 => 'x']], func_get_args()));
});

0 comments on commit 7768aae

Please sign in to comment.