1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Boudewijn Schoon <[email protected]> |
4
|
|
|
* @copyright Zicht Online <http://zicht.nl> |
5
|
|
|
*/ |
6
|
|
|
|
7
|
|
|
namespace Zicht\Itertools; |
8
|
|
|
|
9
|
|
|
use Zicht\Itertools\conversions; |
10
|
|
|
use Zicht\Itertools\lib\AccumulateIterator; |
11
|
|
|
use Zicht\Itertools\lib\ChainIterator; |
12
|
|
|
use Zicht\Itertools\lib\CountIterator; |
13
|
|
|
use Zicht\Itertools\lib\CycleIterator; |
14
|
|
|
use Zicht\Itertools\lib\FilterIterator; |
15
|
|
|
use Zicht\Itertools\lib\GroupbyIterator; |
16
|
|
|
use Zicht\Itertools\lib\IterableIterator; |
17
|
|
|
use Zicht\Itertools\lib\MapByIterator; |
18
|
|
|
use Zicht\Itertools\lib\MapIterator; |
19
|
|
|
use Zicht\Itertools\lib\RepeatIterator; |
20
|
|
|
use Zicht\Itertools\lib\ReversedIterator; |
21
|
|
|
use Zicht\Itertools\lib\SliceIterator; |
22
|
|
|
use Zicht\Itertools\lib\SortedIterator; |
23
|
|
|
use Zicht\Itertools\lib\UniqueIterator; |
24
|
|
|
use Zicht\Itertools\lib\ZipIterator; |
25
|
|
|
use Zicht\Itertools\reductions; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Transforms anything into an \Iterator or throws an \InvalidArgumentException |
29
|
|
|
* |
30
|
|
|
* @param array|string|\Iterator $iterable |
31
|
|
|
* @return \Iterator |
32
|
|
|
* |
33
|
|
|
* @deprecated Use conversions\mixed_to_iterator instead, will be removed in version 3.0 |
34
|
|
|
*/ |
35
|
|
|
function mixedToIterator($iterable) |
36
|
|
|
{ |
37
|
|
|
return conversions\mixed_to_iterator($iterable); |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Try to transforms something into a \Closure |
42
|
|
|
* |
43
|
|
|
* @param null|\Closure $closure |
44
|
|
|
* @return \Closure |
45
|
|
|
* |
46
|
|
|
* @deprecated Use conversions\mixed_to_closure instead, will be removed in version 3.0 |
47
|
|
|
*/ |
48
|
|
|
function mixedToClosure($closure) |
49
|
|
|
{ |
50
|
|
|
return conversions\mixed_to_closure($closure); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Try to transforms something into a \Closure that gets a value from $strategy |
55
|
|
|
* |
56
|
|
|
* @param null|string|\Closure $strategy |
57
|
|
|
* @return \Closure |
58
|
|
|
* |
59
|
|
|
* @deprecated Use Conversions::mixedToValueGetter instead, will be removed in version 3.0 |
60
|
|
|
*/ |
61
|
|
|
function mixedToValueGetter($strategy) |
62
|
|
|
{ |
63
|
|
|
return conversions\mixed_to_value_getter($strategy); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Try to transform something into a \Closure |
68
|
|
|
* |
69
|
|
|
* @param string|\Closure $closure |
70
|
|
|
* @return \Closure |
71
|
|
|
* |
72
|
|
|
* @deprecated Will be removed in version 3.0, no replacement will be needed |
73
|
|
|
*/ |
74
|
|
|
function mixedToOperationClosure($closure) |
75
|
|
|
{ |
76
|
|
|
if (is_string($closure)) { |
77
|
|
|
$closure = reductions\getReduction($closure, $closure); |
|
|
|
|
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
if (!($closure instanceof \Closure)) { |
81
|
|
|
throw new \InvalidArgumentException('Argument $closure must be a \Closure or string (i.e. "add", "join", etc)'); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return $closure; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Make an iterator that returns accumulated sums |
89
|
|
|
* |
90
|
|
|
* If the optional $closure argument is supplied, it should be a string: |
91
|
|
|
* add, sub, mul, min, or max. Or it can be a \Closure taking two |
92
|
|
|
* arguments that will be used to instead of addition. |
93
|
|
|
* |
94
|
|
|
* > accumulate([1,2,3,4,5]) |
95
|
|
|
* 1 3 6 10 15 |
96
|
|
|
* |
97
|
|
|
* > accumulate(['One', 'Two', 'Three'], function ($a, $b) { return $a . $b; }) |
98
|
|
|
* 'One' 'OneTwo' 'OneTwoThree' |
99
|
|
|
* |
100
|
|
|
* @param array|string|\Iterator $iterable |
101
|
|
|
* @param string|\Closure $closure |
102
|
|
|
* @return AccumulateIterator |
103
|
|
|
*/ |
104
|
|
|
function accumulate($iterable, $closure = 'add') |
105
|
|
|
{ |
106
|
17 |
|
return new AccumulateIterator( |
107
|
|
|
conversions\mixed_to_iterator($iterable), |
108
|
15 |
|
$closure instanceof \Closure ? $closure : reductions\getReduction($closure) |
|
|
|
|
109
|
|
|
); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Reduce an iterator to a single value |
114
|
|
|
* |
115
|
|
|
* > reduce([1,2,3]) |
116
|
|
|
* 6 |
117
|
|
|
* |
118
|
|
|
* > reduce([1,2,3], 'max') |
119
|
|
|
* 3 |
120
|
|
|
* |
121
|
|
|
* > reduce([1,2,3], 'sub', 10) |
122
|
|
|
* 4 |
123
|
|
|
* |
124
|
|
|
* > reduce([], 'min', 1) |
125
|
|
|
* 1 |
126
|
|
|
* |
127
|
|
|
* @param array|string|\Iterator $iterable |
128
|
|
|
* @param string|\Closure $closure |
129
|
|
|
* @param mixed $initializer |
130
|
|
|
* @return mixed |
131
|
|
|
*/ |
132
|
|
|
function reduce($iterable, $closure = 'add', $initializer = null) |
133
|
|
|
{ |
134
|
58 |
|
$closure = $closure instanceof \Closure ? $closure : reductions\get_reduction($closure); |
|
|
|
|
135
|
55 |
|
$iterable = conversions\mixed_to_iterator($iterable); |
136
|
52 |
|
$iterable->rewind(); |
137
|
|
|
|
138
|
52 |
|
if (null === $initializer) { |
139
|
44 |
|
if ($iterable->valid()) { |
140
|
43 |
|
$initializer = $iterable->current(); |
141
|
43 |
|
$iterable->next(); |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
145
|
52 |
|
$accumulatedValue = $initializer; |
146
|
52 |
|
while ($iterable->valid()) { |
147
|
48 |
|
$accumulatedValue = $closure($accumulatedValue, $iterable->current()); |
148
|
47 |
|
$iterable->next(); |
149
|
|
|
} |
150
|
|
|
|
151
|
51 |
|
return $accumulatedValue; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Make an iterator that contains all consecutive elements from all provided iterables. |
156
|
|
|
* |
157
|
|
|
* The resulting iterator contains elements from the first iterable in the parameters |
158
|
|
|
* until it is exhausted, then proceeds to the next iterable in the parameters, until |
159
|
|
|
* all the iterables are exhausted. Used for creating consecutive |
160
|
|
|
* sequences as a single sequence |
161
|
|
|
* |
162
|
|
|
* > chain([1, 2, 3], [4, 5, 6]) |
163
|
|
|
* 1 2 3 4 5 6 |
164
|
|
|
* |
165
|
|
|
* > chain('ABC', 'DEF') |
166
|
|
|
* A B C D E F |
167
|
|
|
* |
168
|
|
|
* @return ChainIterator |
169
|
|
|
*/ |
170
|
|
View Code Duplication |
function chain() |
|
|
|
|
171
|
|
|
{ |
172
|
|
|
// note, once we stop supporting php 5.5, we can rewrite the code below |
173
|
|
|
// to the chain(...$iterables) structure. |
174
|
|
|
// http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list |
175
|
|
|
|
176
|
14 |
|
$iterables = array_map( |
177
|
|
|
function ($iterable) { |
178
|
13 |
|
return conversions\mixed_to_iterator($iterable); |
179
|
14 |
|
}, |
180
|
|
|
func_get_args() |
181
|
|
|
); |
182
|
11 |
|
$reflectorClass = new \ReflectionClass('\Zicht\Itertools\lib\ChainIterator'); |
183
|
11 |
|
return $reflectorClass->newInstanceArgs($iterables); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Make an iterator that returns evenly spaced values starting with |
188
|
|
|
* number $start |
189
|
|
|
* |
190
|
|
|
* > count(10) |
191
|
|
|
* 10 11 12 13 14 ... |
192
|
|
|
* |
193
|
|
|
* > count(2.5, 0.5) |
194
|
|
|
* 2.5 3.0 3.5 4.0 ... |
195
|
|
|
* |
196
|
|
|
* @param int|float $start |
197
|
|
|
* @param int|float $step |
198
|
|
|
* @return CountIterator |
199
|
|
|
*/ |
200
|
|
|
function count($start = 0, $step = 1) |
201
|
|
|
{ |
202
|
36 |
|
if (!(is_int($start) || is_float($start))) { |
203
|
3 |
|
throw new \InvalidArgumentException('Argument $start must be an integer or float'); |
204
|
|
|
} |
205
|
|
|
|
206
|
33 |
|
if (!(is_int($step) || is_float($step))) { |
207
|
3 |
|
throw new \InvalidArgumentException('Argument $step must be an integer or float'); |
208
|
|
|
} |
209
|
|
|
|
210
|
30 |
|
return new CountIterator($start, $step); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Make an iterator returning elements from the $iterable and saving a |
215
|
|
|
* copy of each. When the iterable is exhausted, return elements from |
216
|
|
|
* the saved copy, repeating indefinitely |
217
|
|
|
* |
218
|
|
|
* > cycle('ABCD') |
219
|
|
|
* A B C D A B C D A B C D ... |
220
|
|
|
* |
221
|
|
|
* @param array|string|\Iterator $iterable |
222
|
|
|
* @return CycleIterator |
223
|
|
|
*/ |
224
|
|
|
function cycle($iterable) |
225
|
|
|
{ |
226
|
13 |
|
return new CycleIterator(conversions\mixed_to_iterator($iterable)); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Make an iterator returning values from $iterable and keys from |
231
|
|
|
* $strategy |
232
|
|
|
* |
233
|
|
|
* When $strategy is a string, the key is obtained through one of |
234
|
|
|
* the following: |
235
|
|
|
* 1. $value->{$strategy}, when $value is an object and |
236
|
|
|
* $strategy is an existing property, |
237
|
|
|
* 2. call $value->{$strategy}(), when $value is an object and |
238
|
|
|
* $strategy is an existing method, |
239
|
|
|
* 3. $value[$strategy], when $value is an array and $strategy |
240
|
|
|
* is an existing key, |
241
|
|
|
* 4. otherwise the key will default to null. |
242
|
|
|
* |
243
|
|
|
* Alternatively $strategy can be a closure. In this case the |
244
|
|
|
* $strategy closure is called with each value in $iterable and the |
245
|
|
|
* key will be its return value. |
246
|
|
|
* |
247
|
|
|
* > $list = [['id'=>1, 'title'=>'one'], ['id'=>2, 'title'=>'two']] |
248
|
|
|
* > mapBy('id', $list) |
249
|
|
|
* 1=>['id'=>1, 'title'=>'one'] 2=>['id'=>2, 'title'=>'two'] |
250
|
|
|
* |
251
|
|
|
* @param string|\Closure $strategy |
252
|
|
|
* @param array|string|\Iterator $iterable |
253
|
|
|
* @return MapByIterator |
254
|
|
|
*/ |
255
|
|
|
function map_by($strategy, $iterable) |
256
|
|
|
{ |
257
|
|
|
// In version 3.0 mapBy and map_by will be removed |
258
|
|
|
// as its functionality will be merged into map. |
259
|
|
|
|
260
|
24 |
|
return new MapByIterator( |
261
|
|
|
conversions\mixed_to_value_getter($strategy), |
262
|
|
|
conversions\mixed_to_iterator($iterable) |
263
|
|
|
); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Make an iterator returning values from $iterable and keys from |
268
|
|
|
* $strategy |
269
|
|
|
* |
270
|
|
|
* @param string|\Closure $strategy |
271
|
|
|
* @param array|string|\Iterator $iterable |
272
|
|
|
* @return MapByIterator |
273
|
|
|
* |
274
|
|
|
* @deprecated Please use group_by(...)->values() instead (when flatten true), will be removed in version 3.0 |
275
|
|
|
*/ |
276
|
|
|
function mapBy($strategy, $iterable) |
277
|
|
|
{ |
278
|
|
|
return map_by($strategy, $iterable); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Make an iterator returning values from $iterable and keys from |
283
|
|
|
* $strategy |
284
|
|
|
* |
285
|
|
|
* @param string|\Closure $strategy |
286
|
|
|
* @param array|string|\Iterator $iterable |
287
|
|
|
* @return MapByIterator |
288
|
|
|
* |
289
|
|
|
* @deprecated use mapBy() in stead, will be removed in version 3.0 |
290
|
|
|
*/ |
291
|
|
|
function keyCallback($strategy, $iterable) |
292
|
|
|
{ |
293
|
|
|
return mapBy($strategy, $iterable); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Make an iterator that applies $strategy to every entry in the iterables |
298
|
|
|
* |
299
|
|
|
* If one iterable is passed, $strategy is called for each entry in |
300
|
|
|
* the $iterable, where the first argument is the value and the |
301
|
|
|
* second argument is the key of the entry |
302
|
|
|
* |
303
|
|
|
* If more than one iterable is passed, $strategy is called with the |
304
|
|
|
* values and the keys from the iterables. For example, the first |
305
|
|
|
* call to $strategy will be: |
306
|
|
|
* $strategy($value_iterable1, $value_iterable2, $key_iterable2, $key_iterable2) |
307
|
|
|
* |
308
|
|
|
* With multiple iterables, the iterator stops when the shortest |
309
|
|
|
* iterable is exhausted. |
310
|
|
|
* |
311
|
|
|
* > $minimal = function ($value) { return min(3, $value); }; |
312
|
|
|
* > map($minimal, [1, 2, 3, 4]); |
313
|
|
|
* 3 3 3 4 |
314
|
|
|
* |
315
|
|
|
* > $average = function ($value1, $value2) { return ($value1 + $value2) / 2; }; |
316
|
|
|
* > map($average, [1, 2, 3], [4, 5, 6]); |
317
|
|
|
* 2.5 3.5 4.5 |
318
|
|
|
* |
319
|
|
|
* @param null|string|\Closure $strategy |
320
|
|
|
* @param array|string|\Iterator $iterable Additional $iterable parameters may follow |
321
|
|
|
* @return MapIterator |
322
|
|
|
*/ |
323
|
|
|
function map($strategy, $iterable) |
|
|
|
|
324
|
|
|
{ |
325
|
|
|
// note, once we stop supporting php 5.5, we can rewrite the code below |
326
|
|
|
// to the map(...$iterables) structure. |
327
|
|
|
// http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list |
328
|
|
|
|
329
|
60 |
|
$iterables = array_map( |
330
|
|
|
function ($iterable) { |
331
|
60 |
|
return conversions\mixed_to_iterator($iterable); |
332
|
60 |
|
}, |
333
|
60 |
|
array_slice(func_get_args(), 1) |
334
|
|
|
); |
335
|
60 |
|
$reflectorClass = new \ReflectionClass('\Zicht\Itertools\lib\MapIterator'); |
336
|
60 |
|
return $reflectorClass->newInstanceArgs(array_merge(array(conversions\mixed_to_value_getter($strategy)), $iterables)); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Select values from the iterator by applying a function to each of the iterator values, i.e., mapping it to the |
341
|
|
|
* value with a strategy based on the input, similar to map_key |
342
|
|
|
* |
343
|
|
|
* @param null|string|\Closure $strategy |
344
|
|
|
* @param array|string|\Iterator $iterable |
345
|
|
|
* @param bool $flatten |
346
|
|
|
* @return array|MapIterator |
347
|
|
|
* |
348
|
|
|
* @deprecated Please use map(...)->values() instead (when flatten true), will be removed in version 3.0 |
349
|
|
|
*/ |
350
|
|
|
function select($strategy, $iterable, $flatten = true) |
351
|
|
|
{ |
352
|
|
|
if (!is_bool($flatten)) { |
353
|
|
|
throw new \InvalidArgumentException('Argument $FLATTEN must be a boolean'); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
$ret = new MapIterator( |
357
|
|
|
conversions\mixed_to_value_getter($strategy), |
358
|
|
|
conversions\mixed_to_iterator($iterable) |
359
|
|
|
); |
360
|
|
|
if ($flatten) { |
361
|
|
|
return $ret->values(); |
362
|
|
|
} |
363
|
|
|
return $ret; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Make an iterator that returns $mixed over and over again. Runs |
368
|
|
|
* indefinitely unless the $times argument is specified |
369
|
|
|
* |
370
|
|
|
* > repeat(2) |
371
|
|
|
* 2 2 2 2 2 ... |
372
|
|
|
* |
373
|
|
|
* > repeat(10, 3) |
374
|
|
|
* 10 10 10 |
375
|
|
|
* |
376
|
|
|
* @param mixed $mixed |
377
|
|
|
* @param null|int $times |
378
|
|
|
* @return RepeatIterator |
379
|
|
|
*/ |
380
|
|
|
function repeat($mixed, $times = null) |
381
|
|
|
{ |
382
|
9 |
|
if (!(is_null($times) || (is_int($times) && $times >= 0))) { |
383
|
4 |
|
throw new \InvalidArgumentException('Argument $times must be null or a positive integer'); |
384
|
|
|
} |
385
|
|
|
|
386
|
5 |
|
return new RepeatIterator($mixed, $times); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* Make an iterator that returns consecutive groups from the |
391
|
|
|
* $iterable. Generally, the $iterable needs to already be sorted on |
392
|
|
|
* the same key function |
393
|
|
|
* |
394
|
|
|
* When $strategy is a string, the key is obtained through one of |
395
|
|
|
* the following: |
396
|
|
|
* 1. $value->{$strategy}, when $value is an object and |
397
|
|
|
* $strategy is an existing property, |
398
|
|
|
* 2. call $value->{$strategy}(), when $value is an object and |
399
|
|
|
* $strategy is an existing method, |
400
|
|
|
* 3. $value[$strategy], when $value is an array and $strategy |
401
|
|
|
* is an existing key, |
402
|
|
|
* 4. otherwise the key will default to null. |
403
|
|
|
* |
404
|
|
|
* Alternatively $strategy can be a closure. In this case the |
405
|
|
|
* $strategy closure is called with each value in $iterable and the |
406
|
|
|
* key will be its return value. $strategy is called with two |
407
|
|
|
* parameters: the value and the key of the iterable as the first and |
408
|
|
|
* second parameter, respectively. |
409
|
|
|
* |
410
|
|
|
* The operation of groupBy() is similar to the uniq filter in Unix. |
411
|
|
|
* It generates a break or new group every time the value of the key |
412
|
|
|
* function changes (which is why it is usually necessary to have |
413
|
|
|
* sorted the data using the same key function). That behavior |
414
|
|
|
* differs from SQL's GROUP BY which aggregates common elements |
415
|
|
|
* regardless of their input order. |
416
|
|
|
* |
417
|
|
|
* > $list = [['type'=>'A', 'title'=>'one'], ['type'=>'A', 'title'=>'two'], ['type'=>'B', 'title'=>'three']] |
418
|
|
|
* > groupby('type', $list) |
419
|
|
|
* 'A'=>[['type'=>'A', 'title'=>'one'], ['type'=>'A', 'title'=>'two']] 'B'=>[['type'=>'B', 'title'=>'three']] |
420
|
|
|
* |
421
|
|
|
* @param null|string|\Closure $strategy |
422
|
|
|
* @param array|string|\Iterator $iterable |
423
|
|
|
* @param boolean $sort |
424
|
|
|
* @return GroupbyIterator |
425
|
|
|
*/ |
426
|
|
View Code Duplication |
function group_by($strategy, $iterable, $sort = true) |
|
|
|
|
427
|
|
|
{ |
428
|
18 |
|
if (!is_bool($sort)) { |
429
|
1 |
|
throw new \InvalidArgumentException('Argument $sort must be a boolean'); |
430
|
|
|
} |
431
|
|
|
|
432
|
17 |
|
return new GroupbyIterator( |
433
|
|
|
conversions\mixed_to_value_getter($strategy), |
434
|
15 |
|
$sort ? sorted($strategy, $iterable) : conversions\mixed_to_iterator($iterable) |
435
|
|
|
); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Make an iterator that returns consecutive groups from the |
440
|
|
|
* $iterable. Generally, the $iterable needs to already be sorted on |
441
|
|
|
* the same key function |
442
|
|
|
* |
443
|
|
|
* @param null|string|\Closure $strategy |
444
|
|
|
* @param array|string|\Iterator $iterable |
445
|
|
|
* @param boolean $sort |
446
|
|
|
* @return GroupbyIterator |
447
|
|
|
* |
448
|
|
|
* @deprecated Please use group_by(...)->values() instead (when flatten true), will be removed in version 3.0 |
449
|
|
|
*/ |
450
|
|
|
function groupBy($strategy, $iterable, $sort = true) |
451
|
|
|
{ |
452
|
|
|
return group_by($strategy, $iterable, $sort); |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
/** |
456
|
|
|
* Make an iterator that returns the values from $iterable sorted by |
457
|
|
|
* $strategy |
458
|
|
|
* |
459
|
|
|
* When determining the order of two entries the $strategy is called |
460
|
|
|
* twice, once for each value, and the results are used to determine |
461
|
|
|
* the order. $strategy is called with two parameters: the value and |
462
|
|
|
* the key of the iterable as the first and second parameter, respectively. |
463
|
|
|
* |
464
|
|
|
* When $reverse is true the order of the results are reversed. |
465
|
|
|
* |
466
|
|
|
* The sorted() function is guaranteed to be stable. A sort is stable |
467
|
|
|
* if it guarantees not to change the relative order of elements that |
468
|
|
|
* compare equal. this is helpful for sorting in multiple passes (for |
469
|
|
|
* example, sort by department, then by salary grade). This also |
470
|
|
|
* holds up when $reverse is true. |
471
|
|
|
* |
472
|
|
|
* > $list = [['type'=>'B', 'title'=>'second'], ['type'=>'C', 'title'=>'third'], ['type'=>'A', 'title'=>'first']] |
473
|
|
|
* > sorted('type', $list) |
474
|
|
|
* ['type'=>'A', 'title'=>'first'] ['type'=>'B', 'title'=>'second']] ['type'=>'C', 'title'=>'third'] |
475
|
|
|
* |
476
|
|
|
* @param string|\Closure $strategy |
477
|
|
|
* @param array|string|\Iterator $iterable |
478
|
|
|
* @param boolean $reverse |
479
|
|
|
* @return SortedIterator |
480
|
|
|
*/ |
481
|
|
View Code Duplication |
function sorted($strategy, $iterable, $reverse = false) |
|
|
|
|
482
|
|
|
{ |
483
|
35 |
|
if (!is_bool($reverse)) { |
484
|
1 |
|
throw new \InvalidArgumentException('Argument $reverse must be boolean'); |
485
|
|
|
} |
486
|
34 |
|
return new SortedIterator( |
487
|
|
|
conversions\mixed_to_value_getter($strategy), |
488
|
|
|
conversions\mixed_to_iterator($iterable), |
489
|
|
|
$reverse |
490
|
|
|
); |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* Make an iterator that returns values from $iterable where the |
495
|
|
|
* $strategy determines that the values are not empty |
496
|
|
|
* |
497
|
|
|
* An optional $strategy may be given, this must be either null, |
498
|
|
|
* a string, or a \Closure. |
499
|
|
|
* |
500
|
|
|
* Following the (optional) $strategy, one or more $iterable instances |
501
|
|
|
* must be given. They must be either an array, a string, or an \Iterator. |
502
|
|
|
* |
503
|
|
|
* @return FilterIterator |
504
|
|
|
*/ |
505
|
|
|
function filter() |
506
|
|
|
{ |
507
|
|
|
// note, once we stop supporting php 5.5, we can rewrite the code below |
508
|
|
|
// to the filter(...$iterables) structure. |
509
|
|
|
// http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list |
510
|
|
|
|
511
|
10 |
|
$args = func_get_args(); |
512
|
10 |
View Code Duplication |
switch (sizeof($args)) { |
|
|
|
|
513
|
10 |
|
case 1: |
514
|
3 |
|
$strategy = null; |
515
|
3 |
|
$iterable = $args[0]; |
516
|
3 |
|
break; |
517
|
|
|
|
518
|
7 |
|
case 2: |
519
|
6 |
|
$strategy = $args[0]; |
520
|
6 |
|
$iterable = $args[1]; |
521
|
6 |
|
break; |
522
|
|
|
|
523
|
|
|
default: |
524
|
1 |
|
throw new \InvalidArgumentException('filter requires either one (iterable) or two (strategy, iterable) arguments'); |
525
|
|
|
} |
526
|
|
|
|
527
|
9 |
|
$strategy = conversions\mixed_to_value_getter($strategy); |
528
|
|
|
$isValid = function ($value, $key) use ($strategy) { |
529
|
3 |
|
$tempVarPhp54 = $strategy($value, $key); |
530
|
3 |
|
return !empty($tempVarPhp54); |
531
|
8 |
|
}; |
532
|
|
|
|
533
|
8 |
|
return new FilterIterator($isValid, conversions\mixed_to_iterator($iterable)); |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
/** |
537
|
|
|
* Make an iterator that returns values from $iterable where the |
538
|
|
|
* $strategy determines that the values are not empty |
539
|
|
|
* |
540
|
|
|
* An $strategy must be given, this must be either null, a string, |
541
|
|
|
* or a \Closure. |
542
|
|
|
* |
543
|
|
|
* Following the $strategy, an optional $closure may be given, this |
544
|
|
|
* closure is called to determine is the value (which results from |
545
|
|
|
* $strategy) is or is not filtered. Note that without providing a |
546
|
|
|
* $closure, the function !empty(...) is used instead. |
547
|
|
|
* |
548
|
|
|
* Following the (optional) $closure, one or more $iterable instances |
549
|
|
|
* must be given. They must be either an array, a string, or an \Iterator. |
550
|
|
|
* |
551
|
|
|
* @return FilterIterator |
552
|
|
|
* |
553
|
|
|
* @deprecated Use filter() instead, will be removed in version 3.0 |
554
|
|
|
*/ |
555
|
|
|
function filterBy() |
556
|
|
|
{ |
557
|
|
|
// note, once we stop supporting php 5.5, we can rewrite the code below |
558
|
|
|
// to the filterBy(...$iterables) structure. |
559
|
|
|
// http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list |
560
|
|
|
|
561
|
|
|
$args = func_get_args(); |
562
|
|
|
switch (sizeof($args)) { |
563
|
|
View Code Duplication |
case 2: |
|
|
|
|
564
|
|
|
$strategy = conversions\mixed_to_value_getter($args[0]); |
565
|
|
|
$closure = function ($value, $key) use ($strategy) { |
566
|
|
|
$tempVarPhp54 = call_user_func($strategy, $value, $key); |
567
|
|
|
return !empty($tempVarPhp54); |
568
|
|
|
}; |
569
|
|
|
$iterable = conversions\mixed_to_iterator($args[1]); |
570
|
|
|
break; |
571
|
|
|
|
572
|
|
View Code Duplication |
case 3: |
|
|
|
|
573
|
|
|
$strategy = conversions\mixed_to_value_getter($args[0]); |
574
|
|
|
$userClosure = $args[1]; |
575
|
|
|
$closure = function ($value, $key) use ($strategy, $userClosure) { |
576
|
|
|
return call_user_func($userClosure, call_user_func($strategy, $value, $key)); |
577
|
|
|
}; |
578
|
|
|
$iterable = conversions\mixed_to_iterator($args[2]); |
579
|
|
|
break; |
580
|
|
|
|
581
|
|
|
default: |
582
|
|
|
throw new \InvalidArgumentException('filterBy requires either two (strategy, iterable) or three (strategy, closure, iterable) arguments'); |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
return new FilterIterator($closure, $iterable); |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
/** |
589
|
|
|
* Returns an iterator where one or more iterables are zipped together |
590
|
|
|
* |
591
|
|
|
* This function returns a list of tuples, where the i-th tuple contains |
592
|
|
|
* the i-th element from each of the argument sequences or iterables. |
593
|
|
|
* |
594
|
|
|
* The returned list is truncated in length to the length of the |
595
|
|
|
* shortest argument sequence. |
596
|
|
|
* |
597
|
|
|
* > zip([1, 2, 3], ['a', 'b', 'c']) |
598
|
|
|
* [1, 'a'] [2, 'b'] [3, 'c'] |
599
|
|
|
* |
600
|
|
|
* @param array|string|\Iterator $iterable Additional $iterable parameters may follow |
|
|
|
|
601
|
|
|
* @return ZipIterator |
602
|
|
|
*/ |
603
|
|
View Code Duplication |
function zip($iterableA) |
|
|
|
|
604
|
|
|
{ |
605
|
|
|
// note, once we stop supporting php 5.5, we can rewrite the code below |
606
|
|
|
// to the zip(...$iterables) structure. |
607
|
|
|
// http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list |
608
|
|
|
|
609
|
12 |
|
$iterables = array_map( |
610
|
|
|
function ($iterable) { |
611
|
12 |
|
return conversions\mixed_to_iterator($iterable); |
612
|
12 |
|
}, |
613
|
|
|
func_get_args() |
614
|
|
|
); |
615
|
9 |
|
$reflectorClass = new \ReflectionClass('\Zicht\Itertools\lib\ZipIterator'); |
616
|
9 |
|
return $reflectorClass->newInstanceArgs($iterables); |
617
|
|
|
} |
618
|
|
|
|
619
|
|
|
/** |
620
|
|
|
* Returns an iterable with all the elements from $iterable reversed |
621
|
|
|
* |
622
|
|
|
* @param array|string|\Iterator $iterable |
623
|
|
|
* @return ReversedIterator |
624
|
|
|
*/ |
625
|
|
|
function reversed($iterable) |
626
|
|
|
{ |
627
|
7 |
|
return new ReversedIterator(conversions\mixed_to_iterator($iterable)); |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
/** |
631
|
|
|
* Returns an iterator where the values from $strategy are unique |
632
|
|
|
* |
633
|
|
|
* An optional $strategy may be given to specify the value which is used |
634
|
|
|
* to determine weather the element is unique. When no $strategy is |
635
|
|
|
* given, the identity function is used, i.e. the value of the element |
636
|
|
|
* itself is used to determine weather the element is unique. |
637
|
|
|
* |
638
|
|
|
* Following the optional $strategy, a $iterable must be given. Otherwise, |
639
|
|
|
* an \InvalidArgumentException will be raised. |
640
|
|
|
* |
641
|
|
|
* > unique([1, 1, 2, 2, 3, 3]) |
642
|
|
|
* 1 2 3 |
643
|
|
|
* |
644
|
|
|
* > unique('id', [['id' => 1, 'value' => 'a'], ['id' => 1, 'value' => 'b']]) |
645
|
|
|
* ['id' => 1, 'value' => 'a'] # one element in this list |
646
|
|
|
* |
647
|
|
|
* @return UniqueIterator |
648
|
|
|
*/ |
649
|
|
|
function unique() |
650
|
|
|
{ |
651
|
16 |
|
$args = func_get_args(); |
652
|
16 |
View Code Duplication |
switch (sizeof($args)) { |
|
|
|
|
653
|
16 |
|
case 1: |
654
|
8 |
|
$strategy = null; |
655
|
8 |
|
$iterable = $args[0]; |
656
|
8 |
|
break; |
657
|
|
|
|
658
|
8 |
|
case 2: |
659
|
6 |
|
$strategy = $args[0]; |
660
|
6 |
|
$iterable = $args[1]; |
661
|
6 |
|
break; |
662
|
|
|
|
663
|
|
|
default: |
664
|
2 |
|
throw new \InvalidArgumentException('unique requires either one (iterable) or two (strategy, iterable) arguments'); |
665
|
|
|
} |
666
|
|
|
|
667
|
14 |
|
return new UniqueIterator( |
668
|
|
|
conversions\mixed_to_value_getter($strategy), |
669
|
|
|
conversions\mixed_to_iterator($iterable) |
670
|
|
|
); |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
/** |
674
|
|
|
* Returns an iterator where the values from $strategy are unique |
675
|
|
|
* |
676
|
|
|
* @param null|string|\Closure $strategy |
677
|
|
|
* @param array|string|\Iterator $iterable |
678
|
|
|
* @return UniqueIterator |
679
|
|
|
* |
680
|
|
|
* @deprecated use unique($strategy, $iterable) instead, will be removed in version 3.0 |
681
|
|
|
*/ |
682
|
|
|
function uniqueBy($strategy, $iterable) |
683
|
|
|
{ |
684
|
|
|
return new UniqueIterator( |
685
|
|
|
conversions\mixed_to_value_getter($strategy), |
686
|
|
|
conversions\mixed_to_iterator($iterable) |
687
|
|
|
); |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
/** |
691
|
|
|
* Returns true when one or more element of $iterable is not empty, otherwise returns false |
692
|
|
|
* |
693
|
|
|
* An optional $strategy may be given to specify the value which is used |
694
|
|
|
* to determine weather the element evaluates to true. When no $strategy is |
695
|
|
|
* given, the identity function is used, i.e. the value of the element |
696
|
|
|
* itself is used to determine weather the element evaluates to true. |
697
|
|
|
* |
698
|
|
|
* Following the optional $strategy, an $iterable may be given. Its type may |
699
|
|
|
* be either array, string, or \Iterator. When no $iterable is given, false |
700
|
|
|
* is returned. |
701
|
|
|
* |
702
|
|
|
* > any([0, '', false]) |
703
|
|
|
* false |
704
|
|
|
* |
705
|
|
|
* > any([1, null, 3]) |
706
|
|
|
* true |
707
|
|
|
* |
708
|
|
|
* @return boolean |
709
|
|
|
*/ |
710
|
|
View Code Duplication |
function any() |
|
|
|
|
711
|
|
|
{ |
712
|
20 |
|
$args = func_get_args(); |
713
|
20 |
|
switch (sizeof($args)) { |
714
|
20 |
|
case 1: |
715
|
9 |
|
$strategy = conversions\mixed_to_value_getter(null); |
716
|
9 |
|
$iterable = conversions\mixed_to_iterator($args[0]); |
717
|
6 |
|
break; |
718
|
|
|
|
719
|
11 |
|
case 2: |
720
|
9 |
|
$strategy = conversions\mixed_to_value_getter($args[0]); |
721
|
9 |
|
$iterable = conversions\mixed_to_iterator($args[1]); |
722
|
9 |
|
break; |
723
|
|
|
|
724
|
|
|
default: |
725
|
2 |
|
throw new \InvalidArgumentException('any requires either one (iterable) or two (strategy, iterable) arguments'); |
726
|
|
|
} |
727
|
|
|
|
728
|
15 |
|
foreach ($iterable as $item) { |
729
|
13 |
|
$tempVarPhp54 = call_user_func($strategy, $item); |
730
|
13 |
|
if (!empty($tempVarPhp54)) { |
731
|
13 |
|
return true; |
732
|
|
|
} |
733
|
|
|
} |
734
|
|
|
|
735
|
7 |
|
return false; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
/** |
739
|
|
|
* Returns true when all elements of $iterable are not empty, otherwise returns false |
740
|
|
|
* |
741
|
|
|
* An optional $strategy may be given to specify the value which is used |
742
|
|
|
* to determine weather the element evaluates to true. When no $strategy is |
743
|
|
|
* given, the identity function is used, i.e. the value of the element |
744
|
|
|
* itself is used to determine weather the element evaluates to true. |
745
|
|
|
* |
746
|
|
|
* Following the optional $strategy, an $iterable may be given. Its type may |
747
|
|
|
* be either array, string, or \Iterator. When no $iterable is given, true |
748
|
|
|
* is returned. |
749
|
|
|
* |
750
|
|
|
* > all([1, 'hello world', true]) |
751
|
|
|
* true |
752
|
|
|
* |
753
|
|
|
* > all([1, null, 3]) |
754
|
|
|
* false |
755
|
|
|
* |
756
|
|
|
* @return boolean |
757
|
|
|
*/ |
758
|
|
View Code Duplication |
function all() |
|
|
|
|
759
|
|
|
{ |
760
|
20 |
|
$args = func_get_args(); |
761
|
20 |
|
switch (sizeof($args)) { |
762
|
20 |
|
case 1: |
763
|
9 |
|
$strategy = conversions\mixed_to_value_getter(null); |
764
|
9 |
|
$iterable = conversions\mixed_to_iterator($args[0]); |
765
|
6 |
|
break; |
766
|
|
|
|
767
|
11 |
|
case 2: |
768
|
9 |
|
$strategy = conversions\mixed_to_value_getter($args[0]); |
769
|
9 |
|
$iterable = conversions\mixed_to_iterator($args[1]); |
770
|
9 |
|
break; |
771
|
|
|
|
772
|
|
|
default: |
773
|
2 |
|
throw new \InvalidArgumentException('all requires either one (iterable) or two (strategy, iterable) arguments'); |
774
|
|
|
} |
775
|
|
|
|
776
|
15 |
|
foreach ($iterable as $item) { |
777
|
13 |
|
$tempVarPhp54 = call_user_func($strategy, $item); |
778
|
13 |
|
if (empty($tempVarPhp54)) { |
779
|
13 |
|
return false; |
780
|
|
|
} |
781
|
|
|
} |
782
|
|
|
|
783
|
8 |
|
return true; |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
/** |
787
|
|
|
* Make an iterator that contains a slice of $iterable |
788
|
|
|
* |
789
|
|
|
* The parameters $start and $end determine the range that will be taken |
790
|
|
|
* from the $iterable. These values may be negative, in which case they |
791
|
|
|
* will indicate elements in $iterable starting at the end. |
792
|
|
|
* |
793
|
|
|
* > slice(['a', 'b', 'c', 'd', 'e', 1] |
794
|
|
|
* 'b', 'c', 'd', 'e' |
795
|
|
|
* |
796
|
|
|
* > slice(['a', 'b', 'c', 'd', 'e', -1] |
797
|
|
|
* 'e' |
798
|
|
|
* |
799
|
|
|
* > slice(['a', 'b', 'c', 'd', 'e', 1, 2] |
800
|
|
|
* 'b' |
801
|
|
|
* |
802
|
|
|
* > slice(['a', 'b', 'c', 'd', 'e', 1, -1] |
803
|
|
|
* 'b', 'c', 'd' |
804
|
|
|
* |
805
|
|
|
* @param array|string|\Iterator $iterable |
806
|
|
|
* @param integer $start |
807
|
|
|
* @param null|integer $end |
808
|
|
|
* @return SliceIterator |
809
|
|
|
*/ |
810
|
|
|
function slice($iterable, $start, $end = null) |
811
|
|
|
{ |
812
|
30 |
|
if (!is_int($start)) { |
813
|
3 |
|
throw new \InvalidArgumentException('Argument $start must be an integer'); |
814
|
|
|
} |
815
|
27 |
|
if (!(is_null($end) || is_int($end))) { |
816
|
2 |
|
throw new \InvalidArgumentException('Argument $end must be an integer or null'); |
817
|
|
|
} |
818
|
25 |
|
return new SliceIterator(conversions\mixed_to_iterator($iterable), $start, $end); |
819
|
|
|
} |
820
|
|
|
|
821
|
|
|
/** |
822
|
|
|
* Returns the first element of $iterable or returns $default when $iterable is empty |
823
|
|
|
* |
824
|
|
|
* > first([1, 2, 3]) |
825
|
|
|
* 1 |
826
|
|
|
* |
827
|
|
|
* > first([]) |
828
|
|
|
* null |
829
|
|
|
* |
830
|
|
|
* @param array|string|\Iterator $iterable |
831
|
|
|
* @param mixed $default |
832
|
|
|
* @return mixed |
833
|
|
|
*/ |
834
|
|
|
function first($iterable, $default = null) |
835
|
|
|
{ |
836
|
10 |
|
$item = $default; |
837
|
10 |
|
foreach (conversions\mixed_to_iterator($iterable) as $item) { |
838
|
3 |
|
break; |
839
|
|
|
} |
840
|
6 |
|
return $item; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
/** |
844
|
|
|
* Returns the last element of $iterable or returns $default when $iterable is empty |
845
|
|
|
* |
846
|
|
|
* > last([1, 2, 3]) |
847
|
|
|
* 3 |
848
|
|
|
* |
849
|
|
|
* > last([]) |
850
|
|
|
* null |
851
|
|
|
* |
852
|
|
|
* @param array|string|\Iterator $iterable |
853
|
|
|
* @param mixed $default |
854
|
|
|
* @return mixed |
855
|
|
|
*/ |
856
|
|
|
function last($iterable, $default = null) |
857
|
|
|
{ |
858
|
10 |
|
$item = $default; |
859
|
10 |
|
foreach (conversions\mixed_to_iterator($iterable) as $item) { |
|
|
|
|
860
|
|
|
} |
861
|
6 |
|
return $item; |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
/** |
865
|
|
|
* Returns a IterableIterator providing a fluent interface to itertools |
866
|
|
|
* |
867
|
|
|
* > iterable([1, 2, 3])->filter(...)->map(...)->first(...) |
868
|
|
|
* |
869
|
|
|
* @param array|string|\Iterator $iterable |
870
|
|
|
* @return IterableIterator |
871
|
|
|
*/ |
872
|
|
|
function iterable($iterable) |
873
|
|
|
{ |
874
|
108 |
|
return new IterableIterator(conversions\mixed_to_iterator($iterable)); |
875
|
|
|
} |
876
|
|
|
|
This function has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.