diff --git a/src/Utils/Arrays.php b/src/Utils/Arrays.php index 1cf4d6792..85d76ac4b 100644 --- a/src/Utils/Arrays.php +++ b/src/Utils/Arrays.php @@ -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 $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 $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; } diff --git a/tests/Utils/Arrays.first().phpt b/tests/Utils/Arrays.first().phpt index 6aa4925a1..ea0ebce4b 100644 --- a/tests/Utils/Arrays.first().phpt +++ b/tests/Utils/Arrays.first().phpt @@ -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())); +}); diff --git a/tests/Utils/Arrays.last().phpt b/tests/Utils/Arrays.last().phpt index 58ed411c7..02166800a 100644 --- a/tests/Utils/Arrays.last().phpt +++ b/tests/Utils/Arrays.last().phpt @@ -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())); +});