-
-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
569 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
<?php | ||
|
||
/** | ||
* This file is part of the Nette Framework (https://nette.org) | ||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com) | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nette\Utils; | ||
|
||
use Nette; | ||
|
||
|
||
/** | ||
* Utilities for iterables. | ||
*/ | ||
final class Iterables | ||
{ | ||
use Nette\StaticClass; | ||
|
||
/** | ||
* Tests for the presence of value. | ||
*/ | ||
public static function contains(iterable $iterable, mixed $value): bool | ||
{ | ||
foreach ($iterable as $v) { | ||
if ($v === $value) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
|
||
/** | ||
* Tests for the presence of key. | ||
*/ | ||
public static function containsKey(iterable $iterable, mixed $key): bool | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
if ($k === $key) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
|
||
/** | ||
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. | ||
* The $predicate has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`. | ||
* @template T | ||
* @param iterable<T> $iterable | ||
* @return ?T | ||
*/ | ||
public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
if (!$predicate || $predicate($v, $k, $iterable)) { | ||
return $v; | ||
} | ||
} | ||
return $else ? $else() : null; | ||
} | ||
|
||
|
||
/** | ||
* Returns the key of first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. | ||
* The $predicate has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`. | ||
* @template T | ||
* @param iterable<T, mixed> $iterable | ||
* @return ?T | ||
*/ | ||
public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
if (!$predicate || $predicate($v, $k, $iterable)) { | ||
return $k; | ||
} | ||
} | ||
return $else ? $else() : null; | ||
} | ||
|
||
|
||
/** | ||
* Tests whether at least one element in the iterator passes the test implemented by the | ||
* provided callback with signature `function (mixed $value, mixed $key, iterable $iterable): bool`. | ||
* @template K | ||
* @template V | ||
* @param iterable<K, V> $iterable | ||
* @param callable(V, K, iterable<K, V>): bool $predicate | ||
*/ | ||
public static function some(iterable $iterable, callable $predicate): bool | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
if ($predicate($v, $k, $iterable)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
|
||
/** | ||
* Tests whether all elements in the iterator pass the test implemented by the provided function, | ||
* which has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`. | ||
* @template K | ||
* @template V | ||
* @param iterable<K, V> $iterable | ||
* @param callable(V, K, iterable<K, V>): bool $predicate | ||
*/ | ||
public static function every(iterable $iterable, callable $predicate): bool | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
if (!$predicate($v, $k, $iterable)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
|
||
/** | ||
* Iterator that filters elements according to a given $predicate. Maintains original keys. | ||
* The callback has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`. | ||
* @template K | ||
* @template V | ||
* @param iterable<K, V> $iterable | ||
* @param callable(V, K, iterable<K, V>): bool $predicate | ||
* @return \Generator<K, V> | ||
*/ | ||
public static function filter(iterable $iterable, callable $predicate): \Generator | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
if ($predicate($v, $k, $iterable)) { | ||
yield $k => $v; | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Iterator that transforms values by calling $transformer. Maintains original keys. | ||
* The callback has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`. | ||
* @template K | ||
* @template V | ||
* @template R | ||
* @param iterable<K, V> $iterable | ||
* @param callable(V, K, iterable<K, V>): R $transformer | ||
* @return \Generator<K, R> | ||
*/ | ||
public static function map(iterable $iterable, callable $transformer): \Generator | ||
{ | ||
foreach ($iterable as $k => $v) { | ||
yield $k => $transformer($v, $k, $iterable); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Iterables; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
Assert::false(Iterables::contains(new ArrayIterator([]), 'a')); | ||
Assert::true(Iterables::contains(new ArrayIterator(['a']), 'a')); | ||
Assert::true(Iterables::contains(new ArrayIterator([1, 2, 'a']), 'a')); | ||
Assert::false(Iterables::contains(new ArrayIterator([1, 2, 3]), 'a')); | ||
Assert::false(Iterables::contains(new ArrayIterator([1, 2, 3]), '1')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Iterables; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
Assert::false(Iterables::containsKey(new ArrayIterator([]), 'a')); | ||
Assert::true(Iterables::containsKey(new ArrayIterator(['a']), 0)); | ||
Assert::true(Iterables::containsKey(new ArrayIterator(['x' => 1, 'y' => 2, 'z' => 3]), 'y')); | ||
Assert::false(Iterables::containsKey(new ArrayIterator(['x' => 1, 'y' => 2, 'z' => 3]), '')); | ||
Assert::false(Iterables::containsKey(new ArrayIterator([1, 2, 3]), '1')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<?php | ||
|
||
/** | ||
* Test: Nette\Utils\Iterables::every() | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Iterables; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
test('', function () { | ||
$arr = new ArrayIterator([]); | ||
$log = []; | ||
$res = Iterables::every( | ||
$arr, | ||
function ($v, $k, $arr) use (&$log) { | ||
$log[] = func_get_args(); | ||
return false; | ||
}, | ||
); | ||
Assert::true($res); | ||
Assert::same([], $log); | ||
}); | ||
|
||
test('', function () { | ||
$arr = new ArrayIterator([]); | ||
$log = []; | ||
$res = Iterables::every( | ||
$arr, | ||
function ($v, $k, $arr) use (&$log) { | ||
$log[] = func_get_args(); | ||
return true; | ||
}, | ||
); | ||
Assert::true($res); | ||
Assert::same([], $log); | ||
}); | ||
|
||
test('', function () { | ||
$arr = new ArrayIterator(['a', 'b']); | ||
$log = []; | ||
$res = Iterables::every( | ||
$arr, | ||
function ($v, $k, $arr) use (&$log) { | ||
$log[] = func_get_args(); | ||
return false; | ||
}, | ||
); | ||
Assert::false($res); | ||
Assert::same([['a', 0, $arr]], $log); | ||
}); | ||
|
||
test('', function () { | ||
$arr = new ArrayIterator(['a', 'b']); | ||
$log = []; | ||
$res = Iterables::every( | ||
$arr, | ||
function ($v, $k, $arr) use (&$log) { | ||
$log[] = func_get_args(); | ||
return true; | ||
}, | ||
); | ||
Assert::true($res); | ||
Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log); | ||
}); | ||
|
||
test('', function () { | ||
$arr = new ArrayIterator(['a', 'b']); | ||
$log = []; | ||
$res = Iterables::every( | ||
$arr, | ||
function ($v, $k, $arr) use (&$log) { | ||
$log[] = func_get_args(); | ||
return $v === 'a'; | ||
}, | ||
); | ||
Assert::false($res); | ||
Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log); | ||
}); | ||
|
||
test('', function () { | ||
$arr = new ArrayIterator(['x' => 'a', 'y' => 'b']); | ||
$log = []; | ||
$res = Iterables::every( | ||
$arr, | ||
function ($v, $k, $arr) use (&$log) { | ||
$log[] = func_get_args(); | ||
return true; | ||
}, | ||
); | ||
Assert::true($res); | ||
Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
/** | ||
* Test: Nette\Utils\Iterables::filter() | ||
*/ | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Iterables; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
Assert::same( | ||
['a' => 1, 'b' => 2], | ||
iterator_to_array(Iterables::filter( | ||
new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]), | ||
fn($v) => $v < 3, | ||
)), | ||
); | ||
|
||
Assert::same( | ||
['c' => 3], | ||
iterator_to_array(Iterables::filter( | ||
new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]), | ||
fn($v, $k) => $k === 'c', | ||
)), | ||
); | ||
|
||
Assert::same( | ||
['a' => 1, 'b' => 2, 'c' => 3], | ||
iterator_to_array(Iterables::filter( | ||
$it = new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]), | ||
fn($v, $k, $a) => $a === $it, | ||
)), | ||
); | ||
|
||
Assert::same( | ||
[], | ||
iterator_to_array(Iterables::filter( | ||
new ArrayIterator([]), | ||
fn() => true, | ||
)), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Nette\Utils\Iterables; | ||
use Tester\Assert; | ||
|
||
require __DIR__ . '/../bootstrap.php'; | ||
|
||
|
||
test('no predicate', function () { | ||
Assert::null(Iterables::first(new ArrayIterator([]))); | ||
Assert::null(Iterables::first(new ArrayIterator([null]))); | ||
Assert::false(Iterables::first(new ArrayIterator([false]))); | ||
Assert::same(1, Iterables::first(new ArrayIterator([1, 2, 3]))); | ||
}); | ||
|
||
test('internal array pointer is not affected', function () { | ||
$arr = [1, 2, 3]; | ||
end($arr); | ||
Assert::same(1, Iterables::first($arr)); | ||
Assert::same(3, current($arr)); | ||
}); | ||
|
||
test('with predicate', function () { | ||
Assert::null(Iterables::first([], fn() => true)); | ||
Assert::null(Iterables::first([], fn() => false)); | ||
Assert::null(Iterables::first(['' => 'x'], fn() => false)); | ||
Assert::null(Iterables::first([null], fn() => true)); | ||
Assert::null(Iterables::first([null], fn() => false)); | ||
Assert::same(1, Iterables::first([1, 2, 3], fn() => true)); | ||
Assert::null(Iterables::first([1, 2, 3], fn() => false)); | ||
Assert::same(3, Iterables::first([1, 2, 3], fn($v) => $v > 2)); | ||
Assert::same(1, Iterables::first([1, 2, 3], fn($v) => $v < 2)); | ||
}); | ||
|
||
test('predicate arguments', function () { | ||
Iterables::first([2 => 'x'], fn() => Assert::same(['x', 2, [2 => 'x']], func_get_args())); | ||
}); | ||
|
||
test('else', function () { | ||
Assert::same(123, Iterables::first(new ArrayIterator([]), else: fn() => 123)); | ||
}); |
Oops, something went wrong.