friendship ended with social-app. php is my new best friend
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
8declare(strict_types=1);
9
10namespace Nette\Utils;
11
12use Nette;
13use function is_array;
14
15
16/**
17 * Utilities for iterables.
18 */
19final class Iterables
20{
21 use Nette\StaticClass;
22
23 /**
24 * Tests for the presence of value.
25 */
26 public static function contains(iterable $iterable, mixed $value): bool
27 {
28 foreach ($iterable as $v) {
29 if ($v === $value) {
30 return true;
31 }
32 }
33 return false;
34 }
35
36
37 /**
38 * Tests for the presence of key.
39 */
40 public static function containsKey(iterable $iterable, mixed $key): bool
41 {
42 foreach ($iterable as $k => $v) {
43 if ($k === $key) {
44 return true;
45 }
46 }
47 return false;
48 }
49
50
51 /**
52 * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
53 * @template K
54 * @template V
55 * @param iterable<K, V> $iterable
56 * @param ?callable(V, K, iterable<K, V>): bool $predicate
57 * @return ?V
58 */
59 public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
60 {
61 foreach ($iterable as $k => $v) {
62 if (!$predicate || $predicate($v, $k, $iterable)) {
63 return $v;
64 }
65 }
66 return $else ? $else() : null;
67 }
68
69
70 /**
71 * 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.
72 * @template K
73 * @template V
74 * @param iterable<K, V> $iterable
75 * @param ?callable(V, K, iterable<K, V>): bool $predicate
76 * @return ?K
77 */
78 public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
79 {
80 foreach ($iterable as $k => $v) {
81 if (!$predicate || $predicate($v, $k, $iterable)) {
82 return $k;
83 }
84 }
85 return $else ? $else() : null;
86 }
87
88
89 /**
90 * Tests whether at least one element in the iterator passes the test implemented by the provided function.
91 * @template K
92 * @template V
93 * @param iterable<K, V> $iterable
94 * @param callable(V, K, iterable<K, V>): bool $predicate
95 */
96 public static function some(iterable $iterable, callable $predicate): bool
97 {
98 foreach ($iterable as $k => $v) {
99 if ($predicate($v, $k, $iterable)) {
100 return true;
101 }
102 }
103 return false;
104 }
105
106
107 /**
108 * Tests whether all elements in the iterator pass the test implemented by the provided function.
109 * @template K
110 * @template V
111 * @param iterable<K, V> $iterable
112 * @param callable(V, K, iterable<K, V>): bool $predicate
113 */
114 public static function every(iterable $iterable, callable $predicate): bool
115 {
116 foreach ($iterable as $k => $v) {
117 if (!$predicate($v, $k, $iterable)) {
118 return false;
119 }
120 }
121 return true;
122 }
123
124
125 /**
126 * Iterator that filters elements according to a given $predicate. Maintains original keys.
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 * Iterator that transforms values by calling $transformer. Maintains original keys.
145 * @template K
146 * @template V
147 * @template R
148 * @param iterable<K, V> $iterable
149 * @param callable(V, K, iterable<K, V>): R $transformer
150 * @return \Generator<K, R>
151 */
152 public static function map(iterable $iterable, callable $transformer): \Generator
153 {
154 foreach ($iterable as $k => $v) {
155 yield $k => $transformer($v, $k, $iterable);
156 }
157 }
158
159
160 /**
161 * Iterator that transforms keys and values by calling $transformer. If it returns null, the element is skipped.
162 * @template K
163 * @template V
164 * @template ResV
165 * @template ResK
166 * @param iterable<K, V> $iterable
167 * @param callable(V, K, iterable<K, V>): ?array{ResV, ResK} $transformer
168 * @return \Generator<ResV, ResK>
169 */
170 public static function mapWithKeys(iterable $iterable, callable $transformer): \Generator
171 {
172 foreach ($iterable as $k => $v) {
173 $pair = $transformer($v, $k, $iterable);
174 if ($pair) {
175 yield $pair[0] => $pair[1];
176 }
177 }
178 }
179
180
181 /**
182 * Wraps around iterator and caches its keys and values during iteration.
183 * This allows the data to be re-iterated multiple times.
184 * @template K
185 * @template V
186 * @param iterable<K, V> $iterable
187 * @return \IteratorAggregate<K, V>
188 */
189 public static function memoize(iterable $iterable): iterable
190 {
191 return new class (self::toIterator($iterable)) implements \IteratorAggregate {
192 public function __construct(
193 private \Iterator $iterator,
194 private array $cache = [],
195 ) {
196 }
197
198
199 public function getIterator(): \Generator
200 {
201 if (!$this->cache) {
202 $this->iterator->rewind();
203 }
204 $i = 0;
205 while (true) {
206 if (isset($this->cache[$i])) {
207 [$k, $v] = $this->cache[$i];
208 } elseif ($this->iterator->valid()) {
209 $k = $this->iterator->key();
210 $v = $this->iterator->current();
211 $this->iterator->next();
212 $this->cache[$i] = [$k, $v];
213 } else {
214 break;
215 }
216 yield $k => $v;
217 $i++;
218 }
219 }
220 };
221 }
222
223
224 /**
225 * Creates an iterator from anything that is iterable.
226 * @template K
227 * @template V
228 * @param iterable<K, V> $iterable
229 * @return \Iterator<K, V>
230 */
231 public static function toIterator(iterable $iterable): \Iterator
232 {
233 return match (true) {
234 $iterable instanceof \Iterator => $iterable,
235 $iterable instanceof \IteratorAggregate => self::toIterator($iterable->getIterator()),
236 is_array($iterable) => new \ArrayIterator($iterable),
237 default => throw new Nette\ShouldNotHappenException,
238 };
239 }
240}