Skip to content

Commit 19db401

Browse files
committed
added Iterables
1 parent 6b3c1d8 commit 19db401

File tree

9 files changed

+569
-0
lines changed

9 files changed

+569
-0
lines changed

src/Utils/Iterables.php

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Utils;
11+
12+
use Nette;
13+
14+
15+
/**
16+
* Utilities for iterables.
17+
*/
18+
final class Iterables
19+
{
20+
use Nette\StaticClass;
21+
22+
/**
23+
* Tests for the presence of value.
24+
*/
25+
public static function contains(iterable $iterable, mixed $value): bool
26+
{
27+
foreach ($iterable as $v) {
28+
if ($v === $value) {
29+
return true;
30+
}
31+
}
32+
return false;
33+
}
34+
35+
36+
/**
37+
* Tests for the presence of key.
38+
*/
39+
public static function containsKey(iterable $iterable, mixed $key): bool
40+
{
41+
foreach ($iterable as $k => $v) {
42+
if ($k === $key) {
43+
return true;
44+
}
45+
}
46+
return false;
47+
}
48+
49+
50+
/**
51+
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
52+
* The $predicate has the signature `function ($value, $key, $iterable): bool`.
53+
* @template T
54+
* @param iterable<T> $iterable
55+
* @return ?T
56+
*/
57+
public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
58+
{
59+
foreach ($iterable as $k => $v) {
60+
if (!$predicate || $predicate($v, $k, $iterable)) {
61+
return $v;
62+
}
63+
}
64+
return $else ? $else() : null;
65+
}
66+
67+
68+
/**
69+
* 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.
70+
* The $predicate has the signature `function ($value, $key, $iterable): bool`.
71+
* @template T
72+
* @param iterable<T, mixed> $iterable
73+
* @return ?T
74+
*/
75+
public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
76+
{
77+
foreach ($iterable as $k => $v) {
78+
if (!$predicate || $predicate($v, $k, $iterable)) {
79+
return $k;
80+
}
81+
}
82+
return $else ? $else() : null;
83+
}
84+
85+
86+
/**
87+
* Tests whether at least one element in the array passes the test implemented by the
88+
* provided callback with signature `function ($value, $key, $iterable): bool`.
89+
* @template K
90+
* @template V
91+
* @param iterable<K, V> $iterable
92+
* @param callable(V, K, iterable<K, V>): bool $predicate
93+
*/
94+
public static function some(iterable $iterable, callable $predicate): bool
95+
{
96+
foreach ($iterable as $k => $v) {
97+
if ($predicate($v, $k, $iterable)) {
98+
return true;
99+
}
100+
}
101+
return false;
102+
}
103+
104+
105+
/**
106+
* Tests whether all elements in the array pass the test implemented by the provided function,
107+
* which has the signature `function ($value, $key, $iterable): bool`.
108+
* @template K
109+
* @template V
110+
* @param iterable<K, V> $iterable
111+
* @param callable(V, K, iterable<K, V>): bool $predicate
112+
*/
113+
public static function every(iterable $iterable, callable $predicate): bool
114+
{
115+
foreach ($iterable as $k => $v) {
116+
if (!$predicate($v, $k, $iterable)) {
117+
return false;
118+
}
119+
}
120+
return true;
121+
}
122+
123+
124+
/**
125+
* Returns a new array containing all key-value pairs matching the given $predicate.
126+
* The callback has the signature `function ($value, $key, $iterable): bool`.
127+
* @template K
128+
* @template V
129+
* @param iterable<K, V> $iterable
130+
* @param callable(V, K, iterable<K, V>): bool $predicate
131+
* @return \Generator<K, V>
132+
*/
133+
public static function filter(iterable $iterable, callable $predicate): \Generator
134+
{
135+
foreach ($iterable as $k => $v) {
136+
if ($predicate($v, $k, $iterable)) {
137+
yield $k => $v;
138+
}
139+
}
140+
}
141+
142+
143+
/**
144+
* Calls $transform on all elements in the array and returns the array of return values.
145+
* The callback has the signature `function ($value, $key, $iterable): bool`.
146+
* @template K
147+
* @template V
148+
* @template R
149+
* @param iterable<K, V> $iterable
150+
* @param callable(V, K, iterable<K, V>): R $transformer
151+
* @return \Generator<K, R>
152+
*/
153+
public static function map(iterable $iterable, callable $transformer): \Generator
154+
{
155+
foreach ($iterable as $k => $v) {
156+
yield $k => $transformer($v, $k, $iterable);
157+
}
158+
}
159+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Utils\Iterables;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
Assert::false(Iterables::contains(new ArrayIterator([]), 'a'));
12+
Assert::true(Iterables::contains(new ArrayIterator(['a']), 'a'));
13+
Assert::true(Iterables::contains(new ArrayIterator([1, 2, 'a']), 'a'));
14+
Assert::false(Iterables::contains(new ArrayIterator([1, 2, 3]), 'a'));
15+
Assert::false(Iterables::contains(new ArrayIterator([1, 2, 3]), '1'));
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Utils\Iterables;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
Assert::false(Iterables::containsKey(new ArrayIterator([]), 'a'));
12+
Assert::true(Iterables::containsKey(new ArrayIterator(['a']), 0));
13+
Assert::true(Iterables::containsKey(new ArrayIterator(['x' => 1, 'y' => 2, 'z' => 3]), 'y'));
14+
Assert::false(Iterables::containsKey(new ArrayIterator(['x' => 1, 'y' => 2, 'z' => 3]), ''));
15+
Assert::false(Iterables::containsKey(new ArrayIterator([1, 2, 3]), '1'));

tests/Utils/Iterables.every().phpt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Iterables::every()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Iterables;
10+
use Tester\Assert;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
test('', function () {
16+
$arr = new ArrayIterator([]);
17+
$log = [];
18+
$res = Iterables::every(
19+
$arr,
20+
function ($v, $k, $arr) use (&$log) {
21+
$log[] = func_get_args();
22+
return false;
23+
},
24+
);
25+
Assert::true($res);
26+
Assert::same([], $log);
27+
});
28+
29+
test('', function () {
30+
$arr = new ArrayIterator([]);
31+
$log = [];
32+
$res = Iterables::every(
33+
$arr,
34+
function ($v, $k, $arr) use (&$log) {
35+
$log[] = func_get_args();
36+
return true;
37+
},
38+
);
39+
Assert::true($res);
40+
Assert::same([], $log);
41+
});
42+
43+
test('', function () {
44+
$arr = new ArrayIterator(['a', 'b']);
45+
$log = [];
46+
$res = Iterables::every(
47+
$arr,
48+
function ($v, $k, $arr) use (&$log) {
49+
$log[] = func_get_args();
50+
return false;
51+
},
52+
);
53+
Assert::false($res);
54+
Assert::same([['a', 0, $arr]], $log);
55+
});
56+
57+
test('', function () {
58+
$arr = new ArrayIterator(['a', 'b']);
59+
$log = [];
60+
$res = Iterables::every(
61+
$arr,
62+
function ($v, $k, $arr) use (&$log) {
63+
$log[] = func_get_args();
64+
return true;
65+
},
66+
);
67+
Assert::true($res);
68+
Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log);
69+
});
70+
71+
test('', function () {
72+
$arr = new ArrayIterator(['a', 'b']);
73+
$log = [];
74+
$res = Iterables::every(
75+
$arr,
76+
function ($v, $k, $arr) use (&$log) {
77+
$log[] = func_get_args();
78+
return $v === 'a';
79+
},
80+
);
81+
Assert::false($res);
82+
Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log);
83+
});
84+
85+
test('', function () {
86+
$arr = new ArrayIterator(['x' => 'a', 'y' => 'b']);
87+
$log = [];
88+
$res = Iterables::every(
89+
$arr,
90+
function ($v, $k, $arr) use (&$log) {
91+
$log[] = func_get_args();
92+
return true;
93+
},
94+
);
95+
Assert::true($res);
96+
Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log);
97+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Iterables::filter()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Iterables;
10+
use Tester\Assert;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
Assert::same(
16+
['a' => 1, 'b' => 2],
17+
iterator_to_array(Iterables::filter(
18+
new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]),
19+
fn($v) => $v < 3,
20+
)),
21+
);
22+
23+
Assert::same(
24+
['c' => 3],
25+
iterator_to_array(Iterables::filter(
26+
new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]),
27+
fn($v, $k) => $k === 'c',
28+
)),
29+
);
30+
31+
Assert::same(
32+
['a' => 1, 'b' => 2, 'c' => 3],
33+
iterator_to_array(Iterables::filter(
34+
$it = new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]),
35+
fn($v, $k, $a) => $a === $it,
36+
)),
37+
);
38+
39+
Assert::same(
40+
[],
41+
iterator_to_array(Iterables::filter(
42+
new ArrayIterator([]),
43+
fn() => true,
44+
)),
45+
);

tests/Utils/Iterables.first().phpt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Utils\Iterables;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
test('no predicate', function () {
12+
Assert::null(Iterables::first(new ArrayIterator([])));
13+
Assert::null(Iterables::first(new ArrayIterator([null])));
14+
Assert::false(Iterables::first(new ArrayIterator([false])));
15+
Assert::same(1, Iterables::first(new ArrayIterator([1, 2, 3])));
16+
});
17+
18+
test('internal array pointer is not affected', function () {
19+
$arr = [1, 2, 3];
20+
end($arr);
21+
Assert::same(1, Iterables::first($arr));
22+
Assert::same(3, current($arr));
23+
});
24+
25+
test('with predicate', function () {
26+
Assert::null(Iterables::first([], fn() => true));
27+
Assert::null(Iterables::first([], fn() => false));
28+
Assert::null(Iterables::first(['' => 'x'], fn() => false));
29+
Assert::null(Iterables::first([null], fn() => true));
30+
Assert::null(Iterables::first([null], fn() => false));
31+
Assert::same(1, Iterables::first([1, 2, 3], fn() => true));
32+
Assert::null(Iterables::first([1, 2, 3], fn() => false));
33+
Assert::same(3, Iterables::first([1, 2, 3], fn($v) => $v > 2));
34+
Assert::same(1, Iterables::first([1, 2, 3], fn($v) => $v < 2));
35+
});
36+
37+
test('predicate arguments', function () {
38+
Iterables::first([2 => 'x'], fn() => Assert::same(['x', 2, [2 => 'x']], func_get_args()));
39+
});
40+
41+
test('else', function () {
42+
Assert::same(123, Iterables::first(new ArrayIterator([]), else: fn() => 123));
43+
});

0 commit comments

Comments
 (0)