├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock └── src ├── Collection.php ├── CollectionInterface.php ├── CollectionTrait.php ├── Exceptions ├── InvalidArgument.php ├── InvalidReturnValue.php ├── ItemNotFound.php ├── NoMoreItems.php └── RuntimeException.php ├── collection_functions.php └── utility_functions.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ##0.2.0 4 | - First release 5 | - Every operation is represented by different Collection class 6 | 7 | ##0.3.0 8 | - **Total overhaul and move to more functional design** 9 | - Collection class now uses Generator functions under the hood. 10 | - The functions are also accessible allowing for functional programming. 11 | - Performance improved 2x. 12 | 13 | ##0.4.0 14 | - More utility functions added 15 | 16 | ##0.4.1 17 | - Missing utility functions added to readme 18 | 19 | ##0.4.2 20 | - Ditching PHP 5.5. support 21 | - Variadics introduced to some functions 22 | 23 | ##0.4.3 24 | - No longer rewinds wrapped collection in constructor 25 | 26 | ##1.0.0 27 | - The project is ready for production use. No known bugs exist. 28 | 29 | ##2.0.0 30 | - Project moved to new global namespace DusanKasan (whole namespace is DusanKasan\Knapsack) to avoid conflicts. 31 | - Collection::realize was introduced to force materialization of the collection (turning lazy collection into non-lazy). 32 | - Collection::concat and Collection::interleave are now variadic. 33 | - **Breaking change: toArray and Collection::toArray now behave more logicaly and do not convert items recursively.** 34 | 35 | ##3.0.0 36 | - Automatic conversion of return values to Collections is no longer happening if you do not explicitly require it. Details in documentation. 37 | 38 | ##3.1.0 39 | - CollectionTrait has been introduced and its usage documented in readme. 40 | - New functions added: 41 | - second() // seems useless but really usefull :) 42 | - combine($values) // uses current collection as keys, $values as values 43 | - except($keys) // rejects every item with key found in $keys 44 | - only($keys) // filters only items with key found in $keys 45 | - difference(...$collections) // filters items that are in the original collection but not in any of $collections 46 | - flip() // flips keys with values 47 | - has($key) // checks for the existence of item with $key key 48 | - A handful of bugfixes also: 49 | - Collection constructor might have experienced conflicts between callable (in array form) and array arguments 50 | - Pluck might have failed on heterogenous collections. Now ignores non-collection items. 51 | 52 | ##4.0.0 53 | - GroupByKey function introduced 54 | - Serialization support added 55 | - Changelog added 56 | - **Breaking change: combine now throws NonEqualCollectionLength** 57 | 58 | ##5.0.0 59 | - Zip function added 60 | - Extract function added 61 | - Transform function added 62 | - **Breaking change: combine now stops when it runs out of keys or values** 63 | - **Breaking change: pluck removed (replaced by extract)** 64 | 65 | ##6.0.0 66 | - Intersect function added 67 | - Average utility function added 68 | - Concatenate utility function added 69 | - Reduce/reduceRight/second now have the returnAsCollection flag 70 | - **Breaking change: getNth removed (to solve ambiguity with takeNth)** 71 | - **Breaking change: difference renamed to diff** 72 | 73 | ##6.1.0 74 | - Filter can be called without arguments and it will remove falsy values 75 | 76 | ##6.2.0 77 | - sizeIsExactly function added 78 | - sizeIsGreaterThan function added 79 | - sizeIsLessThan function added 80 | - sizeIsBetween function added 81 | 82 | ##7.0.0 83 | - The functionality of sum, average, min, max and concatenate moved into collection. 84 | - Sum collection function added 85 | - Average collection function added 86 | - Min collection function added 87 | - Max collection function added 88 | - ToString collection function added 89 | - **Breaking change: sum utility function removed** 90 | - **Breaking change: average utility function removed** 91 | - **Breaking change: min utility function removed** 92 | - **Breaking change: max utility function removed** 93 | - **Breaking change: concatenate utility function removed** 94 | 95 | ##8.0.0 96 | - **Breaking change: sum function will return integer by default, float if there are float type elements** 97 | - **Breaking change: average function will not force return float and will return integer if the sum/count result is integer** 98 | 99 | ##8.1.0 100 | - ReplaceByKeys function added 101 | 102 | ##8.1.1 103 | - Fixed bug: the only function always included the item with key equal to zero in the result. Caused by comparing string == 0. Also affected extract. 104 | 105 | ##8.2.0 106 | - Dump function added, to make debugging easier. 107 | 108 | ##8.3.0 109 | - PrintDump function added, to make debugging easier. Prints debug output, but returns the original collection. 110 | 111 | ##8.4.0 112 | - Transpose functionality added. 113 | 114 | ##8.4.1 115 | - Issue where group by could blow the stack fixed by internally using array to group the items. 116 | 117 | ##9.0.0 118 | - **Breaking change: Collection no longer implements Iterator but instead implements Traversable via IteratorAggregate** 119 | - Moving from Iterator to Traversable allows for huge performance gains (some 4x improvement at the very least) 120 | 121 | ##10.0.0 122 | - **Breaking change: If inputFactory returns non-collection, throw InvalidReturnValue** -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dusan Kasan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Knapsack 2 | **Collection pipeline library for PHP** 3 | 4 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/5fcb3dc2-2061-4da3-853b-a5e2a35a35fb/mini.png)](https://insight.sensiolabs.com/projects/5fcb3dc2-2061-4da3-853b-a5e2a35a35fb) [![Build Status](https://scrutinizer-ci.com/g/DusanKasan/Knapsack/badges/build.png?b=master)](https://scrutinizer-ci.com/g/DusanKasan/Knapsack/build-status/master) [![Code Coverage](https://scrutinizer-ci.com/g/DusanKasan/Knapsack/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/DusanKasan/Knapsack/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/DusanKasan/Knapsack/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/DusanKasan/Knapsack/?branch=master) 5 | 6 | Knapsack is a collection library for PHP >= 5.6 that implements most of the sequence operations proposed by [Clojures sequences](http://clojure.org/sequences) plus some additional ones. All its features are available as functions (for functional programming) and as a [collection pipeline](http://martinfowler.com/articles/collection-pipeline/) object methods. 7 | 8 | The heart of Knapsack is its [Collection class](https://github.com/DusanKasan/Knapsack/blob/master/src/Collection.php). However its every method calls a simple function with the same name that does the actual heavy lifting. These are located in `DusanKasan\Knapsack` namespace and you can find them [here](https://github.com/DusanKasan/Knapsack/blob/master/src/collection_functions.php). Collection is a [Traversable](https://secure.php.net/manual/en/class.traversable.php) implementor (via [IteratorAggregate](https://secure.php.net/manual/en/class.iteratoraggregate.php)) that accepts Traversable object, array or even a callable that produces a Traversable object or array as constructor argument. It provides most of Clojures sequence functionality plus some extra features. It is also immutable - operations preformed on the collection will return new collection (or value) instead of modifying the original collection. 9 | 10 | Most of the methods of Collection return lazy collections (such as filter/map/etc.). However, some return non-lazy collections (reverse) or simple values (count). For these operations all of the items in the collection must be iterated over (and realized). There are also operations (drop) that iterate over some items of the collection but do not affect/return them in the result. This behaviour as well as laziness is noted for each of the operations. 11 | 12 | If you want more example usage beyond what is provided here, check the [specs](https://github.com/DusanKasan/Knapsack/tree/master/tests/spec) and/or [scenarios](https://github.com/DusanKasan/Knapsack/tree/master/tests/scenarios). There are also [performance tests](https://github.com/DusanKasan/Knapsack/tree/master/tests/performance) you can run on your machine and see the computation time impact of this library (the output of these is included below). 13 | 14 | Feel free to report any [issues](https://github.com/DusanKasan/Knapsack/issues) you find. I will do my best to fix them as soon as possible, but community [pull requests](https://github.com/DusanKasan/Knapsack/pulls) to fix them are more than welcome. 15 | 16 | ## Documentation 17 | Check out the documentation (which is prettified version of this readme) at http://dusankasan.github.io/Knapsack 18 | 19 | ## Installation 20 | 21 | Require this package using Composer. 22 | 23 | ``` 24 | composer require dusank/knapsack 25 | ``` 26 | 27 | ## Usage 28 | 29 | ### Instantiate via static or dynamic constructor 30 | ```php 31 | use DusanKasan\Knapsack\Collection; 32 | 33 | $collection1 = new Collection([1, 2, 3]); 34 | $collection2 = Collection::from([1, 2, 3]); //preferred since you can call methods on its result directly. 35 | ``` 36 | 37 | ### Work with arrays, Traversable objects or callables that produce Traversables 38 | ```php 39 | $collection1 = Collection::from([1, 2, 3]); 40 | $collection2 = Collection::from(new ArrayIterator([1, 2, 3]); 41 | 42 | //Used because Generator can not be rewound 43 | $collection2 = Collection::from(function() { //must have 0 arguments 44 | foreach ([1, 2, 3] as $value) { 45 | yield $value; 46 | } 47 | }); 48 | ``` 49 | 50 | ### Basic map/reduce 51 | ```php 52 | $result = Collection::from([1, 2]) 53 | ->map(function($v) {return $v*2;}) 54 | ->reduce(function($tmp, $v) {return $tmp+$v;}, 0); 55 | 56 | echo $result; //6 57 | ``` 58 | 59 | ### The same map/reduce using Knapsack's collection functions 60 | ```php 61 | $result = reduce( 62 | map( 63 | [1, 2], 64 | function($v) {return $v*2;} 65 | ), 66 | function($tmp, $v) {return $tmp+$v;}, 67 | 0 68 | ); 69 | 70 | echo $result; //6 71 | ``` 72 | 73 | ### Get first 5 items of Fibonacci's sequence 74 | ```php 75 | $result = Collection::iterate([1,1], function($v) { 76 | return [$v[1], $v[0] + $v[1]]; //[1, 2], [2, 3] ... 77 | }) 78 | ->map('\DusanKasan\Knapsack\first') //one of the collection functions 79 | ->take(5); 80 | 81 | foreach ($result as $item) { 82 | echo $item . PHP_EOL; 83 | } 84 | 85 | //1 86 | //1 87 | //2 88 | //3 89 | //5 90 | ``` 91 | 92 | ### If array or Traversable would be returned from functions that return an item from the collection, it can be converted to Collection using the optional flag. By default it returns the item as is. 93 | ```php 94 | $result = Collection::from([[[1]]]) 95 | ->first(true) 96 | ->first(); 97 | 98 | var_dump($result); //[1] 99 | ``` 100 | 101 | 102 | ### Collections are immutable 103 | ```php 104 | function multiplyBy2($v) 105 | { 106 | return $v * 2; 107 | } 108 | 109 | function multiplyBy3($v) 110 | { 111 | return $v * 3; 112 | } 113 | 114 | function add($a, $b) 115 | { 116 | return $a + $b; 117 | } 118 | 119 | $collection = Collection::from([1, 2]); 120 | 121 | $result = $collection 122 | ->map('multiplyBy2') 123 | ->reduce(0, 'add'); 124 | 125 | echo $result; //6 126 | 127 | //On the same collection 128 | $differentResult = $collection 129 | ->map('multiplyBy3') 130 | ->reduce(0, 'add'); 131 | 132 | echo $differentResult; //9 133 | ``` 134 | 135 | ### Keys are not unique by design 136 | It would harm performance. This is only a problem if you need to call toArray(), then you should call values() before. 137 | ```php 138 | $result = Collection::from([1, 2])->concat([3,4]); 139 | 140 | //arrays have unique keys 141 | $result->toArray(); //[3,4] 142 | $result->values()->toArray(); //[1, 2, 3, 4] 143 | 144 | //When iterating, you can have multiple keys. 145 | foreach ($result as $key => $item) { 146 | echo $key . ':' . $item . PHP_EOL; 147 | } 148 | 149 | //0:1 150 | //1:2 151 | //0:3 152 | //1:4 153 | ``` 154 | 155 | ### Collection trait is provided 156 | If you wish to use all the Collection methods in your existing classes directly, no need to proxy their calls, you can just use the provided [CollectionTrait](https://github.com/DusanKasan/Knapsack/blob/master/src/CollectionTrait.php). This will work on any Traversable by default. In any other class you will have to override the getItems() method provided by the trait. Keep in mind that after calling filter or any other method that returns collection, the returned type will be actually Collection, not the original Traversable. 157 | 158 | ```php 159 | class AwesomeIterator extends ArrayIterator { 160 | use CollectionTrait; 161 | } 162 | 163 | $iterator = new AwesomeIterator([1, 2, 3]); 164 | $iterator->size(); //3 165 | ``` 166 | ## Performance tests 167 | 168 | ### PHP 5.6 169 | ```php 170 | +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+ 171 | | operation details | native execution time | collection execution time | difference (percent) | 172 | +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+ 173 | | array_map vs Collection::map on 10000 integers (addition) | 0.0034945011138916s | 0.0034625053405762s | 99% | 174 | | array_map vs Collection::map on 10000 strings (concatenation) | 0.004361891746521s | 0.0049739360809326s | 114% | 175 | | array_map vs Collection::map on 10000 objects (object to field value) | 0.02332329750061s | 0.027161455154419s | 116% | 176 | | array_map vs Collection::map on 10000 md5 invocations | 0.0086771726608276s | 0.0080755949020386s | 93% | 177 | | array_map vs Collection::map on 10000 integers n, counting sum(0, n) the naive way | 1.5985415458679s | 1.580038356781s | 98% | 178 | +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+ 179 | ``` 180 | 181 | ### PHP 7.1.1 182 | ```php 183 | +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+ 184 | | operation details | native execution time | collection execution time | difference (percent) | 185 | +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+ 186 | | array_map vs Collection::map on 10000 integers (addition) | 0.00082111358642578s | 0.001681661605835s | 204% | 187 | | array_map vs Collection::map on 10000 strings (concatenation) | 0.00081214904785156s | 0.0015116214752197s | 186% | 188 | | array_map vs Collection::map on 10000 objects (object to field value) | 0.0015491008758545s | 0.0036969423294067s | 238% | 189 | | array_map vs Collection::map on 10000 md5 invocations | 0.0032038688659668s | 0.0039427280426025s | 123% | 190 | | array_map vs Collection::map on 10000 integers n, counting sum(0, n) the naive way | 0.93844709396362s | 0.93354930877686s | 99% | 191 | +------------------------------------------------------------------------------------+-----------------------+---------------------------+----------------------+ 192 | ``` 193 | 194 | ## Constructors 195 | These are ways how to create the Collection class. There is one default constructor and few named (static) ones. 196 | 197 | #### new(iterable|callable $input) 198 | The default constructor accepts array, Traversable or a callable that takes no arguments and produces Traversable or array. The use case for the callable argument is for example a Generator, which can not be rewound so the Collection must be able to reconstruct it when rewinding itself. 199 | 200 | ```php 201 | $collection = new Collection([1, 2, 3]); 202 | ``` 203 | ```php 204 | $collection = new Collection(new ArrayIterator([1, 2, 3])); 205 | ``` 206 | ```php 207 | $generatorFactory = function () { 208 | foreach ([1, 2] as $value) { 209 | yield $value; 210 | } 211 | }; 212 | 213 | $collection = new Collection($generatorFactory); 214 | ``` 215 | 216 | #### from(iterable|callable $input) 217 | Collection::from is a static alias of the default constructor. This is the preferred way to create a Collection. 218 | 219 | ```php 220 | $collection = Collection::from([1, 2, 3]); 221 | ``` 222 | ```php 223 | $collection = Collection::from(new ArrayIterator([1, 2, 3])); 224 | ``` 225 | ```php 226 | $generatorFactory = function () { 227 | foreach ([1, 2] as $value) { 228 | yield $value; 229 | } 230 | }; 231 | 232 | $collection = Collection::from($generatorFactory); 233 | ``` 234 | 235 | #### iterate(mixed $input, callable $function) 236 | Returns lazy collection of values, where first value is $input and all subsequent values are computed by applying $function to the last value in the collection. By default this produces an infinite collection. However you can end the collection by throwing a NoMoreItems exception. 237 | 238 | ```php 239 | $collection = Collection::iterate(1, function ($value) {return $value + 1;}); // 1, 2, 3, 4 ... 240 | ``` 241 | 242 | #### repeat(mixed $value, int $times = -1) 243 | Returns a lazy collection of $value repeated $times times. If $times is not provided the collection is infinite. 244 | 245 | ```php 246 | Collection::repeat(1); //infinite collection of ones 247 | ``` 248 | 249 | ```php 250 | Collection::repeat(1, 4)->toArray(); //[1, 1, 1, 1] 251 | ``` 252 | 253 | #### range(int $start = 0, int $end = null, int step = 1) 254 | Returns a lazy collection of numbers starting at $start, incremented by $step until $end is reached. 255 | ```php 256 | Collection::range(0, 6, 2)->toArray(); //[0, 2, 4, 6] 257 | ``` 258 | 259 | ## Operations 260 | These are the operations (methods) provided by Collection class. For each one, there is a function with the same name in Knapsack namespace. The function has the same footprint as the method, except it has one extra argument prepended - the collection (array or Traversable). 261 | 262 | ### Standard Iterator methods 263 | It implements http://php.net/manual/en/class.iterator.php 264 | 265 | #### append(mixed $item, mixed $key = null) : Collection 266 | Returns a lazy collection of items of this collection with $item added as last element. If $key is not provided, its key will be the next integer in the sequence. 267 | ```php 268 | Collection::from([1, 3, 3, 2]) 269 | ->append(1) 270 | ->toArray(); //[1, 3, 3, 2, 1] 271 | ``` 272 | ```php 273 | Collection::from([1, 3, 3, 2]) 274 | ->append(1, 'key') 275 | ->toArray(); //[1, 3, 3, 2, 'key' => 1] 276 | ``` 277 | ```php 278 | toArray(append([1, 3, 3, 2], 1, 'key')); //[1, 3, 3, 2, 'key' => 1] 279 | ``` 280 | 281 | #### average() : int|float 282 | Returns average of values in this collection. 283 | ```php 284 | Collection::from([1, 2, 3, 2])->average(); //2 285 | Collection::from([1, 2, 3, 2, 2])->average(); //2.2 286 | Collection::from([])->average(); //0 287 | ``` 288 | ```php 289 | average([1, 2, 3]); //2 290 | ``` 291 | 292 | #### combine(iterable $collection, bool $strict = false) : Collection 293 | Combines the values of this collection as keys, with values of $collection as values. The resulting collection has length equal to the size of smaller collection. If $strict is true, the size of both collections must be equal, otherwise ItemNotFound is thrown. When strict, the collection is realized immediately. 294 | ```php 295 | Collection::from(['a', 'b']) 296 | ->combine([1, 2]) 297 | ->toArray(); //['a' => 1, 'b' => 2] 298 | ``` 299 | ```php 300 | toArray(combine(['a', 'b'], [1, 2])); //['a' => 1, 'b' => 2] 301 | ``` 302 | 303 | #### concat(iterable ...$collections) : Collection 304 | Returns a lazy collection with items from this collection followed by items from the collection from first argument, then second and so on. 305 | ```php 306 | Collection::from([1, 3, 3, 2]) 307 | ->concat([4, 5]) //If we would convert to array here, we would loose 2 items because of same keys [4, 5, 3, 2] 308 | ->values() 309 | ->toArray(); //[1, 3, 3, 2, 4, 5] 310 | ``` 311 | ```php 312 | toArray(values(concat([1, 3, 3, 2], [4, 5]))); //[1, 3, 3, 2, 4, 5] 313 | ``` 314 | 315 | #### contains(mixed $needle) : bool 316 | Returns true if $needle is present in the collection. 317 | ```php 318 | Collection::from([1, 3, 3, 2])->contains(2); //true 319 | ``` 320 | ```php 321 | contains([1, 3, 3, 2], 2); //true 322 | ``` 323 | 324 | #### countBy(callable $function) : Collection 325 | Returns a collection of items whose keys are the return values of $function(value, key) and values are the number of items in this collection for which the $function returned this value. 326 | ```php 327 | Collection::from([1, 2, 3, 4, 5]) 328 | ->countBy(function ($value) { 329 | return $value % 2 == 0 ? 'even' : 'odd'; 330 | }) 331 | ->toArray(); //['odd' => 3, 'even' => 2] 332 | ``` 333 | ```php 334 | toArray(countBy([1, 2, 3, 4, 5], function ($value) {return $value % 2 == 0 ? 'even' : 'odd';})); 335 | ``` 336 | 337 | #### cycle() : Collection 338 | Returns an infinite lazy collection of items in this collection repeated infinitely. 339 | ```php 340 | Collection::from([1, 3, 3, 2]) 341 | ->cycle() 342 | ->take(8) //we take just 8 items, since this collection is infinite 343 | ->values() 344 | ->toArray(); //[1, 3, 3, 2, 1, 3, 3, 2] 345 | ``` 346 | ```php 347 | toArray(values(take(cycle([1, 3, 3, 2]), 8))); //[1, 3, 3, 2, 1, 3, 3, 2] 348 | ``` 349 | 350 | #### diff(iterable ...$collections) : Collection 351 | Returns a lazy collection of items that are in $collection but are not in any of the other arguments, indexed by the keys from the first collection. Note that the ...$collections are iterated non-lazily. 352 | ```php 353 | Collection::from([1, 3, 3, 2]) 354 | ->diff([1, 3]) 355 | ->toArray(); //[3 => 2] 356 | ``` 357 | ```php 358 | toArray(diff([1, 3, 3, 2], [1, 3])); //[3 => 2] 359 | ``` 360 | 361 | #### distinct() : Collection 362 | Returns a lazy collection of distinct items. The comparison whether the item is in the collection or not is the same as in in_array. 363 | ```php 364 | Collection::from([1, 3, 3, 2]) 365 | ->distinct() 366 | ->toArray(); //[1, 3, 3 => 2] - each item has key of the first occurrence 367 | ``` 368 | ```php 369 | toArray(distinct([1, 3, 3, 2])); //[1, 3, 3 => 2] - each item has key of the first occurrence 370 | ``` 371 | 372 | #### drop(int $numberOfItems) : Collection 373 | A form of slice that returns all but first $numberOfItems items. 374 | ```php 375 | Collection::from([1, 2, 3, 4, 5]) 376 | ->drop(4) 377 | ->toArray(); //[4 => 5] 378 | ``` 379 | ```php 380 | toArray(drop([1, 2, 3, 4, 5], 4)); //[4 => 5] 381 | ``` 382 | 383 | #### dropLast($numberOfItems = 1) : Collection 384 | Returns a lazy collection with last $numberOfItems items skipped. These are still realized, just skipped. 385 | ```php 386 | Collection::from([1, 2, 3]) 387 | ->dropLast() 388 | ->toArray(); //[1, 2] 389 | ``` 390 | ```php 391 | Collection::from([1, 2, 3]) 392 | $collection 393 | ->dropLast(2) 394 | ->toArray(); //[1] 395 | ``` 396 | ```php 397 | toArray(dropLast([1, 2, 3], 2)); //[1] 398 | ``` 399 | 400 | #### dropWhile(callable $function) : Collection 401 | Returns a lazy collection by removing items from this collection until first item for which $function(value, key) returns false. 402 | ```php 403 | Collection::from([1, 3, 3, 2]) 404 | ->dropWhile(function ($v) { 405 | return $v < 3; 406 | }) 407 | ->toArray(); //[1 => 3, 2 => 3, 3 => 2]) 408 | ``` 409 | ```php 410 | Collection::from([1, 3, 3, 2]) 411 | ->dropWhile(function ($v, $k) { 412 | return $k < 2 && $v < 3; 413 | }) 414 | ->toArray(); //[1 => 3, 2 => 3, 3 => 2]) 415 | ``` 416 | ```php 417 | Collection::from([1, 3, 3, 2]) 418 | ->dropWhile(function ($v, $k) { 419 | return $k < 2 && $v < 3; 420 | }) 421 | ->toArray(); //[1 => 3, 2 => 3, 3 => 2]) 422 | ``` 423 | ```php 424 | toArray(values(dropWhile([1, 3, 3, 2], function ($v) {return $v < 3;}))); // [3, 3, 2] 425 | ``` 426 | 427 | #### dump(int $maxItemsPerCollection = null, $maxDepth = null) : array 428 | Dumps this collection into array (recursively). 429 | 430 | - scalars are returned as they are, 431 | - array of class name => properties (name => value and only properties accessible for this class) is returned for objects, 432 | - arrays or Traversables are returned as arrays, 433 | - for anything else result of calling gettype($input) is returned 434 | 435 | If specified, $maxItemsPerCollection will only leave specified number of items in collection, 436 | appending a new element at end '>>>' if original collection was longer. 437 | 438 | If specified, $maxDepth will only leave specified n levels of nesting, replacing elements 439 | with '^^^' once the maximum nesting level was reached. 440 | 441 | If a collection with duplicate keys is encountered, the duplicate keys (except the first one) 442 | will be change into a format originalKey//duplicateCounter where duplicateCounter starts from 443 | 1 at the first duplicate. So [0 => 1, 0 => 2] will become [0 => 1, '0//1' => 2] 444 | 445 | ```php 446 | Collection::from([1, 3, 3, 2])->dump(); //[1, 3, 3, 2] 447 | ``` 448 | ```php 449 | $collection = Collection::from( 450 | [ 451 | [ 452 | [1, [2], 3], 453 | ['a' => 'b'], 454 | new ArrayIterator([1, 2, 3]) 455 | ], 456 | [1, 2, 3], 457 | new ArrayIterator(['a', 'b', 'c']), 458 | true, 459 | new \DusanKasan\Knapsack\Tests\Helpers\Car('sedan', 5), 460 | \DusanKasan\Knapsack\concat([1], [1]) 461 | ] 462 | ); 463 | 464 | $collection->dump(2, 3); 465 | //[ 466 | // [ 467 | // [1, '^^^', '>>>'], 468 | // ['a' => 'b'], 469 | // '>>>' 470 | // ], 471 | // [1, 2, '>>>'], 472 | // '>>>' 473 | //] 474 | 475 | $collection->dump(); 476 | //[ 477 | // [ 478 | // [1, [2], 3], 479 | // ['a' => 'b'], 480 | // [1, 2, 3] 481 | // ], 482 | // [1, 2, 3], 483 | // ['a', 'b', 'c'], 484 | // true, 485 | // [ 486 | // 'DusanKasan\Knapsack\Tests\Helpers\Car' => [ 487 | // 'numberOfSeats' => 5, 488 | // ], 489 | // ], 490 | // [1, '0//1' => 1] 491 | //] 492 | ``` 493 | ```php 494 | dump([1, 3, 3, 2], 2); // [1, 3, '>>>'] 495 | ``` 496 | 497 | #### each(callable $function) : Collection 498 | Returns a lazy collection in which $function(value, key) is executed for each item. 499 | ```php 500 | Collection::from([1, 2, 3, 4, 5]) 501 | ->each(function ($i) { 502 | echo $i . PHP_EOL; 503 | }) 504 | ->toArray(); //[1, 2, 3, 4, 5] 505 | 506 | //1 507 | //2 508 | //3 509 | //4 510 | //5 511 | ``` 512 | ```php 513 | each([1, 2, 3, 4, 5], function ($v) {echo $v . PHP_EOL;}); 514 | 515 | //1 516 | //2 517 | //3 518 | //4 519 | //5 520 | ``` 521 | 522 | #### every(callable $function) : bool 523 | Returns true if $function(value, key) returns true for every item in this collection, false otherwise. 524 | ```php 525 | Collection::from([1, 3, 3, 2]) 526 | ->every(function ($v) { 527 | return $v < 3; 528 | }); //false 529 | ``` 530 | ```php 531 | Collection::from([1, 3, 3, 2]) 532 | ->every(function ($v, $k) { 533 | return $v < 4 && $k < 2; 534 | }); //false 535 | ``` 536 | ```php 537 | every([1, 3, 3, 2], function ($v) {return $v < 5;}); //true 538 | ``` 539 | 540 | #### except(iterable $keys) : Collection 541 | Returns a lazy collection without the items associated to any of the keys from $keys. 542 | ```php 543 | Collection::from(['a' => 1, 'b' => 2]) 544 | ->except(['a']) 545 | ->toArray(); //['b' => 2] 546 | ``` 547 | ```php 548 | toArray(except(['a' => 1, 'b' => 2], ['a'])); //['b' => 2] 549 | ``` 550 | 551 | #### extract(mixed $keyPath) : Collection 552 | Returns a lazy collection of data extracted from $collection items by dot separated key path. Supports the * wildcard. If a key contains \ or * it must be escaped using \ character. 553 | ```php 554 | $collection = Collection::from([['a' => ['b' => 1]], ['a' => ['b' => 2]], ['c' => ['b' => 3]]]) 555 | $collection->extract('a.b')->toArray(); //[1, 2] 556 | $collection->extract('*.b')->toArray(); //[1, 2, 3] 557 | ``` 558 | ```php 559 | toArray(extract([['a' => ['b' => 1]], ['a' => ['b' => 2]]], 'a.b')); //[1, 2] 560 | ``` 561 | 562 | #### filter(callable $function = null) : Collection 563 | Returns a lazy collection of items for which $function(value, key) returned true. 564 | ```php 565 | Collection::from([1, 3, 3, 2]) 566 | ->filter(function ($value) { 567 | return $value > 2; 568 | }) 569 | ->values() 570 | ->toArray(); //[3, 3] 571 | ``` 572 | ```php 573 | Collection::from([1, 3, 3, 2]) 574 | ->filter(function ($value, $key) { 575 | return $value > 2 && $key > 1; 576 | }) 577 | ->toArray(); //[2 => 3] 578 | ``` 579 | ```php 580 | toArray(values(filter([1, 3, 3, 2], function ($value) {return $value > 2;}))); //[3, 3] 581 | ``` 582 | 583 | If `$function` is not provided, `\DusanKasan\Knapsack\identity` is used so every falsy value is removed. 584 | ```php 585 | Collection::from([0, 0.0, false, null, "", []]) 586 | ->filter() 587 | ->isEmpty(); //true 588 | ``` 589 | ```php 590 | isEmpty(values(filter([0, 0.0, false, null, "", []]))); //true 591 | ``` 592 | 593 | 594 | #### find(callable $function, mixed $ifNotFound = null, bool $convertToCollection = false) : mixed|Collection 595 | Returns first value for which $function(value, key) returns true. If no item is matched, returns $ifNotFound. If $convertToCollection is true and the return value is an iterable an instance of Collection will be returned. 596 | ```php 597 | Collection::from([1, 3, 3, 2]) 598 | ->find(function ($value) { 599 | return $value < 3; 600 | }); //1 601 | ``` 602 | ```php 603 | Collection::from([1, 3, 3, 2]) 604 | ->find(function ($value) { 605 | return $value > 3; 606 | }, 10); //10 607 | ``` 608 | ```php 609 | Collection::from([1, 3, 3, 2]) 610 | ->find(function ($value, $key) { 611 | return $value < 3 && $key > 1; 612 | }); //2 613 | ``` 614 | ```php 615 | //if the output can be converted to Collection (it's array or Traversable), it will be. 616 | Collection::from([1, [4, 5], 3, 2]) 617 | ->find(function ($value) { 618 | return is_array($value); 619 | }, [], true) 620 | ->size(); //2 621 | ``` 622 | ```php 623 | find([1, 3, 3, 2], function ($value) {return $value > 2;}); //3 624 | ``` 625 | 626 | #### first(bool $convertToCollection = false) : mixed|Collection 627 | Returns first value in the collection or throws ItemNotFound if the collection is empty. If $convertToCollection is true and the return value is an iterable an instance of Collection will be returned. 628 | ```php 629 | Collection::from([1, 2, 3])->first(); //1 630 | ``` 631 | ```php 632 | Collection::from([[1], 2, 3])->first(); //[1] 633 | ``` 634 | ```php 635 | Collection::from([])->first(); //throws ItemNotFound 636 | ``` 637 | ```php 638 | first([1, 2, 3]); //1 639 | ``` 640 | 641 | #### flatten(int $depth = -1) : Collection 642 | Returns a lazy collection with one or multiple levels of nesting flattened. Removes all nesting when no $depth value is passed. 643 | ```php 644 | Collection::from([1,[2, [3]]]) 645 | ->flatten() 646 | ->values() //1, 2 and 3 have all key 0 647 | ->toArray(); //[1, 2, 3] 648 | ``` 649 | ```php 650 | Collection::from([1,[2, [3]]]) 651 | ->flatten(1) 652 | ->values() //1, 2 and 3 have all key 0 653 | ->toArray(); //[1, 2, [3]] 654 | ``` 655 | ```php 656 | toArray(values(flatten([1, [2, [3]]]))); //[1, 2, 3] 657 | ``` 658 | 659 | #### flip() : Collection 660 | Returns a lazy collection where keys and values are flipped. 661 | ```php 662 | Collection::from(['a' => 0, 'b' => 1]) 663 | ->flip() 664 | ->toArray(); //['a', 'b'] 665 | ``` 666 | ```php 667 | toArray(flip(['a' => 0, 'b' => 1])); //['a', 'b'] 668 | ``` 669 | 670 | #### frequencies() : Collection 671 | Returns a collection where keys are distinct items from this collection and their values are number of occurrences of each value. 672 | ```php 673 | Collection::from([1, 3, 3, 2]) 674 | ->frequencies() 675 | ->toArray(); //[1 => 1, 3 => 2, 2 => 1] 676 | ``` 677 | ```php 678 | toArray(frequencies([1, 3, 3, 2])); //[1 => 1, 3 => 2, 2 => 1] 679 | ``` 680 | 681 | #### get(mixed $key, bool $convertToCollection = false) : mixed|Collection 682 | Returns value at the key $key. If multiple values have this key, return first. If no value has this key, throw `ItemNotFound`. If $convertToCollection is true and the return value is an iterable an instance of Collection will be returned. 683 | ```php 684 | Collection::from([1, 3, 3, 2])->get(2); //3 685 | ``` 686 | ```php 687 | Collection::from([1, [1, 2]])->get(1, true)->toArray(); //[1, 2] 688 | ``` 689 | ```php 690 | Collection::from([1, 3, 3, 2])->get(5); //throws ItemNotFound 691 | ``` 692 | ```php 693 | get([1, 3, 3, 2], 2); //3 694 | ``` 695 | 696 | #### getOrDefault(mixed $key, mixed $default = null, bool $convertToCollection = false) : mixed|Collection 697 | Returns value at the key $key. If multiple values have this key, return first. If no value has this key, return $default. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned. 698 | ```php 699 | Collection::from([1, 3, 3, 2])->getOrDefault(2); //3 700 | ``` 701 | ```php 702 | Collection::from([1, 3, 3, 2])->getOrDefault(5); //null 703 | ``` 704 | ```php 705 | Collection::from([1, 3, 3, 2])->getOrDefault(5, 'asd'); //'asd' 706 | ``` 707 | ```php 708 | Collection::from([1, 3, 3, 2])->getOrDefault(5, [1, 2], true)->toArray(); //[1, 2] 709 | ``` 710 | ```php 711 | getOrDefault([1, 3, 3, 2], 5, 'asd'); //'asd' 712 | ``` 713 | 714 | #### groupBy(callable $function) : Collection 715 | Returns collection which items are separated into groups indexed by the return value of $function(value, key). 716 | ```php 717 | Collection::from([1, 2, 3, 4, 5]) 718 | ->groupBy(function ($value) { 719 | return $value % 2; 720 | }) 721 | ->toArray(); //[1 => [1, 3, 5], 0 => [2, 4]] 722 | ``` 723 | ```php 724 | toArray(groupBy([1, 2, 3, 4, 5], function ($value) {return $value % 2;})); //[1 => [1, 3, 5], 0 => [2, 4]] 725 | ``` 726 | 727 | #### groupByKey(mixed $key) : Collection 728 | Returns collection where items are separated into groups indexed by the value at given key. 729 | ```php 730 | Collection::from([ 731 | ['letter' => 'A', 'type' => 'caps'], 732 | ['letter' => 'a', 'type' => 'small'], 733 | ['letter' => 'B', 'type' => 'caps'], 734 | ]) 735 | ->groupByKey('type') 736 | ->map('DusanKasan\Knapsack\toArray') 737 | ->toArray(); 738 | // [ 'caps' => [['letter' => 'A', 'type' => 'caps'], ...], 'small' => [['letter' => 'a', 'type' => 'small']]] 739 | ``` 740 | 741 | ```php 742 | $data = [ 743 | ['letter' => 'A', 'type' => 'caps'], 744 | ['letter' => 'a', 'type' => 'small'], 745 | ['letter' => 'B', 'type' => 'caps'], 746 | ]; 747 | toArray(map(groupByKey($data, 'type'), 'toArray')); //[ 'caps' => [['letter' => 'A', 'type' => 'caps'], ...], 'small' => [['letter' => 'a', 'type' => 'small']]] 748 | ``` 749 | 750 | #### has(mixed $key) : bool 751 | Checks for the existence of $key in this collection. 752 | ```php 753 | Collection::from(['a' => 1])->has('a'); //true 754 | ``` 755 | ```php 756 | has(['a' => 1], 'a'); //true 757 | ``` 758 | 759 | #### indexBy(callable $function) : Collection 760 | Returns a lazy collection by changing keys of this collection for each item to the result of $function(value, key) for that key/value. 761 | ```php 762 | Collection::from([1, 3, 3, 2]) 763 | ->indexBy(function ($v) { 764 | return $v; 765 | }) 766 | ->toArray(); //[1 => 1, 3 => 3, 2 => 2] 767 | ``` 768 | ```php 769 | toArray(indexBy([1, 3, 3, 2], '\DusanKasan\Knapsack\identity')); //[1 => 1, 3 => 3, 2 => 2] 770 | ``` 771 | 772 | #### interleave(iterable ...$collections) : Collection 773 | Returns a lazy collection of first item from first collection, first item from second, second from first and so on. Works with any number of arguments. 774 | ```php 775 | Collection::from([1, 3, 3, 2]) 776 | ->interleave(['a', 'b', 'c', 'd', 'e']) 777 | ->values() 778 | ->toArray(); //[1, 'a', 3, 'b', 3, 'c', 2, 'd', 'e'] 779 | ``` 780 | ```php 781 | toArray(interleave([1, 3, 3, 2], ['a', 'b', 'c', 'd', 'e'])); //[1, 'a', 3, 'b', 3, 'c', 2, 'd', 'e'] 782 | ``` 783 | 784 | #### interpose(mixed $separator) : Collection 785 | Returns a lazy collection of items of this collection separated by $separator item. 786 | ```php 787 | Collection::from([1, 2, 3]) 788 | ->interpose('a') 789 | ->values() // we must reset the keys, because each 'a' has undecided key 790 | ->toArray(); //[1, 'a', 2, 'a', 3] 791 | ``` 792 | ```php 793 | toArray(interpose([1, 2, 3], 'a')); //[1, 'a', 2, 'a', 3] 794 | ``` 795 | 796 | #### intersect(iterable ...$collections) : Collection 797 | Returns a lazy collection of items that are in $collection and all the other arguments, indexed by the keys from the first collection. Note that the ...$collections are iterated non-lazily. 798 | ```php 799 | Collection::from([1, 2, 3]) 800 | ->intersect([1, 3]) 801 | ->toArray(); //[1, 2 => 3] 802 | ``` 803 | ```php 804 | toArray(intersect([1, 2, 3],[1, 3])); //[1, 2 => 3] 805 | ``` 806 | 807 | #### isEmpty() : bool 808 | Returns true if is collection is empty. False otherwise. 809 | ```php 810 | Collection::from([1, 3, 3, 2])->isEmpty(); //false 811 | ``` 812 | ```php 813 | isEmpty([1]); //false 814 | ``` 815 | 816 | #### isNotEmpty() : bool 817 | Opposite of isEmpty 818 | ```php 819 | Collection::from([1, 3, 3, 2])->isNotEmpty(); //true 820 | ``` 821 | ```php 822 | isNotEmpty([1]); //true 823 | ``` 824 | 825 | #### keys() : Collection 826 | Returns a lazy collection of the keys of this collection. 827 | ```php 828 | Collection::from(['a' => [1, 2], 'b' => [2, 3]]) 829 | ->keys() 830 | ->toArray(); //['a', 'b'] 831 | ``` 832 | ```php 833 | toArray(keys(['a' => [1, 2], 'b' => [2, 3]])); //['a', 'b'] 834 | ``` 835 | 836 | #### last(bool $convertToCollection = false) : mixed|Collection 837 | Returns last value in the collection or throws ItemNotFound if the collection is empty. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned. 838 | ```php 839 | Collection::from([1, 2, 3])->last(); //3 840 | ``` 841 | ```php 842 | Collection::from([1, 2, [3]])->last(true)->toArray(); //[1] 843 | ``` 844 | ```php 845 | Collection::from([])->last(); //throws ItemNotFound 846 | ``` 847 | ```php 848 | last([1, 2, 3]); //3 849 | ``` 850 | 851 | #### map(callable $function) : Collection 852 | Returns collection where each value is changed to the output of executing $function(value, key). 853 | ```php 854 | Collection::from([1, 3, 3, 2]) 855 | ->map(function ($value) { 856 | return $value + 1; 857 | }) 858 | ->toArray(); //[2, 4, 4, 3] 859 | ``` 860 | ```php 861 | toArray(map([1, 3, 3, 2], '\DusanKasan\Knapsack\increment')); //[2, 4, 4, 3] 862 | ``` 863 | 864 | #### mapcat(callable $mapper) : Collection 865 | Returns a lazy collection which is a result of calling map($mapper(value, key)) and then flatten(1). 866 | ```php 867 | Collection::from([1, 3, 3, 2]) 868 | ->mapcat(function ($value) { 869 | return [[$value]]; 870 | }) 871 | ->values() 872 | ->toArray(); //[[1], [3], [3], [2]] 873 | ``` 874 | ```php 875 | Collection::from([1, 3, 3, 2]) 876 | ->mapcat(function ($key, $value) { 877 | return [[$key]]; 878 | }) 879 | ->values() 880 | ->toArray(); //[[0], [1], [2], [3]] 881 | ``` 882 | ```php 883 | toArray(values(mapcat([1, 3, 3, 2], function ($value) {return [[$value]];}))); //[[1], [3], [3], [2]] 884 | ``` 885 | 886 | #### max() : mixed 887 | Returns mthe maximal value in this collection. 888 | ```php 889 | Collection::from([1, 2, 3, 2])->max(); //3 890 | Collection::from([])->max(); //null 891 | ``` 892 | ```php 893 | max([1, 2, 3, 2]); //3 894 | ``` 895 | 896 | #### min() : mixed 897 | Returns mthe minimal value in this collection. 898 | ```php 899 | Collection::from([1, 2, 3, 2])->min(); //1 900 | Collection::from([])->min(); //null 901 | ``` 902 | ```php 903 | min([1, 2, 3, 2]); //1 904 | ``` 905 | 906 | #### only(iterable $keys) : Collection 907 | Returns a lazy collection of items associated to any of the keys from $keys. 908 | ```php 909 | Collection::from(['a' => 1, 'b' => 2]) 910 | ->only(['b']) 911 | ->toArray(); //['b' => 2] 912 | ``` 913 | ```php 914 | toArray(only(['a' => 1, 'b' => 2], ['b'])); //['b' => 2] 915 | ``` 916 | 917 | #### partition(int $numberOfItems, int $step = 0, iterable $padding = []) : Collection 918 | Returns a lazy collection of collections of $numberOfItems items each, at $step step apart. If $step is not supplied, defaults to $numberOfItems, i.e. the partitionsdo not overlap. If a $padding collection is supplied, use its elements asnecessary to complete last partition up to $numberOfItems items. In case there are not enough padding elements, return a partition with less than $numberOfItems items. 919 | ```php 920 | Collection::from([1, 3, 3, 2]) 921 | ->partition(3, 2, [0, 1]) 922 | ->toArray(); //[[1, 3, 3], [2 => 3, 3 => 2, 0 => 0]] 923 | ``` 924 | ```php 925 | Collection::from([1, 3, 3, 2]) 926 | ->partition(3, 2) 927 | ->toArray(); //[[1, 3, 3], [2 => 3, 3 => 2]] 928 | ``` 929 | ```php 930 | Collection::from([1, 3, 3, 2]) 931 | ->partition(3) 932 | ->toArray(); //[[1, 3, 3], [3 => 2]] 933 | ``` 934 | ```php 935 | toArray(partition([1, 3, 3, 2], 3)); //[[1, 3, 3], [3 => 2]] 936 | ``` 937 | 938 | #### partitionBy(callable $function) : Collection 939 | Creates a lazy collection of collections created by partitioning this collection every time $function(value, key) will return different result. 940 | ```php 941 | Collection::from([1, 3, 3, 2]) 942 | ->partitionBy(function ($v) { 943 | return $v % 3 == 0; 944 | }) 945 | ->toArray(); //[[1], [1 => 3, 2 => 3], [3 => 2]] 946 | ``` 947 | ```php 948 | toArray(partitionBy([1, 3, 3, 2], function ($value) {return $value % 3 == 0;})); //[[1], [1 => 3, 2 => 3], [3 => 2]] 949 | ``` 950 | 951 | #### prepend(mixed $item, mixed $key = null) : Collection 952 | Returns a lazy collection of items of this collection with $item added as first element. Its key will be $key. If $key is not provided, its key will be the next numerical index. 953 | ```php 954 | Collection::from([1, 3, 3, 2]) 955 | ->prepend(1) 956 | ->values() //both 1 have 0 key 957 | ->toArray(); //[1, 1, 3, 3, 2] 958 | ``` 959 | ```php 960 | Collection::from([1, 3, 3, 2]) 961 | ->prepend(1, 'a') 962 | ->toArray(); //['a' => 1, 0 => 1, 1 => 3, 2 => 3, 3 => 2] 963 | ``` 964 | 965 | #### printDump(int $maxItemsPerCollection = null, $maxDepth = null) : Collection 966 | Calls dump on $input and then prints it using the var_export. Returns the collection. See dump function for arguments and output documentation. 967 | ```php 968 | Collection::from([1, 3, 3, 2]) 969 | ->printDump() 970 | ->toArray(); //[1, 3, 3, 2] 971 | ``` 972 | ```php 973 | toArray(printDump([1, 3, 3, 2])); //[1, 3, 3, 2] 974 | ``` 975 | 976 | #### realize() : Collection 977 | Realizes collection - turns lazy collection into non-lazy one by iterating over it and storing the key/values. 978 | ```php 979 | $helper->setValue(1); 980 | 981 | $realizedCollection = Collection::from([1, 3, 3, 2]) 982 | ->map(function ($item) use ($helper) {return $helper->getValue() + $item;}) 983 | ->realize(); 984 | 985 | $helper->setValue(10); 986 | $realizedCollection->toArray(); // [2, 4, 4, 3] 987 | ``` 988 | ```php 989 | toArray(realize([1, 3, 3, 2])); //[1, 3, 3, 2] 990 | ``` 991 | 992 | #### reduce(callable $function, mixed $start, bool $convertToCollection = false) : mixed|Collection 993 | Reduces the collection to single value by iterating over the collection and calling $function(tmp, value, key) while passing $start and current key/item as parameters. The output of callable is used as $start in next iteration. The output of callable on last element is the return value of this function. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned. 994 | ```php 995 | Collection::from([1, 3, 3, 2]) 996 | ->reduce( 997 | function ($tmp, $i) { 998 | return $tmp + $i; 999 | }, 1000 | 0 1001 | ); //9 1002 | 1003 | Collection::from([1, 3, 3, 2]) 1004 | ->reduce( 1005 | function ($tmp, $i) { 1006 | $tmp[] = $i + 1; 1007 | return $tmp; 1008 | }, 1009 | [], 1010 | true 1011 | ) 1012 | ->first(); //2 1013 | ``` 1014 | ```php 1015 | reduce([1, 3, 3, 2], function ($tmp, $value) {return $tmp + $value;}, 0); //9 1016 | ``` 1017 | 1018 | #### reduceRight(callable $function, mixed $start, bool $convertToCollection = false) : mixed|Collection 1019 | Like reduce, but walks from last item to the first one. If $convertToCollection is true and the return value is an iterable an instance of Collection is returned. 1020 | ```php 1021 | Collection::from([1, 3, 3, 2]) 1022 | ->reduceRight( 1023 | function ($tmp, $i) { 1024 | return $tmp + $i; 1025 | }, 1026 | 0 1027 | ); //9 1028 | 1029 | Collection::from([1, 3, 3, 2]) 1030 | ->reduce( 1031 | function ($tmp, $i) { 1032 | $tmp[] = $i + 1; 1033 | return $tmp; 1034 | }, 1035 | [], 1036 | true 1037 | ) 1038 | ->first(); //3 1039 | ``` 1040 | ```php 1041 | reduceRight([1, 3, 3, 2], function ($tmp, $value) {return $tmp + $value;}, 0); //9 1042 | ``` 1043 | 1044 | #### reductions(callable $reduction, mixed $start) : Collection 1045 | Returns a lazy collection of reduction steps. 1046 | ```php 1047 | Collection::from([1, 3, 3, 2]) 1048 | ->reductions(function ($tmp, $i) { 1049 | return $tmp + $i; 1050 | }, 0) 1051 | ->toArray(); //[1, 4, 7, 9] 1052 | ``` 1053 | ```php 1054 | toArray(reductions([1, 3, 3, 2], function ($tmp, $value) {return $tmp + $value;}, 0)); //[1, 4, 7, 9] 1055 | ``` 1056 | 1057 | #### reject(callable $filter) : Collection 1058 | Returns a lazy collection of items for which $filter(value, key) returned false. 1059 | ```php 1060 | Collection::from([1, 3, 3, 2]) 1061 | ->reject(function ($value) { 1062 | return $value > 2; 1063 | }) 1064 | ->toArray(); //[1, 3 => 2] 1065 | ``` 1066 | ```php 1067 | Collection::from([1, 3, 3, 2]) 1068 | ->reject(function ($value, $key) { 1069 | return $value > 2 && $key > 1; 1070 | }) 1071 | ->toArray(); //[1, 1 => 3, 3 => 2] 1072 | ``` 1073 | ```php 1074 | toArray(reject([1, 3, 3, 2], function ($value) {return $value > 2;})); //[1, 1 => 3, 3 => 2] 1075 | ``` 1076 | 1077 | #### replace(iterable $replacementMap) : Collection 1078 | Returns a lazy collection with items from this collection equal to any key in $replacementMap replaced for their value. 1079 | ```php 1080 | Collection::from([1, 3, 3, 2]) 1081 | ->replace([3 => 'a']) 1082 | ->toArray(); //[1, 'a', 'a', 2] 1083 | ``` 1084 | ```php 1085 | toArray(replace([1, 3, 3, 2], [3 => 'a'])); //[1, 'a', 'a', 2] 1086 | ``` 1087 | 1088 | #### replaceByKeys(iterable $replacementMap) : Collection 1089 | Returns a lazy collection with items from $collection, but items with keys that are found in keys of $replacementMap are replaced by their values. 1090 | ```php 1091 | Collection::from([1, 3, 3, 2]) 1092 | ->replace([3 => 'a']) 1093 | ->toArray(); //[1, 3, 3, 'a'] 1094 | ``` 1095 | ```php 1096 | toArray(replace([1, 3, 3, 2], [3 => 'a'])); //[1, 3, 3, 'a'] 1097 | ``` 1098 | 1099 | #### reverse() : Collection 1100 | Returns a non-lazy collection of items in this collection in reverse order. 1101 | ```php 1102 | Collection::from([1, 2, 3]) 1103 | ->reverse() 1104 | ->toArray(); //[2 => 3, 1 => 2, 0 => 1] 1105 | ``` 1106 | ```php 1107 | toArray(reverse([1, 2, 3])); //[2 => 3, 1 => 2, 0 => 1] 1108 | ``` 1109 | 1110 | 1111 | #### second(bool $convertToCollection = false) : mixed|Collection 1112 | Returns the second item of $collection or throws ItemNotFound if $collection is empty or has 1 item. If $convertToCollection is true and the return value is an iterable it is converted to Collection. 1113 | ```php 1114 | Collection::from([1, 3, 3, 2])->second(); //3 1115 | ``` 1116 | ```php 1117 | second([1, 3]); //3 1118 | ``` 1119 | 1120 | 1121 | #### shuffle() : Collection 1122 | Returns a collection of shuffled items from this collection 1123 | ```php 1124 | Collection::from([1, 3, 3, 2]) 1125 | ->shuffle() 1126 | ->toArray(); //something like [2 => 3, 0 => 1, 3 => 2, 1 => 3] 1127 | ``` 1128 | ```php 1129 | toArray(shuffle([1, 3, 3, 2])); //something like [2 => 3, 0 => 1, 3 => 2, 1 => 3] 1130 | ``` 1131 | 1132 | #### size() : int 1133 | Returns the number of items in this collection. 1134 | ```php 1135 | Collection::from([1, 3, 3, 2])->size(); //4 1136 | ``` 1137 | ```php 1138 | size([1, 3, 3, 2]); //4 1139 | ``` 1140 | 1141 | #### sizeIsBetween(int $fromSize, int $toSize) : bool 1142 | Checks whether this collection has between $fromSize to $toSize items. $toSize can be smaller than $fromSize. 1143 | ```php 1144 | Collection::from([1, 3, 3, 2])->sizeIsBetween(3, 5); //true 1145 | ``` 1146 | ```php 1147 | sizeIsBetween([1, 3, 3, 2], 3, 5); //true 1148 | ``` 1149 | 1150 | #### sizeIs(int $size) : bool 1151 | Checks whether this collection has exactly $size items. 1152 | ```php 1153 | Collection::from([1, 3, 3, 2])->sizeIs(4); //true 1154 | ``` 1155 | ```php 1156 | sizeIs([1, 3, 3, 2], 4); //true 1157 | ``` 1158 | 1159 | #### sizeIsGreaterThan(int $size) : bool 1160 | Checks whether this collection has more than $size items. 1161 | ```php 1162 | Collection::from([1, 3, 3, 2])->sizeIsGreaterThan(3); //true 1163 | ``` 1164 | ```php 1165 | sizeIsGreaterThan([1, 3, 3, 2], 3); //true 1166 | ``` 1167 | 1168 | #### sizeIsLessThan(int $size) : bool 1169 | Checks whether this collection has less than $size items. 1170 | ```php 1171 | Collection::from([1, 3, 3, 2])->sizeIsLessThan(5); //true 1172 | ``` 1173 | ```php 1174 | sizeIsLessThan([1, 3, 3, 2], 5); //true 1175 | ``` 1176 | 1177 | #### slice(int $from, int $to) : Collection 1178 | Returns a lazy collection of items which are part of the original collection from item number $from to item number $to inclusive. The items before $from are also realized, just not returned. 1179 | ```php 1180 | Collection::from([1, 2, 3, 4, 5]) 1181 | ->slice(2, 4) 1182 | ->toArray(); //[2 => 3, 3 => 4] 1183 | ``` 1184 | ```php 1185 | Collection::from([1, 2, 3, 4, 5]) 1186 | ->slice(4) 1187 | ->toArray(); //[4 => 5] 1188 | ``` 1189 | ```php 1190 | toArray(slice([1, 2, 3, 4, 5], 4)); //[4 => 5] 1191 | ``` 1192 | 1193 | #### some(callable $function) : bool 1194 | Returns true if $function(value, key) returns true for at least one item in this collection, false otherwise. 1195 | ```php 1196 | Collection::from([1, 3, 3, 2]) 1197 | ->some(function ($value) { 1198 | return $value < 3; 1199 | }); //true 1200 | ``` 1201 | ```php 1202 | Collection::from([1, 3, 3, 2]) 1203 | ->some(function ($value, $key) { 1204 | return $value < 4 && $key < 2; 1205 | }); //true 1206 | ``` 1207 | ```php 1208 | some([1, 3, 3 ,2], function ($value) {return $value < 3;}); //true 1209 | ``` 1210 | 1211 | #### sort(callable $function) : Collection 1212 | Returns collection sorted using $function(value1, value2, key1, key2). $function should return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second. 1213 | ```php 1214 | Collection::from([3, 1, 2]) 1215 | ->sort(function ($a, $b) { 1216 | return $a > $b; 1217 | }) 1218 | ->toArray(); //[1 => 1, 2 => 2, 0 => 3] 1219 | ``` 1220 | ```php 1221 | Collection::from([3, 1, 2]) 1222 | ->sort(function ($v1, $v2, $k1, $k2) { 1223 | return $v1 < $v2; 1224 | }) 1225 | ->toArray(); //[2 => 2, 1 => 1, 0 => 3] 1226 | ``` 1227 | ```php 1228 | toArray(sort([3, 1, 2], function ($a, $b) {return $a > $b;})); //[1 => 1, 2 => 2, 0 => 3] 1229 | ``` 1230 | 1231 | #### splitAt(int $position) : Collection 1232 | Returns a collection of lazy collections: [take($position), drop($position)]. 1233 | ```php 1234 | Collection::from([1, 3, 3, 2]) 1235 | ->splitAt(2) 1236 | ->toArray(); //[[1, 3], [2 => 3, 3 => 2]] 1237 | ``` 1238 | ```php 1239 | toArray(splitAt([1, 3, 3, 2], 2)); //[[1, 3], [2 => 3, 3 => 2]] 1240 | ``` 1241 | 1242 | #### splitWith(callable $function) : Collection 1243 | Returns a collection of lazy collections: [takeWhile($function(value, key)), dropWhile($function(value, key))]. 1244 | ```php 1245 | Collection::from([1, 3, 3, 2]) 1246 | ->splitWith(function ($value) { 1247 | return $value < 3; 1248 | }) 1249 | ->toArray(); //[[1], [1 => 3, 2 => 3, 3 => 2]] 1250 | ``` 1251 | ```php 1252 | Collection::from([1, 3, 3, 2]) 1253 | ->splitWith(function ($value, $key) { 1254 | return $key < 2 && $value < 3; 1255 | }) 1256 | ->toArray(); //[[1], [1 => 3, 2 => 3, 3 => 2]] 1257 | ``` 1258 | ```php 1259 | toArray(splitWith([1, 3, 3, 2], function ($value) {return $value < 3;})); //[[1], [1 => 3, 2 => 3, 3 => 2]] 1260 | ``` 1261 | 1262 | #### sum() : int|float 1263 | Returns a sum of all values in this collection. 1264 | ```php 1265 | Collection::from([1, 2, 3])->sum(); //6 1266 | Collection::from([1, 2, 3, 1.5])->sum(); //7.5 1267 | Collection::from([])->sum(); //0 1268 | ``` 1269 | ```php 1270 | sum([1, 2, 3]); //6 1271 | ``` 1272 | 1273 | #### take(int $numberOfItems) : Collection 1274 | A form of slice that returns first $numberOfItems items. 1275 | ```php 1276 | Collection::from([1, 2, 3, 4, 5]) 1277 | ->take(2) 1278 | ->toArray(); //[1, 2] 1279 | ``` 1280 | ```php 1281 | toArray(take([1, 2, 3, 4, 5], 2)); //[1, 2] 1282 | ``` 1283 | 1284 | #### takeNth(int $step) : Collection 1285 | Returns a lazy collection of every nth item in this collection 1286 | ```php 1287 | Collection::from([1, 3, 3, 2]) 1288 | ->takeNth(2) 1289 | ->toArray(); //[1, 2 => 3] 1290 | ``` 1291 | ```php 1292 | toArray(takeNth([1, 3, 3, 2], 2)); //[1, 2 => 3] 1293 | ``` 1294 | 1295 | #### takeWhile(callable $function) : Collection 1296 | Returns a lazy collection of items from the start of the collection until the first item for which $function(value, key) returns false. 1297 | ```php 1298 | Collection::from([1, 3, 3, 2]) 1299 | ->takeWhile(function ($value) { 1300 | return $value < 3; 1301 | }) 1302 | ->toArray(); //[1] 1303 | ``` 1304 | ```php 1305 | Collection::from([1, 3, 3, 2]) 1306 | ->takeWhile(function ($value, $key) { 1307 | return $key < 2 && $value < 3; 1308 | }) 1309 | ->toArray(); //[1] 1310 | ``` 1311 | ```php 1312 | toArray(takeWhile([1, 3, 3, 2], function ($value) {return $value < 3;})); //[1] 1313 | ``` 1314 | 1315 | #### transform(callable $transformer) : Collection 1316 | Uses a $transformer callable on itself that takes a Collection and returns Collection. This allows for creating a separate and reusable algorithms. 1317 | ```php 1318 | $transformer = function (Collection $collection) { 1319 | return $collection 1320 | ->filter(function ($item) { 1321 | return $item > 1; 1322 | }) 1323 | ->map('\DusanKasan\Knapsack\increment'); 1324 | }; 1325 | 1326 | Collection::from([1, 3, 3, 2]) 1327 | ->transform($transformer) 1328 | ->toArray(); //[4, 4, 3] 1329 | ``` 1330 | 1331 | 1332 | #### toArray() : array 1333 | Converts the collection to array recursively. Obviously this is not lazy since all the items must be realized. Calls iterator_to_array internaly. 1334 | ```php 1335 | Collection::from([1, 3, 3, 2])->toArray(); //[1, 3, 3, 2] 1336 | ``` 1337 | ```php 1338 | toArray([1, 3, 3, 2]); //[1, 3, 3, 2] 1339 | ``` 1340 | 1341 | #### toString() : string 1342 | Returns a string by concatenating this collection's values into a string. 1343 | ```php 1344 | Collection::from([1, 'a', 3, null])->toString(); //'1a3' 1345 | Collection::from([])->toString(); //'' 1346 | ``` 1347 | ```php 1348 | toString([1, 'a', 3, null]); //'1a3' 1349 | ``` 1350 | 1351 | #### transpose(iterable $collection) : string 1352 | Returns a non-lazy collection by interchanging each row and the corresponding column. The input must be a multi-dimensional collection or an InvalidArgumentException is thrown. 1353 | ```php 1354 | $arr = Collection::from([ 1355 | new Collection([1, 2, 3]), 1356 | new Collection([4, 5, new Collection(['foo', 'bar'])]), 1357 | new Collection([7, 8, 9]), 1358 | ])->transpose()->toArray(); 1359 | 1360 | // $arr = 1361 | // [ 1362 | // new Collection([1, 4, 7]), 1363 | // new Collection([2, 5, 8]), 1364 | // new Collection([3, new Collection(['foo', 'bar']), 9]), 1365 | // ] 1366 | ``` 1367 | 1368 | #### zip(iterable ...$collections) : Collection 1369 | Returns a lazy collection of non-lazy collections of items from nth position from this collection and each passed collection. Stops when any of the collections don't have an item at the nth position. 1370 | ```php 1371 | Collection::from([1, 2, 3]) 1372 | ->zip(['a' => 1, 'b' => 2, 'c' => 4]) 1373 | ->map('\DusanKasan\Knapsack\toArray') 1374 | ->toArray(); //[[1, 'a' => 1], [1 => 2, 'b' => 2], [2 => 3, 'c' => 4]] 1375 | 1376 | Collection::from([1, 2, 3]) 1377 | ->zip(['a' => 1, 'b' => 2]) 1378 | ->map('\DusanKasan\Knapsack\toArray') 1379 | ->toArray(); //[[1, 'a' => 1], [1 => 2, 'b' => 2]] 1380 | ``` 1381 | ```php 1382 | toArray(map(zip([1, 2, 3], ['a' => 1, 'b' => 2, 'c' => 4]), '\DusanKasan\Knapsack\toArray')); //[[1, 'a' => 1], [1 => 2, 'b' => 2], [2 => 3, 'c' => 4]] 1383 | ``` 1384 | 1385 | ## Utility functions 1386 | These are the functions bundled with Knapsack to make your life easier when transitioning into functional programming. 1387 | 1388 | #### identity(mixed $value) 1389 | Returns $value 1390 | 1391 | ```php 1392 | $value === identity($value); //true 1393 | ``` 1394 | 1395 | #### compare(mixed $a, mixed $b) 1396 | Default comparator function. Returns a negative number, zero, or a positive number when $a is logically 'less than', 'equal to', or 'greater than' $b. 1397 | 1398 | ```php 1399 | compare(1, 2); //-1 1400 | ``` 1401 | 1402 | #### increment(int $value) 1403 | Returns value of $value incremented by one. 1404 | 1405 | ```php 1406 | increment(0) === 1; //true 1407 | ``` 1408 | 1409 | #### decrement(int $value) 1410 | Returns value of $value decremented by one. 1411 | 1412 | ```php 1413 | decrement(2) === 1; //true 1414 | ``` 1415 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dusank/knapsack", 3 | "license": "MIT", 4 | "type": "library", 5 | "description": "Collection library for PHP", 6 | "keywords": ["collections","sequences", "map", "reduce"], 7 | "homepage": "https://github.com/DusanKasan/Knapsack", 8 | "authors": [ 9 | { 10 | "name": "Dusan Kasan", 11 | "email": "dusan@kasan.sk", 12 | "homepage": "http://kasan.sk", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require-dev": { 17 | "phpspec/phpspec": "^3.4", 18 | "henrikbjorn/phpspec-code-coverage": "^3.0", 19 | "squizlabs/php_codesniffer": "^3.4", 20 | "phpmd/phpmd" : "^2.0", 21 | "ciaranmcnulty/phpspec-typehintedmethods": "^2.0", 22 | "phpunit/phpunit": "^5.7", 23 | "symfony/console": "^2.7" 24 | }, 25 | "require": { 26 | "php": ">=5.6.0" 27 | }, 28 | "autoload": { 29 | "files": [ 30 | "src/collection_functions.php", 31 | "src/utility_functions.php" 32 | ], 33 | "psr-4": { 34 | "DusanKasan\\Knapsack\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "DusanKasan\\Knapsack\\Tests\\Helpers\\": "tests/helpers/" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Collection.php: -------------------------------------------------------------------------------- 1 | inputFactory = $input; 32 | $input = $input(); 33 | } 34 | 35 | if (is_array($input)) { 36 | $this->input = new ArrayIterator($input); 37 | } elseif ($input instanceof Traversable) { 38 | $this->input = $input; 39 | } else { 40 | throw $this->inputFactory ? new InvalidReturnValue : new InvalidArgument; 41 | } 42 | } 43 | 44 | /** 45 | * Static alias of normal constructor. 46 | * 47 | * @param callable|array|Traversable $input 48 | * @return Collection 49 | */ 50 | public static function from($input) 51 | { 52 | return new self($input); 53 | } 54 | 55 | /** 56 | * Returns lazy collection of values, where first value is $input and all subsequent values are computed by applying 57 | * $function to the last value in the collection. By default this produces an infinite collection. However you can 58 | * end the collection by throwing a NoMoreItems exception. 59 | * 60 | * @param mixed $input 61 | * @param callable $function 62 | * @return Collection 63 | */ 64 | public static function iterate($input, callable $function) 65 | { 66 | return iterate($input, $function); 67 | } 68 | 69 | /** 70 | * Returns a lazy collection of $value repeated $times times. If $times is not provided the collection is infinite. 71 | * 72 | * @param mixed $value 73 | * @param int $times 74 | * @return Collection 75 | */ 76 | public static function repeat($value, $times = -1) 77 | { 78 | return repeat($value, $times); 79 | } 80 | 81 | /** 82 | * Returns a lazy collection of numbers starting at $start, incremented by $step until $end is reached. 83 | * 84 | * @param int $start 85 | * @param int|null $end 86 | * @param int $step 87 | * @return Collection 88 | */ 89 | public static function range($start = 0, $end = null, $step = 1) 90 | { 91 | return \DusanKasan\Knapsack\range($start, $end, $step); 92 | } 93 | 94 | /** 95 | * {@inheritdoc} 96 | * @throws InvalidReturnValue 97 | */ 98 | public function getIterator() 99 | { 100 | if ($this->inputFactory) { 101 | $input = call_user_func($this->inputFactory); 102 | 103 | if (is_array($input)) { 104 | $input = new ArrayIterator($input); 105 | } 106 | 107 | if (!($input instanceof Traversable)) { 108 | throw new InvalidReturnValue; 109 | } 110 | 111 | $this->input = $input; 112 | } 113 | 114 | return $this->input; 115 | } 116 | 117 | /** 118 | * {@inheritdoc} 119 | */ 120 | public function serialize() 121 | { 122 | return serialize( 123 | toArray( 124 | map( 125 | $this->input, 126 | function ($value, $key) { 127 | return [$key, $value]; 128 | } 129 | ) 130 | ) 131 | ); 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function unserialize($serialized) 138 | { 139 | $this->input = dereferenceKeyValue(unserialize($serialized)); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/CollectionInterface.php: -------------------------------------------------------------------------------- 1 | Collection 527 | * @return Collection 528 | * @throws InvalidReturnValue 529 | */ 530 | public function transform(callable $transformer); 531 | 532 | /** 533 | * Transpose each item in a collection, interchanging the row and column indexes. 534 | * Can only transpose collections of collections. Otherwise an InvalidArgument is raised. 535 | * 536 | * @return Collection 537 | */ 538 | public function transpose(); 539 | 540 | /** 541 | * Extracts data from collection items by dot separated key path. Supports the * wildcard. If a key contains \ or 542 | * it must be escaped using \ character. 543 | * 544 | * @param mixed $keyPath 545 | * @return Collection 546 | */ 547 | public function extract($keyPath); 548 | 549 | /** 550 | * Returns a lazy collection of items that are in $this and all the other arguments, indexed by the keys from 551 | * the first collection. Note that the ...$collections are iterated non-lazily. 552 | * 553 | * @param array|\Traversable ...$collections 554 | * @return Collection 555 | */ 556 | public function intersect(...$collections); 557 | 558 | /** 559 | * Checks whether this collection has exactly $size items. 560 | * 561 | * @param int $size 562 | * @return bool 563 | */ 564 | public function sizeIs($size); 565 | 566 | /** 567 | * Checks whether this collection has less than $size items. 568 | * 569 | * @param int $size 570 | * @return bool 571 | */ 572 | public function sizeIsLessThan($size); 573 | 574 | /** 575 | * Checks whether this collection has more than $size items. 576 | * 577 | * @param int $size 578 | * @return bool 579 | */ 580 | public function sizeIsGreaterThan($size); 581 | 582 | /** 583 | * Checks whether this collection has between $fromSize to $toSize items. $toSize can be 584 | * smaller than $fromSize. 585 | * 586 | * @param int $fromSize 587 | * @param int $toSize 588 | * @return bool 589 | */ 590 | public function sizeIsBetween($fromSize, $toSize); 591 | 592 | /** 593 | * Returns a sum of all values in this collection. 594 | * 595 | * @return int|float 596 | */ 597 | public function sum(); 598 | 599 | /** 600 | * Returns average of values from this collection. 601 | * 602 | * @return int|float 603 | */ 604 | public function average(); 605 | 606 | /** 607 | * Returns maximal value from this collection. 608 | * 609 | * @return mixed 610 | */ 611 | public function max(); 612 | 613 | /** 614 | * Returns minimal value from this collection. 615 | * 616 | * @return mixed 617 | */ 618 | public function min(); 619 | 620 | /** 621 | * Returns a string by concatenating the collection values into a string. 622 | * 623 | * @return string 624 | */ 625 | public function toString(); 626 | 627 | /** 628 | * Returns a lazy collection with items from $collection, but items with keys that are found in keys of 629 | * $replacementMap are replaced by their values. 630 | * 631 | * @param array|\Traversable $replacementMap 632 | * @return Collection 633 | */ 634 | public function replaceByKeys($replacementMap); 635 | 636 | /** 637 | * /** 638 | * Dumps this collection into array (recursively). 639 | * 640 | * - scalars are returned as they are, 641 | * - array of class name => properties (name => value and only properties accessible for this class) 642 | * is returned for objects, 643 | * - arrays or Traversables are returned as arrays, 644 | * - for anything else result of calling gettype($input) is returned 645 | * 646 | * If specified, $maxItemsPerCollection will only leave specified number of items in collection, 647 | * appending a new element at end '>>>' if original collection was longer. 648 | * 649 | * If specified, $maxDepth will only leave specified n levels of nesting, replacing elements 650 | * with '^^^' once the maximum nesting level was reached. 651 | * 652 | * If a collection with duplicate keys is encountered, the duplicate keys (except the first one) 653 | * will be change into a format originalKey//duplicateCounter where duplicateCounter starts from 654 | * 1 at the first duplicate. So [0 => 1, 0 => 2] will become [0 => 1, '0//1' => 2] 655 | * 656 | * @param int|null $maxItemsPerCollection 657 | * @param int|null $maxDepth 658 | * @return array 659 | */ 660 | public function dump($maxItemsPerCollection = null, $maxDepth = null); 661 | 662 | /** 663 | * Calls dump on this collection and then prints it using the var_export. 664 | * 665 | * @param int|null $maxItemsPerCollection 666 | * @param int|null $maxDepth 667 | * @return Collection 668 | */ 669 | public function printDump($maxItemsPerCollection = null, $maxDepth = null); 670 | } -------------------------------------------------------------------------------- /src/CollectionTrait.php: -------------------------------------------------------------------------------- 1 | getItems()); 17 | } 18 | 19 | /** 20 | * Returns a lazy collection of items for which $function returned true. 21 | * 22 | * @param callable|null $function ($value, $key) 23 | * @return Collection 24 | */ 25 | public function filter(callable $function = null) 26 | { 27 | return filter($this->getItems(), $function); 28 | } 29 | 30 | /** 31 | * Returns a lazy collection of distinct items. The comparison is the same as in in_array. 32 | * 33 | * @return Collection 34 | */ 35 | public function distinct() 36 | { 37 | return distinct($this->getItems()); 38 | } 39 | 40 | /** 41 | * Returns a lazy collection with items from all $collections passed as argument appended together 42 | * 43 | * @param array|\Traversable ...$collections 44 | * @return Collection 45 | */ 46 | public function concat(...$collections) 47 | { 48 | return concat($this, ...$collections); 49 | } 50 | 51 | /** 52 | * Returns collection where each item is changed to the output of executing $function on each key/item. 53 | * 54 | * @param callable $function 55 | * @return Collection 56 | */ 57 | public function map(callable $function) 58 | { 59 | return map($this->getItems(), $function); 60 | } 61 | 62 | /** 63 | * Reduces the collection to single value by iterating over the collection and calling $function while 64 | * passing $startValue and current key/item as parameters. The output of $function is used as $startValue in 65 | * next iteration. The output of $function on last element is the return value of this function. If 66 | * $convertToCollection is true and the return value is a collection (array|Traversable) an instance of Collection 67 | * is returned. 68 | * 69 | * @param callable $function ($tmpValue, $value, $key) 70 | * @param mixed $startValue 71 | * @param bool $convertToCollection 72 | * @return mixed|Collection 73 | */ 74 | public function reduce(callable $function, $startValue, $convertToCollection = false) 75 | { 76 | $result = reduce($this->getItems(), $function, $startValue); 77 | 78 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 79 | } 80 | 81 | /** 82 | * Returns a lazy collection with one or multiple levels of nesting flattened. Removes all nesting when no value 83 | * is passed. 84 | * 85 | * @param int $depth How many levels should be flatten, default (-1) is infinite. 86 | * @return Collection 87 | */ 88 | public function flatten($depth = -1) 89 | { 90 | return flatten($this->getItems(), $depth); 91 | } 92 | 93 | /** 94 | * Returns a non-lazy collection sorted using $function($item1, $item2, $key1, $key2 ). $function should 95 | * return true if first item is larger than the second and false otherwise. 96 | * 97 | * @param callable $function ($value1, $value2, $key1, $key2) 98 | * @return Collection 99 | */ 100 | public function sort(callable $function) 101 | { 102 | return \DusanKasan\Knapsack\sort($this->getItems(), $function); 103 | } 104 | 105 | /** 106 | * Returns lazy collection items of which are part of the original collection from item number $from to item 107 | * number $to. The items before $from are also iterated over, just not returned. 108 | * 109 | * @param int $from 110 | * @param int $to If omitted, will slice until end 111 | * @return Collection 112 | */ 113 | public function slice($from, $to = -1) 114 | { 115 | return slice($this->getItems(), $from, $to); 116 | } 117 | 118 | /** 119 | * Returns collection which items are separated into groups indexed by the return value of $function. 120 | * 121 | * @param callable $function ($value, $key) 122 | * @return Collection 123 | */ 124 | public function groupBy(callable $function) 125 | { 126 | return groupBy($this->getItems(), $function); 127 | } 128 | 129 | /** 130 | * Returns collection where items are separated into groups indexed by the value at given key. 131 | * 132 | * @param mixed $key 133 | * @return Collection 134 | */ 135 | public function groupByKey($key) 136 | { 137 | return groupByKey($this->getItems(), $key); 138 | } 139 | 140 | /** 141 | * Returns a lazy collection in which $function is executed for each item. 142 | * 143 | * @param callable $function ($value, $key) 144 | * @return Collection 145 | */ 146 | public function each(callable $function) 147 | { 148 | return \DusanKasan\Knapsack\each($this->getItems(), $function); 149 | } 150 | 151 | /** 152 | * Returns the number of items in this collection. 153 | * 154 | * @return int 155 | */ 156 | public function size() 157 | { 158 | return size($this->getItems()); 159 | } 160 | 161 | /** 162 | * Returns value at the key $key. If multiple values have this key, return first. If no value has this key, throw 163 | * ItemNotFound. If $convertToCollection is true and the return value is a collection (array|Traversable) an 164 | * instance of Collection will be returned. 165 | * 166 | * @param mixed $key 167 | * @param bool $convertToCollection 168 | * @return mixed|Collection 169 | * @throws \DusanKasan\Knapsack\Exceptions\ItemNotFound 170 | */ 171 | public function get($key, $convertToCollection = false) 172 | { 173 | $result = get($this->getItems(), $key); 174 | 175 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 176 | } 177 | 178 | /** 179 | * Returns item at the key $key. If multiple items have this key, return first. If no item has this key, return 180 | * $ifNotFound. If no value has this key, throw ItemNotFound. If $convertToCollection is true and the return value 181 | * is a collection (array|Traversable) an instance of Collection will be returned. 182 | * 183 | * @param mixed $key 184 | * @param mixed $default 185 | * @param bool $convertToCollection 186 | * @return mixed|Collection 187 | * @throws \DusanKasan\Knapsack\Exceptions\ItemNotFound 188 | */ 189 | public function getOrDefault($key, $default = null, $convertToCollection = false) 190 | { 191 | $result = getOrDefault($this->getItems(), $key, $default); 192 | 193 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 194 | } 195 | 196 | /** 197 | * Returns first value matched by $function. If no value matches, return $default. If $convertToCollection is true 198 | * and the return value is a collection (array|Traversable) an instance of Collection will be returned. 199 | * 200 | * @param callable $function 201 | * @param mixed|null $default 202 | * @param bool $convertToCollection 203 | * @return mixed|Collection 204 | */ 205 | public function find(callable $function, $default = null, $convertToCollection = false) 206 | { 207 | $result = find($this->getItems(), $function, $default); 208 | 209 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 210 | } 211 | 212 | /** 213 | * Returns a non-lazy collection of items whose keys are the return values of $function and values are the number of 214 | * items in this collection for which the $function returned this value. 215 | * 216 | * @param callable $function 217 | * @return Collection 218 | */ 219 | public function countBy(callable $function) 220 | { 221 | return countBy($this->getItems(), $function); 222 | } 223 | 224 | /** 225 | * Returns a lazy collection by changing keys of this collection for each item to the result of $function for 226 | * that item. 227 | * 228 | * @param callable $function 229 | * @return Collection 230 | */ 231 | public function indexBy(callable $function) 232 | { 233 | return indexBy($this->getItems(), $function); 234 | } 235 | 236 | /** 237 | * Returns true if $function returns true for every item in this collection, false otherwise. 238 | * 239 | * @param callable $function 240 | * @return bool 241 | */ 242 | public function every(callable $function) 243 | { 244 | return every($this->getItems(), $function); 245 | } 246 | 247 | /** 248 | * Returns true if $function returns true for at least one item in this collection, false otherwise. 249 | * 250 | * @param callable $function 251 | * @return bool 252 | */ 253 | public function some(callable $function) 254 | { 255 | return some($this->getItems(), $function); 256 | } 257 | 258 | /** 259 | * Returns true if $value is present in the collection. 260 | * 261 | * @param mixed $value 262 | * @return bool 263 | */ 264 | public function contains($value) 265 | { 266 | return contains($this->getItems(), $value); 267 | } 268 | 269 | /** 270 | * Returns collection of items in this collection in reverse order. 271 | * 272 | * @return Collection 273 | */ 274 | public function reverse() 275 | { 276 | return reverse($this->getItems()); 277 | } 278 | 279 | /** 280 | * Reduce the collection to single value. Walks from right to left. If $convertToCollection is true and the return 281 | * value is a collection (array|Traversable) an instance of Collection is returned. 282 | * 283 | * @param callable $function Must take 2 arguments, intermediate value and item from the iterator. 284 | * @param mixed $startValue 285 | * @param bool $convertToCollection 286 | * @return mixed|Collection 287 | */ 288 | public function reduceRight(callable $function, $startValue, $convertToCollection = false) 289 | { 290 | $result = reduceRight($this->getItems(), $function, $startValue); 291 | 292 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 293 | } 294 | 295 | /** 296 | * A form of slice that returns first $numberOfItems items. 297 | * 298 | * @param int $numberOfItems 299 | * @return Collection 300 | */ 301 | public function take($numberOfItems) 302 | { 303 | return take($this->getItems(), $numberOfItems); 304 | } 305 | 306 | /** 307 | * A form of slice that returns all but first $numberOfItems items. 308 | * 309 | * @param int $numberOfItems 310 | * @return Collection 311 | */ 312 | public function drop($numberOfItems) 313 | { 314 | return drop($this->getItems(), $numberOfItems); 315 | } 316 | 317 | /** 318 | * Returns collection of values from this collection but with keys being numerical from 0 upwards. 319 | * 320 | * @return Collection 321 | */ 322 | public function values() 323 | { 324 | return values($this->getItems()); 325 | } 326 | 327 | /** 328 | * Returns a lazy collection without elements matched by $function. 329 | * 330 | * @param callable $function 331 | * @return Collection 332 | */ 333 | public function reject(callable $function) 334 | { 335 | return reject($this->getItems(), $function); 336 | } 337 | 338 | /** 339 | * Returns a lazy collection of the keys of this collection. 340 | * 341 | * @return Collection 342 | */ 343 | public function keys() 344 | { 345 | return keys($this->getItems()); 346 | } 347 | 348 | /** 349 | * Returns a lazy collection of items of this collection separated by $separator 350 | * 351 | * @param mixed $separator 352 | * @return Collection 353 | */ 354 | public function interpose($separator) 355 | { 356 | return interpose($this->getItems(), $separator); 357 | } 358 | 359 | /** 360 | * Returns a lazy collection with last $numberOfItems items skipped. These are still iterated over, just skipped. 361 | * 362 | * @param int $numberOfItems 363 | * @return Collection 364 | */ 365 | public function dropLast($numberOfItems = 1) 366 | { 367 | return dropLast($this->getItems(), $numberOfItems); 368 | } 369 | 370 | /** 371 | * Returns a lazy collection of first item from first collection, first item from second, second from first and 372 | * so on. Accepts any number of collections. 373 | * 374 | * @param array|\Traversable ...$collections 375 | * @return Collection 376 | */ 377 | public function interleave(...$collections) 378 | { 379 | return interleave($this->getItems(), ...$collections); 380 | } 381 | 382 | /** 383 | * Returns an infinite lazy collection of items in this collection repeated infinitely. 384 | * 385 | * @return Collection 386 | */ 387 | public function cycle() 388 | { 389 | return cycle($this->getItems()); 390 | } 391 | 392 | /** 393 | * Returns a lazy collection of items of this collection with $value added as first element. If $key is not provided 394 | * it will be next integer index. 395 | * 396 | * @param mixed $value 397 | * @param mixed|null $key 398 | * @return Collection 399 | */ 400 | public function prepend($value, $key = null) 401 | { 402 | return prepend($this->getItems(), $value, $key); 403 | } 404 | 405 | /** 406 | * Returns a lazy collection of items of this collection with $value added as last element. If $key is not provided 407 | * it will be next integer index. 408 | * 409 | * @param mixed $value 410 | * @param mixed $key 411 | * @return Collection 412 | */ 413 | public function append($value, $key = null) 414 | { 415 | return append($this->getItems(), $value, $key); 416 | } 417 | 418 | /** 419 | * Returns a lazy collection by removing items from this collection until first item for which $function returns 420 | * false. 421 | * 422 | * @param callable $function 423 | * @return Collection 424 | */ 425 | public function dropWhile(callable $function) 426 | { 427 | return dropWhile($this->getItems(), $function); 428 | } 429 | 430 | /** 431 | * Returns a lazy collection which is a result of calling map($function) and then flatten(1) 432 | * 433 | * @param callable $function 434 | * @return Collection 435 | */ 436 | public function mapcat(callable $function) 437 | { 438 | return mapcat($this->getItems(), $function); 439 | } 440 | 441 | /** 442 | * Returns a lazy collection of items from the start of the ollection until the first item for which $function 443 | * returns false. 444 | * 445 | * @param callable $function 446 | * @return Collection 447 | */ 448 | public function takeWhile(callable $function) 449 | { 450 | return takeWhile($this->getItems(), $function); 451 | } 452 | 453 | /** 454 | * Returns a collection of [take($position), drop($position)] 455 | * 456 | * @param int $position 457 | * @return Collection 458 | */ 459 | public function splitAt($position) 460 | { 461 | return splitAt($this->getItems(), $position); 462 | } 463 | 464 | /** 465 | * Returns a collection of [takeWhile($predicament), dropWhile($predicament] 466 | * 467 | * @param callable $function 468 | * @return Collection 469 | */ 470 | public function splitWith(callable $function) 471 | { 472 | return splitWith($this->getItems(), $function); 473 | } 474 | 475 | /** 476 | * Returns a lazy collection with items from this collection but values that are found in keys of $replacementMap 477 | * are replaced by their values. 478 | * 479 | * @param array|\Traversable $replacementMap 480 | * @return Collection 481 | */ 482 | public function replace($replacementMap) 483 | { 484 | return replace($this->getItems(), $replacementMap); 485 | } 486 | 487 | /** 488 | * Returns a lazy collection of reduction steps. 489 | * 490 | * @param callable $function 491 | * @param mixed $startValue 492 | * @return Collection 493 | */ 494 | public function reductions(callable $function, $startValue) 495 | { 496 | return reductions($this->getItems(), $function, $startValue); 497 | } 498 | 499 | /** 500 | * Returns a lazy collection of every nth item in this collection 501 | * 502 | * @param int $step 503 | * @return Collection 504 | */ 505 | public function takeNth($step) 506 | { 507 | return takeNth($this->getItems(), $step); 508 | } 509 | 510 | /** 511 | * Returns a non-collection of shuffled items from this collection 512 | * 513 | * @return Collection 514 | */ 515 | public function shuffle() 516 | { 517 | return \DusanKasan\Knapsack\shuffle($this->getItems()); 518 | } 519 | 520 | /** 521 | * Returns a lazy collection of collections of $numberOfItems items each, at $step step 522 | * apart. If $step is not supplied, defaults to $numberOfItems, i.e. the partitions 523 | * do not overlap. If a $padding collection is supplied, use its elements as 524 | * necessary to complete last partition up to $numberOfItems items. In case there are 525 | * not enough padding elements, return a partition with less than $numberOfItems items. 526 | * 527 | * @param int $numberOfItems 528 | * @param int $step 529 | * @param array|\Traversable $padding 530 | * @return Collection 531 | */ 532 | public function partition($numberOfItems, $step = 0, $padding = []) 533 | { 534 | return partition($this->getItems(), $numberOfItems, $step, $padding); 535 | } 536 | 537 | /** 538 | * Creates a lazy collection of collections created by partitioning this collection every time $function will 539 | * return different result. 540 | * 541 | * @param callable $function 542 | * @return Collection 543 | */ 544 | public function partitionBy(callable $function) 545 | { 546 | return partitionBy($this->getItems(), $function); 547 | } 548 | 549 | /** 550 | * Returns true if this collection is empty. False otherwise. 551 | * 552 | * @return bool 553 | */ 554 | public function isEmpty() 555 | { 556 | return isEmpty($this->getItems()); 557 | } 558 | 559 | /** 560 | * Opposite of isEmpty. 561 | * 562 | * @return bool 563 | */ 564 | public function isNotEmpty() 565 | { 566 | return isNotEmpty($this->getItems()); 567 | } 568 | 569 | /** 570 | * Returns a collection where keys are distinct items from this collection and their values are number of 571 | * occurrences of each value. 572 | * 573 | * @return Collection 574 | */ 575 | public function frequencies() 576 | { 577 | return frequencies($this->getItems()); 578 | } 579 | 580 | /** 581 | * Returns first item of this collection. If the collection is empty, throws ItemNotFound. If $convertToCollection 582 | * is true and the return value is a collection (array|Traversable) an instance of Collection is returned. 583 | * 584 | * @param bool $convertToCollection 585 | * @return mixed|Collection 586 | * @throws \DusanKasan\Knapsack\Exceptions\ItemNotFound 587 | */ 588 | public function first($convertToCollection = false) 589 | { 590 | $result = first($this->getItems()); 591 | 592 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 593 | } 594 | 595 | /** 596 | * Returns last item of this collection. If the collection is empty, throws ItemNotFound. If $convertToCollection 597 | * is true and the return value is a collection (array|Traversable) it is converted to Collection. 598 | * 599 | * @param bool $convertToCollection 600 | * @return mixed|Collection 601 | * @throws \DusanKasan\Knapsack\Exceptions\ItemNotFound 602 | */ 603 | public function last($convertToCollection = false) 604 | { 605 | $result = last($this->getItems()); 606 | 607 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 608 | } 609 | 610 | /** 611 | * Realizes collection - turns lazy collection into non-lazy one by iterating over it and storing the key/values. 612 | * 613 | * @return Collection 614 | */ 615 | public function realize() 616 | { 617 | return realize($this->getItems()); 618 | } 619 | 620 | /** 621 | * Returns the second item in this collection or throws ItemNotFound if the collection is empty or has 1 item. If 622 | * $convertToCollection is true and the return value is a collection (array|Traversable) it is converted to 623 | * Collection. 624 | * 625 | * @param bool $convertToCollection 626 | * @return mixed|Collection 627 | * @throws \DusanKasan\Knapsack\Exceptions\ItemNotFound 628 | */ 629 | public function second($convertToCollection = false) 630 | { 631 | $result = second($this->getItems()); 632 | 633 | return ($convertToCollection && isCollection($result)) ? new Collection($result) : $result; 634 | } 635 | 636 | /** 637 | * Combines the values of this collection as keys, with values of $collection as values. The resulting collection 638 | * has length equal to the size of smaller collection. 639 | * 640 | * @param array|\Traversable $collection 641 | * @return Collection 642 | * @throws \DusanKasan\Knapsack\Exceptions\ItemNotFound 643 | */ 644 | public function combine($collection) 645 | { 646 | return combine($this->getItems(), $collection); 647 | } 648 | 649 | /** 650 | * Returns a lazy collection without the items associated to any of the keys from $keys. 651 | * 652 | * @param array|\Traversable $keys 653 | * @return Collection 654 | */ 655 | public function except($keys) 656 | { 657 | return except($this->getItems(), $keys); 658 | } 659 | 660 | /** 661 | * Returns a lazy collection of items associated to any of the keys from $keys. 662 | * 663 | * @param array|\Traversable $keys 664 | * @return Collection 665 | */ 666 | public function only($keys) 667 | { 668 | return only($this->getItems(), $keys); 669 | } 670 | 671 | /** 672 | * Returns a lazy collection of items that are in $this but are not in any of the other arguments, indexed by the 673 | * keys from the first collection. Note that the ...$collections are iterated non-lazily. 674 | * 675 | * @param array|\Traversable ...$collections 676 | * @return Collection 677 | */ 678 | public function diff(...$collections) 679 | { 680 | return diff($this->getItems(), ...$collections); 681 | } 682 | 683 | /** 684 | * Returns a lazy collection where keys and values are flipped. 685 | * 686 | * @return Collection 687 | */ 688 | public function flip() 689 | { 690 | return flip($this->getItems()); 691 | } 692 | 693 | /** 694 | * Checks for the existence of an item with$key in this collection. 695 | * 696 | * @param mixed $key 697 | * @return bool 698 | */ 699 | public function has($key) 700 | { 701 | return has($this->getItems(), $key); 702 | } 703 | 704 | /** 705 | * Returns a lazy collection of non-lazy collections of items from nth position from this collection and each 706 | * passed collection. Stops when any of the collections don't have an item at the nth position. 707 | * 708 | * @param array|\Traversable ...$collections 709 | * @return Collection 710 | */ 711 | public function zip(...$collections) 712 | { 713 | array_unshift($collections, $this->getItems()); 714 | 715 | return zip(...$collections); 716 | } 717 | 718 | /** 719 | * Uses a $transformer callable that takes a Collection and returns Collection on itself. 720 | * 721 | * @param callable $transformer Collection => Collection 722 | * @return Collection 723 | * @throws InvalidReturnValue 724 | */ 725 | public function transform(callable $transformer) 726 | { 727 | $items = $this->getItems(); 728 | 729 | $transformed = $transformer($items instanceof Collection ? $items : new Collection($items)); 730 | 731 | if (!($transformed instanceof Collection)) { 732 | throw new InvalidReturnValue; 733 | } 734 | 735 | return $transformed; 736 | } 737 | 738 | /** 739 | * Transpose each item in a collection, interchanging the row and column indexes. 740 | * Can only transpose collections of collections. Otherwise an InvalidArgument is raised. 741 | * 742 | * @return Collection 743 | */ 744 | public function transpose() 745 | { 746 | return transpose($this->getItems()); 747 | } 748 | 749 | /** 750 | * Extracts data from collection items by dot separated key path. Supports the * wildcard. If a key contains \ or 751 | * it must be escaped using \ character. 752 | * 753 | * @param mixed $keyPath 754 | * @return Collection 755 | */ 756 | public function extract($keyPath) 757 | { 758 | return \DusanKasan\Knapsack\extract($this->getItems(), $keyPath); 759 | } 760 | 761 | /** 762 | * Returns a lazy collection of items that are in $this and all the other arguments, indexed by the keys from 763 | * the first collection. Note that the ...$collections are iterated non-lazily. 764 | * 765 | * @param array|\Traversable ...$collections 766 | * @return Collection 767 | */ 768 | public function intersect(...$collections) 769 | { 770 | return intersect($this->getItems(), ...$collections); 771 | } 772 | 773 | /** 774 | * Checks whether this collection has exactly $size items. 775 | * 776 | * @param int $size 777 | * @return bool 778 | */ 779 | public function sizeIs($size) 780 | { 781 | return sizeIs($this->getItems(), $size); 782 | } 783 | 784 | /** 785 | * Checks whether this collection has less than $size items. 786 | * 787 | * @param int $size 788 | * @return bool 789 | */ 790 | public function sizeIsLessThan($size) 791 | { 792 | return sizeIsLessThan($this->getItems(), $size); 793 | } 794 | 795 | /** 796 | * Checks whether this collection has more than $size items. 797 | * 798 | * @param int $size 799 | * @return bool 800 | */ 801 | public function sizeIsGreaterThan($size) 802 | { 803 | return sizeIsGreaterThan($this->getItems(), $size); 804 | } 805 | 806 | /** 807 | * Checks whether this collection has between $fromSize to $toSize items. $toSize can be 808 | * smaller than $fromSize. 809 | * 810 | * @param int $fromSize 811 | * @param int $toSize 812 | * @return bool 813 | */ 814 | public function sizeIsBetween($fromSize, $toSize) 815 | { 816 | return sizeIsBetween($this->getItems(), $fromSize, $toSize); 817 | } 818 | 819 | /** 820 | * Returns a sum of all values in this collection. 821 | * 822 | * @return int|float 823 | */ 824 | public function sum() 825 | { 826 | return sum($this->getItems()); 827 | } 828 | 829 | /** 830 | * Returns average of values from this collection. 831 | * 832 | * @return int|float 833 | */ 834 | public function average() 835 | { 836 | return average($this->getItems()); 837 | } 838 | 839 | /** 840 | * Returns maximal value from this collection. 841 | * 842 | * @return mixed 843 | */ 844 | public function max() 845 | { 846 | return \DusanKasan\Knapsack\max($this->getItems()); 847 | } 848 | 849 | /** 850 | * Returns minimal value from this collection. 851 | * 852 | * @return mixed 853 | */ 854 | public function min() 855 | { 856 | return \DusanKasan\Knapsack\min($this->getItems()); 857 | } 858 | 859 | /** 860 | * Returns a string by concatenating the collection values into a string. 861 | * 862 | * @return string 863 | */ 864 | public function toString() 865 | { 866 | return toString($this->getItems()); 867 | } 868 | 869 | /** 870 | * Returns a lazy collection with items from $collection, but items with keys that are found in keys of 871 | * $replacementMap are replaced by their values. 872 | * 873 | * @param array|\Traversable $replacementMap 874 | * @return Collection 875 | */ 876 | public function replaceByKeys($replacementMap) 877 | { 878 | return replaceByKeys($this->getItems(), $replacementMap); 879 | } 880 | 881 | /** 882 | * /** 883 | * Dumps this collection into array (recursively). 884 | * 885 | * - scalars are returned as they are, 886 | * - array of class name => properties (name => value and only properties accessible for this class) 887 | * is returned for objects, 888 | * - arrays or Traversables are returned as arrays, 889 | * - for anything else result of calling gettype($input) is returned 890 | * 891 | * If specified, $maxItemsPerCollection will only leave specified number of items in collection, 892 | * appending a new element at end '>>>' if original collection was longer. 893 | * 894 | * If specified, $maxDepth will only leave specified n levels of nesting, replacing elements 895 | * with '^^^' once the maximum nesting level was reached. 896 | * 897 | * If a collection with duplicate keys is encountered, the duplicate keys (except the first one) 898 | * will be change into a format originalKey//duplicateCounter where duplicateCounter starts from 899 | * 1 at the first duplicate. So [0 => 1, 0 => 2] will become [0 => 1, '0//1' => 2] 900 | * 901 | * @param int|null $maxItemsPerCollection 902 | * @param int|null $maxDepth 903 | * @return array 904 | */ 905 | public function dump($maxItemsPerCollection = null, $maxDepth = null) 906 | { 907 | return dump($this->getItems(), $maxItemsPerCollection, $maxDepth); 908 | } 909 | 910 | /** 911 | * Calls dump on this collection and then prints it using the var_export. 912 | * 913 | * @param int|null $maxItemsPerCollection 914 | * @param int|null $maxDepth 915 | * @return Collection 916 | */ 917 | public function printDump($maxItemsPerCollection = null, $maxDepth = null) 918 | { 919 | return printDump($this->getItems(), $maxItemsPerCollection, $maxDepth); 920 | } 921 | 922 | /** 923 | * @return array|\Traversable 924 | */ 925 | protected function getItems() 926 | { 927 | return $this; 928 | } 929 | } 930 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidArgument.php: -------------------------------------------------------------------------------- 1 | $value) { 35 | if (!in_array($value, $distinctValues)) { 36 | $distinctValues[] = $value; 37 | yield $key => $value; 38 | } 39 | } 40 | }; 41 | 42 | return new Collection($generatorFactory); 43 | } 44 | 45 | /** 46 | * Returns number of items in $collection. 47 | * 48 | * @param array|Traversable $collection 49 | * @return int 50 | */ 51 | function size($collection) 52 | { 53 | $result = 0; 54 | foreach ($collection as $value) { 55 | $result++; 56 | } 57 | 58 | return $result; 59 | } 60 | 61 | /** 62 | * Returns a non-lazy collection with items from $collection in reversed order. 63 | * 64 | * @param array|Traversable $collection 65 | * @return Collection 66 | */ 67 | function reverse($collection) 68 | { 69 | $generatorFactory = function () use ($collection) { 70 | $array = []; 71 | foreach ($collection as $key => $value) { 72 | $array[] = [$key, $value]; 73 | } 74 | 75 | return map( 76 | indexBy( 77 | array_reverse($array), 78 | function ($item) { 79 | return $item[0]; 80 | } 81 | ), 82 | function ($item) { 83 | return $item[1]; 84 | } 85 | ); 86 | }; 87 | 88 | return new Collection($generatorFactory); 89 | } 90 | 91 | /** 92 | * Returns a lazy collection of values from $collection (i.e. the keys are reset). 93 | * 94 | * @param array|Traversable $collection 95 | * @return Collection 96 | */ 97 | function values($collection) 98 | { 99 | $generatorFactory = function () use ($collection) { 100 | foreach ($collection as $value) { 101 | yield $value; 102 | } 103 | }; 104 | 105 | return new Collection($generatorFactory); 106 | } 107 | 108 | /** 109 | * Returns a lazy collection of keys from $collection. 110 | * 111 | * @param array|Traversable $collection 112 | * @return Collection 113 | */ 114 | function keys($collection) 115 | { 116 | $generatorFactory = function () use ($collection) { 117 | foreach ($collection as $key => $value) { 118 | yield $key; 119 | } 120 | }; 121 | 122 | return new Collection($generatorFactory); 123 | } 124 | 125 | /** 126 | * Returns a lazy collection of items from $collection repeated infinitely. 127 | * 128 | * @param array|Traversable $collection 129 | * @return Collection 130 | */ 131 | function cycle($collection) 132 | { 133 | $generatorFactory = function () use ($collection) { 134 | while (true) { 135 | foreach ($collection as $key => $value) { 136 | yield $key => $value; 137 | } 138 | } 139 | }; 140 | 141 | return new Collection($generatorFactory); 142 | } 143 | 144 | /** 145 | * Returns a non-lazy collection of shuffled items from $collection. 146 | * 147 | * @param array|Traversable $collection 148 | * @return Collection 149 | */ 150 | function shuffle($collection) 151 | { 152 | $buffer = []; 153 | foreach ($collection as $key => $value) { 154 | $buffer[] = [$key, $value]; 155 | } 156 | 157 | \shuffle($buffer); 158 | 159 | return dereferenceKeyValue($buffer); 160 | } 161 | 162 | /** 163 | * Returns true if $collection does not contain any items. 164 | * 165 | * @param array|Traversable $collection 166 | * @return bool 167 | */ 168 | function isEmpty($collection) 169 | { 170 | foreach ($collection as $value) { 171 | return false; 172 | } 173 | 174 | return true; 175 | } 176 | 177 | /** 178 | * Returns true if $collection does contain any items. 179 | * 180 | * @param array|Traversable $collection 181 | * @return bool 182 | */ 183 | function isNotEmpty($collection) 184 | { 185 | return !isEmpty($collection); 186 | } 187 | 188 | /** 189 | * Returns a collection where keys are distinct values from $collection and values are number of occurrences of each 190 | * value. 191 | * 192 | * @param array|Traversable $collection 193 | * @return Collection 194 | */ 195 | function frequencies($collection) 196 | { 197 | return countBy($collection, '\DusanKasan\Knapsack\identity'); 198 | } 199 | 200 | /** 201 | * Returns the first item of $collection or throws ItemNotFound if #collection is empty. 202 | * 203 | * @param array|Traversable $collection 204 | * @return mixed 205 | */ 206 | function first($collection) 207 | { 208 | return get(values($collection), 0); 209 | } 210 | 211 | /** 212 | * Returns the last item of $collection or throws ItemNotFound if #collection is empty. 213 | * 214 | * @param array|Traversable $collection 215 | * @return mixed 216 | */ 217 | function last($collection) 218 | { 219 | return first(reverse($collection)); 220 | } 221 | 222 | /** 223 | * Returns a lazy collection of items of $collection where value of each item is set to the return value of calling 224 | * $function on its value and key. 225 | * 226 | * @param array|Traversable $collection 227 | * @param callable $function ($value, $key) 228 | * @return Collection 229 | */ 230 | function map($collection, callable $function) 231 | { 232 | $generatorFactory = function () use ($collection, $function) { 233 | foreach ($collection as $key => $value) { 234 | yield $key => $function($value, $key); 235 | } 236 | }; 237 | 238 | return new Collection($generatorFactory); 239 | } 240 | 241 | /** 242 | * Returns a lazy collection of items from $collection for which $function returns true. 243 | * 244 | * @param array|Traversable $collection 245 | * @param callable|null $function ($value, $key) 246 | * @return Collection 247 | */ 248 | function filter($collection, callable $function = null) 249 | { 250 | if (null === $function) { 251 | $function = function ($value) { 252 | return (bool) $value; 253 | }; 254 | } 255 | 256 | $generatorFactory = function () use ($collection, $function) { 257 | foreach ($collection as $key => $value) { 258 | if ($function($value, $key)) { 259 | yield $key => $value; 260 | } 261 | } 262 | }; 263 | 264 | return new Collection($generatorFactory); 265 | } 266 | 267 | /** 268 | * Returns a lazy collection with items from all $collections passed as argument appended together 269 | * 270 | * @param array|Traversable ...$collections 271 | * @return Collection 272 | */ 273 | function concat(...$collections) 274 | { 275 | $generatorFactory = function () use ($collections) { 276 | foreach ($collections as $collection) { 277 | foreach ($collection as $key => $value) { 278 | yield $key => $value; 279 | } 280 | } 281 | }; 282 | 283 | return new Collection($generatorFactory); 284 | } 285 | 286 | /** 287 | * Reduces the collection to single value by iterating over the collection and calling $reduction while 288 | * passing $startValue and current key/item as parameters. The output of $function is used as $startValue in 289 | * next iteration. The output of $function on last element is the return value of this function. 290 | * 291 | * @param array|Traversable $collection 292 | * @param callable $function ($tmpValue, $value, $key) 293 | * @param mixed $startValue 294 | * @return mixed 295 | */ 296 | function reduce($collection, callable $function, $startValue) 297 | { 298 | $tmp = duplicate($startValue); 299 | 300 | foreach ($collection as $key => $value) { 301 | $tmp = $function($tmp, $value, $key); 302 | } 303 | 304 | return $tmp; 305 | } 306 | 307 | /** 308 | * Flattens multiple levels of nesting in collection. If $levelsToFlatten is not specified, flattens all levels of 309 | * nesting. 310 | * 311 | * @param array|Traversable $collection 312 | * @param int $levelsToFlatten -1 to flatten everything 313 | * @return Collection 314 | */ 315 | function flatten($collection, $levelsToFlatten = -1) 316 | { 317 | $generatorFactory = function () use ($collection, $levelsToFlatten) { 318 | $flattenNextLevel = $levelsToFlatten < 0 || $levelsToFlatten > 0; 319 | $childLevelsToFlatten = $levelsToFlatten > 0 ? $levelsToFlatten - 1 : $levelsToFlatten; 320 | 321 | foreach ($collection as $key => $value) { 322 | if ($flattenNextLevel && (is_array($value) || $value instanceof Traversable)) { 323 | foreach (flatten($value, $childLevelsToFlatten) as $childKey => $childValue) { 324 | yield $childKey => $childValue; 325 | } 326 | } else { 327 | yield $key => $value; 328 | } 329 | } 330 | }; 331 | 332 | return new Collection($generatorFactory); 333 | } 334 | 335 | /** 336 | * Returns a non-lazy collection sorted using $collection($item1, $item2, $key1, $key2 ). $collection should 337 | * return true if first item is larger than the second and false otherwise. 338 | * 339 | * @param array|Traversable $collection 340 | * @param callable $function ($value1, $value2, $key1, $key2) 341 | * @return Collection 342 | */ 343 | function sort($collection, callable $function) 344 | { 345 | $array = iterator_to_array( 346 | values( 347 | map( 348 | $collection, 349 | function ($value, $key) { 350 | return [$key, $value]; 351 | } 352 | ) 353 | ) 354 | ); 355 | 356 | uasort( 357 | $array, 358 | function ($a, $b) use ($function) { 359 | return $function($a[1], $b[1], $a[0], $b[0]); 360 | } 361 | ); 362 | 363 | return dereferenceKeyValue($array); 364 | } 365 | 366 | /** 367 | * Returns a lazy collection that is a part of $collection starting from $from position and ending in $to position. 368 | * If $to is not provided, the returned collection is contains all items from $from until end of $collection. All items 369 | * before $from are iterated over, but not included in result. 370 | * 371 | * @param array|Traversable $collection 372 | * @param int $from 373 | * @param int $to -1 to slice until end 374 | * @return Collection 375 | */ 376 | function slice($collection, $from, $to = -1) 377 | { 378 | $generatorFactory = function () use ($collection, $from, $to) { 379 | $index = 0; 380 | foreach ($collection as $key => $value) { 381 | if ($index >= $from && ($index < $to || $to == -1)) { 382 | yield $key => $value; 383 | } elseif ($index >= $to && $to >= 0) { 384 | break; 385 | } 386 | 387 | $index++; 388 | } 389 | }; 390 | 391 | return new Collection($generatorFactory); 392 | } 393 | 394 | /** 395 | * Returns a non-lazy collection of items grouped by the result of $function. 396 | * 397 | * @param array|Traversable $collection 398 | * @param callable $function ($value, $key) 399 | * @return Collection 400 | */ 401 | function groupBy($collection, callable $function) 402 | { 403 | $result = []; 404 | 405 | foreach ($collection as $key => $value) { 406 | $newKey = $function($value, $key); 407 | 408 | $result[$newKey][] = $value; 409 | } 410 | 411 | return Collection::from($result) 412 | ->map(function ($entry) { 413 | return new Collection($entry); 414 | }); 415 | } 416 | 417 | /** 418 | * Returns a non-lazy collection of items grouped by the value at given key. Ignores non-collection items and items 419 | * without the given keys 420 | * 421 | * @param array|Traversable $collection 422 | * @param mixed $key 423 | * @return Collection 424 | */ 425 | function groupByKey($collection, $key) 426 | { 427 | $generatorFactory = function () use ($collection, $key) { 428 | 429 | return groupBy( 430 | filter( 431 | $collection, 432 | function ($item) use ($key) { 433 | return isCollection($item) && has($item, $key); 434 | } 435 | ), 436 | function ($value) use ($key) { 437 | return get($value, $key); 438 | } 439 | ); 440 | }; 441 | 442 | return new Collection($generatorFactory); 443 | } 444 | /** 445 | * Executes $function for each item in $collection 446 | * 447 | * @param array|Traversable $collection 448 | * @param callable $function ($value, $key) 449 | * @return Collection 450 | */ 451 | function each($collection, callable $function) 452 | { 453 | $generatorFactory = function () use ($collection, $function) { 454 | foreach ($collection as $key => $value) { 455 | $function($value, $key); 456 | 457 | yield $key => $value; 458 | } 459 | }; 460 | 461 | return new Collection($generatorFactory); 462 | } 463 | 464 | /** 465 | * Returns an item with $key key from $collection. If that key is not present, throws ItemNotFound. 466 | * 467 | * @param array|Traversable $collection 468 | * @param mixed $key 469 | * @return mixed 470 | */ 471 | function get($collection, $key) 472 | { 473 | foreach ($collection as $valueKey => $value) { 474 | if ($key === $valueKey) { 475 | return $value; 476 | } 477 | } 478 | 479 | throw new ItemNotFound; 480 | } 481 | 482 | /** 483 | * Returns an item with $key key from $collection. If that key is not present, returns $default. 484 | * 485 | * @param array|Traversable $collection 486 | * @param mixed $key 487 | * @param mixed $default value returned if key is not found 488 | * @return mixed 489 | */ 490 | function getOrDefault($collection, $key, $default) 491 | { 492 | try { 493 | return get($collection, $key); 494 | } catch (ItemNotFound $e) { 495 | return $default; 496 | } 497 | } 498 | 499 | /** 500 | * Returns the first item from $collection for which $function returns true. If item like that is not present, returns 501 | * $default. 502 | * 503 | * @param array|Traversable $collection 504 | * @param callable $function ($value, $key) 505 | * @param mixed $default 506 | * @return mixed 507 | */ 508 | function find($collection, callable $function, $default = null) 509 | { 510 | foreach ($collection as $key => $value) { 511 | if ($function($value, $key)) { 512 | return $value; 513 | } 514 | } 515 | 516 | return $default; 517 | } 518 | 519 | /** 520 | * Returns a lazy collection by changing keys of $collection for each item to the result of $function for 521 | * that item. 522 | * 523 | * @param array|Traversable $collection 524 | * @param callable $function ($value, $key) 525 | * @return Collection 526 | */ 527 | function indexBy($collection, callable $function) 528 | { 529 | $generatorFactory = function () use ($collection, $function) { 530 | foreach ($collection as $key => $value) { 531 | yield $function($value, $key) => $value; 532 | } 533 | }; 534 | 535 | return new Collection($generatorFactory); 536 | } 537 | 538 | /** 539 | * Returns a non-lazy collection of items whose keys are the return values of $function and values are the number of 540 | * items in this collection for which the $function returned this value. 541 | * 542 | * @param array|Traversable $collection 543 | * @param callable $function ($value, $key) 544 | * @return Collection 545 | */ 546 | function countBy($collection, callable $function) 547 | { 548 | return map( 549 | groupBy($collection, $function), 550 | '\DusanKasan\Knapsack\size' 551 | ); 552 | } 553 | 554 | /** 555 | * Returns true if $function returns true for every item in $collection 556 | * 557 | * @param array|Traversable $collection 558 | * @param callable $function ($value, $key) 559 | * @return bool 560 | */ 561 | function every($collection, callable $function) 562 | { 563 | foreach ($collection as $key => $value) { 564 | if (!$function($value, $key)) { 565 | return false; 566 | } 567 | } 568 | 569 | return true; 570 | } 571 | 572 | /** 573 | * Returns true if $function returns true for at least one item in $collection. 574 | * 575 | * @param array|Traversable $collection 576 | * @param callable $function ($value, $key) 577 | * @return bool 578 | */ 579 | function some($collection, callable $function) 580 | { 581 | foreach ($collection as $key => $value) { 582 | if ($function($value, $key)) { 583 | return true; 584 | } 585 | } 586 | 587 | return false; 588 | } 589 | 590 | /** 591 | * Returns true if $needle is found in $collection values. 592 | * 593 | * @param array|Traversable $collection 594 | * @param mixed $needle 595 | * @return bool 596 | */ 597 | function contains($collection, $needle) 598 | { 599 | foreach ($collection as $key => $value) { 600 | if ($value === $needle) { 601 | return true; 602 | } 603 | } 604 | 605 | return false; 606 | } 607 | 608 | /** 609 | * Reduce that walks from right to the left. 610 | * 611 | * @param array|Traversable $collection 612 | * @param callable $function 613 | * @param mixed $startValue 614 | * @return mixed 615 | */ 616 | function reduceRight($collection, callable $function, $startValue) 617 | { 618 | return reduce(reverse($collection), $function, $startValue); 619 | } 620 | 621 | /** 622 | * Returns a lazy collection of first $numberOfItems items of $collection. 623 | * 624 | * @param array|Traversable $collection 625 | * @param int $numberOfItems 626 | * @return Collection 627 | */ 628 | function take($collection, $numberOfItems) 629 | { 630 | return slice($collection, 0, $numberOfItems); 631 | } 632 | 633 | /** 634 | * Returns a lazy collection of all but first $numberOfItems items of $collection. 635 | * 636 | * @param array|Traversable $collection 637 | * @param int $numberOfItems 638 | * @return Collection 639 | */ 640 | function drop($collection, $numberOfItems) 641 | { 642 | return slice($collection, $numberOfItems); 643 | } 644 | 645 | /** 646 | * Returns a lazy collection of values, where first value is $value and all subsequent values are computed by applying 647 | * $function to the last value in the collection. By default this produces an infinite collection. However you can 648 | * end the collection by throwing a NoMoreItems exception. 649 | * 650 | * @param mixed $value 651 | * @param callable $function ($value, $key) 652 | * @return Collection 653 | */ 654 | function iterate($value, callable $function) 655 | { 656 | $duplicated = duplicate($value); 657 | $generatorFactory = function () use ($duplicated, $function) { 658 | $value = $duplicated; 659 | 660 | yield $value; 661 | 662 | while (true) { 663 | try { 664 | $value = $function($value); 665 | yield $value; 666 | } catch (NoMoreItems $e) { 667 | break; 668 | } 669 | } 670 | }; 671 | 672 | return new Collection($generatorFactory); 673 | } 674 | 675 | /** 676 | * Returns a lazy collection of items from $collection for which $function returned true. 677 | * 678 | * @param array|Traversable $collection 679 | * @param callable $function ($value, $key) 680 | * @return Collection 681 | */ 682 | function reject($collection, callable $function) 683 | { 684 | return filter( 685 | $collection, 686 | function ($value, $key) use ($function) { 687 | return !$function($value, $key); 688 | } 689 | ); 690 | } 691 | 692 | /** 693 | * Returns a lazy collection of items in $collection without the last $numberOfItems items. 694 | * 695 | * @param array|Traversable $collection 696 | * @param int $numberOfItems 697 | * @return Collection 698 | */ 699 | function dropLast($collection, $numberOfItems = 1) 700 | { 701 | $generatorFactory = function () use ($collection, $numberOfItems) { 702 | $buffer = []; 703 | 704 | foreach ($collection as $key => $value) { 705 | $buffer[] = [$key, $value]; 706 | 707 | if (count($buffer) > $numberOfItems) { 708 | $val = array_shift($buffer); 709 | yield $val[0] => $val[1]; 710 | } 711 | } 712 | }; 713 | 714 | return new Collection($generatorFactory); 715 | } 716 | 717 | /** 718 | * Returns a lazy collection of items from $collection separated by $separator. 719 | * 720 | * @param array|Traversable $collection 721 | * @param mixed $separator 722 | * @return Collection 723 | */ 724 | function interpose($collection, $separator) 725 | { 726 | $generatorFactory = function () use ($collection, $separator) { 727 | foreach (take($collection, 1) as $key => $value) { 728 | yield $key => $value; 729 | } 730 | 731 | foreach (drop($collection, 1) as $key => $value) { 732 | yield $separator; 733 | yield $key => $value; 734 | } 735 | }; 736 | 737 | return new Collection($generatorFactory); 738 | } 739 | 740 | /** 741 | * Returns a lazy collection of first item from first collection, first item from second, second from first and 742 | * so on. Accepts any number of collections. 743 | * 744 | * @param array|Traversable ...$collections 745 | * @return Collection 746 | */ 747 | function interleave(...$collections) 748 | { 749 | $generatorFactory = function () use ($collections) { 750 | /* @var Iterator[] $iterators */ 751 | $iterators = array_map( 752 | function ($collection) { 753 | $it = new IteratorIterator(new Collection($collection)); 754 | $it->rewind(); 755 | return $it; 756 | }, 757 | $collections 758 | ); 759 | 760 | do { 761 | $valid = false; 762 | foreach ($iterators as $it) { 763 | if ($it->valid()) { 764 | yield $it->key() => $it->current(); 765 | $it->next(); 766 | $valid = true; 767 | } 768 | } 769 | } while ($valid); 770 | }; 771 | 772 | return new Collection($generatorFactory); 773 | } 774 | 775 | /** 776 | * Returns a lazy collection of items in $collection with $value added as first element. If $key is not provided 777 | * it will be next integer index. 778 | * 779 | * @param array|Traversable $collection 780 | * @param mixed $value 781 | * @param mixed|null $key 782 | * @return Collection 783 | */ 784 | function prepend($collection, $value, $key = null) 785 | { 786 | $generatorFactory = function () use ($collection, $value, $key) { 787 | if ($key === null) { 788 | yield $value; 789 | } else { 790 | yield $key => $value; 791 | } 792 | 793 | foreach ($collection as $key => $value) { 794 | yield $key => $value; 795 | } 796 | }; 797 | 798 | return new Collection($generatorFactory); 799 | } 800 | 801 | /** 802 | * Returns a lazy collection of items in $collection with $value added as last element. If $key is not provided 803 | * it will be next integer index. 804 | * 805 | * @param array|Traversable $collection 806 | * @param mixed $value 807 | * @param mixed|null $key 808 | * @return Collection 809 | */ 810 | function append($collection, $value, $key = null) 811 | { 812 | $generatorFactory = function () use ($collection, $value, $key) { 813 | foreach ($collection as $k => $v) { 814 | yield $k => $v; 815 | } 816 | 817 | if ($key === null) { 818 | yield $value; 819 | } else { 820 | yield $key => $value; 821 | } 822 | }; 823 | 824 | return new Collection($generatorFactory); 825 | } 826 | 827 | /** 828 | * Returns a lazy collection by removing items from $collection until first item for which $function returns false. 829 | * 830 | * @param array|Traversable $collection 831 | * @param callable $function ($value, $key) 832 | * @return Collection 833 | */ 834 | function dropWhile($collection, callable $function) 835 | { 836 | $generatorFactory = function () use ($collection, $function) { 837 | $shouldDrop = true; 838 | foreach ($collection as $key => $value) { 839 | if ($shouldDrop) { 840 | $shouldDrop = $function($value, $key); 841 | } 842 | 843 | if (!$shouldDrop) { 844 | yield $key => $value; 845 | } 846 | } 847 | }; 848 | 849 | return new Collection($generatorFactory); 850 | } 851 | 852 | /** 853 | * Returns a lazy collection of items from $collection until first item for which $function returns false. 854 | * 855 | * @param array|Traversable $collection 856 | * @param callable $function ($value, $key) 857 | * @return Collection 858 | */ 859 | function takeWhile($collection, callable $function) 860 | { 861 | $generatorFactory = function () use ($collection, $function) { 862 | $shouldTake = true; 863 | foreach ($collection as $key => $value) { 864 | if ($shouldTake) { 865 | $shouldTake = $function($value, $key); 866 | } 867 | 868 | if ($shouldTake) { 869 | yield $key => $value; 870 | } 871 | } 872 | }; 873 | 874 | return new Collection($generatorFactory); 875 | } 876 | 877 | /** 878 | * Returns a lazy collection. A result of calling map and flatten(1) 879 | * 880 | * @param array|Traversable $collection 881 | * @param callable $function ($value, $key) 882 | * @return Collection 883 | */ 884 | function mapcat($collection, callable $function) 885 | { 886 | return flatten(map($collection, $function), 1); 887 | } 888 | 889 | /** 890 | * Returns a lazy collection [take($collection, $position), drop($collection, $position)] 891 | * 892 | * @param array|Traversable $collection 893 | * @param int $position 894 | * @return Collection 895 | */ 896 | function splitAt($collection, $position) 897 | { 898 | $generatorFactory = function () use ($collection, $position) { 899 | yield take($collection, $position); 900 | yield drop($collection, $position); 901 | }; 902 | 903 | return new Collection($generatorFactory); 904 | } 905 | 906 | /** 907 | * Returns a lazy collection [takeWhile($collection, $function), dropWhile($collection, $function)] 908 | * 909 | * @param array|Traversable $collection 910 | * @param callable $function ($value, $key) 911 | * @return Collection 912 | */ 913 | function splitWith($collection, callable $function) 914 | { 915 | $generatorFactory = function () use ($collection, $function) { 916 | yield takeWhile($collection, $function); 917 | yield dropWhile($collection, $function); 918 | }; 919 | 920 | return new Collection($generatorFactory); 921 | } 922 | 923 | /** 924 | * Returns a lazy collection with items from $collection but values that are found in keys of $replacementMap 925 | * are replaced by their values. 926 | * 927 | * @param array|Traversable $collection 928 | * @param array|Traversable $replacementMap 929 | * @return Collection 930 | */ 931 | function replace($collection, $replacementMap) 932 | { 933 | $generatorFactory = function () use ($collection, $replacementMap) { 934 | foreach ($collection as $key => $value) { 935 | $newValue = getOrDefault($replacementMap, $value, $value); 936 | yield $key => $newValue; 937 | } 938 | }; 939 | 940 | return new Collection($generatorFactory); 941 | } 942 | 943 | /** 944 | * Returns a lazy collection of reduction steps. 945 | * 946 | * @param array|Traversable $collection 947 | * @param callable $function 948 | * @param mixed $startValue 949 | * @return Collection 950 | */ 951 | function reductions($collection, callable $function, $startValue) 952 | { 953 | $generatorFactory = function () use ($collection, $function, $startValue) { 954 | $tmp = duplicate($startValue); 955 | 956 | yield $tmp; 957 | foreach ($collection as $key => $value) { 958 | $tmp = $function($tmp, $value, $key); 959 | yield $tmp; 960 | } 961 | }; 962 | 963 | return new Collection($generatorFactory); 964 | } 965 | 966 | /** 967 | * Returns a lazy collection of every nth ($step) item in $collection. 968 | * 969 | * @param array|Traversable $collection 970 | * @param int $step 971 | * @return Collection 972 | */ 973 | function takeNth($collection, $step) 974 | { 975 | $generatorFactory = function () use ($collection, $step) { 976 | $index = 0; 977 | foreach ($collection as $key => $value) { 978 | if ($index % $step == 0) { 979 | yield $key => $value; 980 | } 981 | 982 | $index++; 983 | } 984 | }; 985 | 986 | return new Collection($generatorFactory); 987 | } 988 | 989 | /** 990 | * Returns a lazy collection of collections of $numberOfItems items each, at $step step 991 | * apart. If $step is not supplied, defaults to $numberOfItems, i.e. the partitions 992 | * do not overlap. If a $padding collection is supplied, use its elements as 993 | * necessary to complete last partition up to $numberOfItems items. In case there are 994 | * not enough padding elements, return a partition with less than $numberOfItems items. 995 | * 996 | * @param array|Traversable $collection 997 | * @param int $numberOfItems 998 | * @param int $step 999 | * @param array|Traversable $padding 1000 | * @return Collection 1001 | */ 1002 | function partition($collection, $numberOfItems, $step = -1, $padding = []) 1003 | { 1004 | $generatorFactory = function () use ($collection, $numberOfItems, $step, $padding) { 1005 | $buffer = []; 1006 | $itemsToSkip = 0; 1007 | $tmpStep = $step ?: $numberOfItems; 1008 | 1009 | foreach ($collection as $key => $value) { 1010 | if (count($buffer) == $numberOfItems) { 1011 | yield dereferenceKeyValue($buffer); 1012 | 1013 | $buffer = array_slice($buffer, $tmpStep); 1014 | $itemsToSkip = $tmpStep - $numberOfItems; 1015 | } 1016 | 1017 | if ($itemsToSkip <= 0) { 1018 | $buffer[] = [$key, $value]; 1019 | } else { 1020 | $itemsToSkip--; 1021 | } 1022 | } 1023 | 1024 | yield take( 1025 | concat(dereferenceKeyValue($buffer), $padding), 1026 | $numberOfItems 1027 | ); 1028 | }; 1029 | 1030 | return new Collection($generatorFactory); 1031 | } 1032 | 1033 | /** 1034 | * Returns a lazy collection created by partitioning $collection each time $function returned a different value. 1035 | * 1036 | * @param array|Traversable $collection 1037 | * @param callable $function 1038 | * @return Collection 1039 | */ 1040 | function partitionBy($collection, callable $function) 1041 | { 1042 | $generatorFactory = function () use ($collection, $function) { 1043 | $result = null; 1044 | $buffer = []; 1045 | 1046 | foreach ($collection as $key => $value) { 1047 | $newResult = $function($value, $key); 1048 | 1049 | if (!empty($buffer) && $result != $newResult) { 1050 | yield dereferenceKeyValue($buffer); 1051 | $buffer = []; 1052 | } 1053 | 1054 | $result = $newResult; 1055 | $buffer[] = [$key, $value]; 1056 | } 1057 | 1058 | if (!empty($buffer)) { 1059 | yield dereferenceKeyValue($buffer); 1060 | } 1061 | }; 1062 | 1063 | return new Collection($generatorFactory); 1064 | } 1065 | 1066 | /** 1067 | * Returns a lazy collection of $value repeated $times times. If $times is not provided the collection is infinite. 1068 | * 1069 | * @param mixed $value 1070 | * @param int $times 1071 | * @return Collection 1072 | */ 1073 | function repeat($value, $times = -1) 1074 | { 1075 | $generatorFactory = function () use ($value, $times) { 1076 | $tmpTimes = $times; 1077 | 1078 | while ($tmpTimes != 0) { 1079 | yield $value; 1080 | 1081 | $tmpTimes = $tmpTimes < 0 ? -1 : $tmpTimes - 1; 1082 | } 1083 | }; 1084 | 1085 | return new Collection($generatorFactory); 1086 | } 1087 | 1088 | /** 1089 | * Returns a lazy collection of numbers starting at $start, incremented by $step until $end is reached. 1090 | * 1091 | * @param int $start 1092 | * @param int|null $end 1093 | * @param int $step 1094 | * @return Collection 1095 | */ 1096 | function range($start = 0, $end = null, $step = 1) 1097 | { 1098 | $generatorFactory = function () use ($start, $end, $step) { 1099 | return iterate( 1100 | $start, 1101 | function ($value) use ($step, $end) { 1102 | $result = $value + $step; 1103 | 1104 | if ($end !== null && $result > $end) { 1105 | throw new NoMoreItems; 1106 | } 1107 | 1108 | return $result; 1109 | } 1110 | ); 1111 | }; 1112 | 1113 | return new Collection($generatorFactory); 1114 | } 1115 | 1116 | /** 1117 | * Returns true if $input is array or Traversable object. 1118 | * 1119 | * @param mixed $input 1120 | * @return bool 1121 | */ 1122 | function isCollection($input) 1123 | { 1124 | return is_array($input) || $input instanceof Traversable; 1125 | } 1126 | 1127 | /** 1128 | * Returns duplicated/cloned $input that has no relation to the original one. Used for making sure there are no side 1129 | * effect in functions. 1130 | * 1131 | * @param mixed $input 1132 | * @return mixed 1133 | */ 1134 | function duplicate($input) 1135 | { 1136 | if (is_array($input)) { 1137 | return toArray( 1138 | map( 1139 | $input, 1140 | function ($i) { 1141 | return duplicate($i); 1142 | } 1143 | ) 1144 | ); 1145 | } elseif (is_object($input)) { 1146 | return clone $input; 1147 | } else { 1148 | return $input; 1149 | } 1150 | } 1151 | 1152 | /** 1153 | * Transforms [[$key, $value], [$key2, $value2]] into [$key => $value, $key2 => $value2]. Used as a helper 1154 | * 1155 | * @param array|Traversable $collection 1156 | * @return Collection 1157 | */ 1158 | function dereferenceKeyValue($collection) 1159 | { 1160 | $generatorFactory = function () use ($collection) { 1161 | foreach ($collection as $value) { 1162 | yield $value[0] => $value[1]; 1163 | } 1164 | }; 1165 | 1166 | return new Collection($generatorFactory); 1167 | } 1168 | 1169 | /** 1170 | * Realizes collection - turns lazy collection into non-lazy one by iterating over it and storing the key/values. 1171 | * 1172 | * @param array|Traversable $collection 1173 | * @return Collection 1174 | */ 1175 | function realize($collection) 1176 | { 1177 | return dereferenceKeyValue( 1178 | toArray( 1179 | map( 1180 | $collection, 1181 | function ($value, $key) { 1182 | return [$key, $value]; 1183 | } 1184 | ) 1185 | ) 1186 | ); 1187 | } 1188 | 1189 | /** 1190 | * Returns the second item of $collection or throws ItemNotFound if $collection is empty or has 1 item. 1191 | * 1192 | * @param array|Traversable $collection 1193 | * @return mixed 1194 | */ 1195 | function second($collection) 1196 | { 1197 | return get(values($collection), 1); 1198 | } 1199 | 1200 | /** 1201 | * Combines $keys and $values into a lazy collection. The resulting collection has length equal to the size of smaller 1202 | * argument. 1203 | * 1204 | * @param array|Traversable $keys 1205 | * @param array|Traversable $values 1206 | * @return Collection 1207 | */ 1208 | function combine($keys, $values) 1209 | { 1210 | $generatorFactory = function () use ($keys, $values) { 1211 | $keyCollection = new Collection($keys); 1212 | $valueIt = new IteratorIterator(new Collection($values)); 1213 | $valueIt->rewind(); 1214 | 1215 | foreach ($keyCollection as $key) { 1216 | if (!$valueIt->valid()) { 1217 | break; 1218 | } 1219 | 1220 | yield $key => $valueIt->current(); 1221 | $valueIt->next(); 1222 | } 1223 | }; 1224 | 1225 | return new Collection($generatorFactory); 1226 | } 1227 | 1228 | /** 1229 | * Returns a lazy collection without the items associated to any of the keys from $keys. 1230 | * 1231 | * @param array|Traversable $collection 1232 | * @param array|Traversable $keys 1233 | * @return Collection 1234 | */ 1235 | function except($collection, $keys) 1236 | { 1237 | $keys = toArray(values($keys)); 1238 | 1239 | return reject( 1240 | $collection, 1241 | function ($value, $key) use ($keys) { 1242 | return in_array($key, $keys); 1243 | } 1244 | ); 1245 | } 1246 | 1247 | /** 1248 | * Returns a lazy collection of items associated to any of the keys from $keys. 1249 | * 1250 | * @param array|Traversable $collection 1251 | * @param array|Traversable $keys 1252 | * @return Collection 1253 | */ 1254 | function only($collection, $keys) 1255 | { 1256 | $keys = toArray(values($keys)); 1257 | 1258 | return filter( 1259 | $collection, 1260 | function ($value, $key) use ($keys) { 1261 | return in_array($key, $keys, true); 1262 | } 1263 | ); 1264 | } 1265 | 1266 | /** 1267 | * Returns a lazy collection of items that are in $collection but are not in any of the other arguments, indexed by the 1268 | * keys from the first collection. Note that the ...$collections are iterated non-lazily. 1269 | * 1270 | * @param array|Traversable $collection 1271 | * @param array|Traversable ...$collections 1272 | * @return Collection 1273 | */ 1274 | function diff($collection, ...$collections) 1275 | { 1276 | $valuesToCompare = toArray(values(concat(...$collections))); 1277 | $generatorFactory = function () use ($collection, $valuesToCompare) { 1278 | foreach ($collection as $key => $value) { 1279 | if (!in_array($value, $valuesToCompare)) { 1280 | yield $key => $value; 1281 | } 1282 | } 1283 | }; 1284 | 1285 | return new Collection($generatorFactory); 1286 | } 1287 | 1288 | /** 1289 | * Returns a lazy collection of items that are in $collection and all the other arguments, indexed by the keys from the 1290 | * first collection. Note that the ...$collections are iterated non-lazily. 1291 | * 1292 | * @param array|Traversable $collection 1293 | * @param array|Traversable ...$collections 1294 | * @return Collection 1295 | */ 1296 | function intersect($collection, ...$collections) 1297 | { 1298 | $valuesToCompare = toArray(values(concat(...$collections))); 1299 | $generatorFactory = function () use ($collection, $valuesToCompare) { 1300 | foreach ($collection as $key => $value) { 1301 | if (in_array($value, $valuesToCompare)) { 1302 | yield $key => $value; 1303 | } 1304 | } 1305 | }; 1306 | 1307 | return new Collection($generatorFactory); 1308 | } 1309 | 1310 | /** 1311 | * Returns a lazy collection where keys and values are flipped. 1312 | * 1313 | * @param array|Traversable $collection 1314 | * @return Collection 1315 | */ 1316 | function flip($collection) 1317 | { 1318 | $generatorFactory = function () use ($collection) { 1319 | foreach ($collection as $key => $value) { 1320 | yield $value => $key; 1321 | } 1322 | }; 1323 | 1324 | return new Collection($generatorFactory); 1325 | } 1326 | 1327 | /** 1328 | * Checks for the existence of an item with key $key in $collection. 1329 | * 1330 | * @param array|Traversable $collection 1331 | * @param mixed $key 1332 | * @return bool 1333 | */ 1334 | function has($collection, $key) 1335 | { 1336 | try { 1337 | get($collection, $key); 1338 | return true; 1339 | } catch (ItemNotFound $e) { 1340 | return false; 1341 | } 1342 | } 1343 | 1344 | /** 1345 | * Returns a lazy collection of non-lazy collections of items from nth position from each passed collection. Stops when 1346 | * any of the collections don't have an item at the nth position. 1347 | * 1348 | * @param array|Traversable ...$collections 1349 | * @return Collection 1350 | */ 1351 | function zip(...$collections) 1352 | { 1353 | /* @var Iterator[] $iterators */ 1354 | $iterators = array_map( 1355 | function ($collection) { 1356 | $it = new IteratorIterator(new Collection($collection)); 1357 | $it->rewind(); 1358 | return $it; 1359 | }, 1360 | $collections 1361 | ); 1362 | 1363 | $generatorFactory = function () use ($iterators) { 1364 | while (true) { 1365 | $isMissingItems = false; 1366 | $zippedItem = new Collection([]); 1367 | 1368 | foreach ($iterators as $it) { 1369 | if (!$it->valid()) { 1370 | $isMissingItems = true; 1371 | break; 1372 | } 1373 | 1374 | $zippedItem = append($zippedItem, $it->current(), $it->key()); 1375 | $it->next(); 1376 | } 1377 | 1378 | if (!$isMissingItems) { 1379 | yield $zippedItem; 1380 | } else { 1381 | break; 1382 | } 1383 | } 1384 | }; 1385 | 1386 | return new Collection($generatorFactory); 1387 | } 1388 | 1389 | /** 1390 | * Transpose each item in a collection, interchanging the row and column indexes. 1391 | * Can only transpose collections of collections. Otherwise an InvalidArgument is raised. 1392 | * 1393 | * @param Collection[] $collection 1394 | * @return Collection 1395 | */ 1396 | function transpose($collection) 1397 | { 1398 | if (some($collection, function ($value) { 1399 | return !($value instanceof Collection); 1400 | })) { 1401 | throw new InvalidArgument('Can only transpose collections of collections.'); 1402 | } 1403 | 1404 | return Collection::from( 1405 | array_map( 1406 | function (...$items) { 1407 | return new Collection($items); 1408 | }, 1409 | ...toArray( 1410 | map( 1411 | $collection, 1412 | 'DusanKasan\Knapsack\toArray' 1413 | ) 1414 | ) 1415 | ) 1416 | ); 1417 | } 1418 | 1419 | /** 1420 | * Returns a lazy collection of data extracted from $collection items by dot separated key path. Supports the * 1421 | * wildcard. If a key contains \ or * it must be escaped using \ character. 1422 | * 1423 | * @param array|Traversable $collection 1424 | * @param mixed $keyPath 1425 | * @return Collection 1426 | */ 1427 | function extract($collection, $keyPath) 1428 | { 1429 | preg_match_all('/(.*[^\\\])(?:\.|$)/U', $keyPath, $matches); 1430 | $pathParts = $matches[1]; 1431 | 1432 | $extractor = function ($coll) use ($pathParts) { 1433 | foreach ($pathParts as $pathPart) { 1434 | $coll = flatten(filter($coll, '\DusanKasan\Knapsack\isCollection'), 1); 1435 | 1436 | if ($pathPart != '*') { 1437 | $pathPart = str_replace(['\.', '\*'], ['.', '*'], $pathPart); 1438 | $coll = values(only($coll, [$pathPart])); 1439 | } 1440 | } 1441 | 1442 | return $coll; 1443 | }; 1444 | 1445 | $generatorFactory = function () use ($collection, $extractor) { 1446 | foreach ($collection as $item) { 1447 | foreach ($extractor([$item]) as $extracted) { 1448 | yield $extracted; 1449 | } 1450 | } 1451 | }; 1452 | 1453 | return new Collection($generatorFactory); 1454 | } 1455 | 1456 | /** 1457 | * Checks whether $collection has exactly $size items. 1458 | * 1459 | * @param array|Traversable $collection 1460 | * @param int $size 1461 | * @return bool 1462 | */ 1463 | function sizeIs($collection, $size) 1464 | { 1465 | $itemsTempCount = 0; 1466 | 1467 | foreach ($collection as $key => $value) { 1468 | $itemsTempCount++; 1469 | 1470 | if ($itemsTempCount > $size) { 1471 | return false; 1472 | } 1473 | } 1474 | 1475 | return $itemsTempCount == $size; 1476 | } 1477 | 1478 | /** 1479 | * Checks whether $collection has less than $size items. 1480 | * 1481 | * @param array|Traversable $collection 1482 | * @param int $size 1483 | * @return bool 1484 | */ 1485 | function sizeIsLessThan($collection, $size) 1486 | { 1487 | $itemsTempCount = 0; 1488 | 1489 | foreach ($collection as $key => $value) { 1490 | $itemsTempCount++; 1491 | 1492 | if ($itemsTempCount > $size) { 1493 | return false; 1494 | } 1495 | } 1496 | 1497 | return $itemsTempCount < $size; 1498 | } 1499 | 1500 | /** 1501 | * Checks whether $collection has more than $size items. 1502 | * 1503 | * @param array|Traversable $collection 1504 | * @param int $size 1505 | * @return bool 1506 | */ 1507 | function sizeIsGreaterThan($collection, $size) 1508 | { 1509 | $itemsTempCount = 0; 1510 | 1511 | foreach ($collection as $key => $value) { 1512 | $itemsTempCount++; 1513 | 1514 | if ($itemsTempCount > $size) { 1515 | return true; 1516 | } 1517 | } 1518 | 1519 | return $itemsTempCount > $size; 1520 | } 1521 | 1522 | /** 1523 | * Checks whether $collection has between $fromSize to $toSize items. $toSize can be 1524 | * smaller than $fromSize. 1525 | * 1526 | * @param array|Traversable $collection 1527 | * @param int $fromSize 1528 | * @param int $toSize 1529 | * @return bool 1530 | */ 1531 | function sizeIsBetween($collection, $fromSize, $toSize) 1532 | { 1533 | if ($fromSize > $toSize) { 1534 | $tmp = $toSize; 1535 | $toSize = $fromSize; 1536 | $fromSize = $tmp; 1537 | } 1538 | 1539 | $itemsTempCount = 0; 1540 | foreach ($collection as $key => $value) { 1541 | $itemsTempCount++; 1542 | 1543 | if ($itemsTempCount > $toSize) { 1544 | return false; 1545 | } 1546 | } 1547 | 1548 | return $fromSize < $itemsTempCount && $itemsTempCount < $toSize; 1549 | } 1550 | 1551 | /** 1552 | * Returns a sum of all values in the $collection. 1553 | * 1554 | * @param array|Traversable $collection 1555 | * @return int|float 1556 | */ 1557 | function sum($collection) 1558 | { 1559 | $result = 0; 1560 | 1561 | foreach ($collection as $value) { 1562 | $result += $value; 1563 | } 1564 | 1565 | return $result; 1566 | } 1567 | 1568 | /** 1569 | * Returns average of values from $collection. 1570 | * 1571 | * @param array|Traversable $collection 1572 | * @return int|float 1573 | */ 1574 | function average($collection) 1575 | { 1576 | $sum = 0; 1577 | $count = 0; 1578 | 1579 | foreach ($collection as $value) { 1580 | $sum += $value; 1581 | $count++; 1582 | } 1583 | 1584 | return $count ? $sum/$count : 0; 1585 | } 1586 | 1587 | /** 1588 | * Returns maximal value from $collection. 1589 | * 1590 | * @param array|Traversable $collection 1591 | * @return mixed 1592 | */ 1593 | function max($collection) 1594 | { 1595 | $result = null; 1596 | 1597 | foreach ($collection as $value) { 1598 | $result = $value > $result ? $value : $result; 1599 | } 1600 | 1601 | return $result; 1602 | } 1603 | 1604 | /** 1605 | * Returns minimal value from $collection. 1606 | * 1607 | * @param array|Traversable $collection 1608 | * @return mixed 1609 | */ 1610 | function min($collection) 1611 | { 1612 | $result = null; 1613 | $hasItem = false; 1614 | 1615 | foreach ($collection as $value) { 1616 | if (!$hasItem) { 1617 | $hasItem = true; 1618 | $result = $value; 1619 | } 1620 | 1621 | $result = $value < $result ? $value : $result; 1622 | } 1623 | 1624 | return $result; 1625 | } 1626 | 1627 | /** 1628 | * Returns a string by concatenating the $collection values into a string. 1629 | * 1630 | * @param array|Traversable $collection 1631 | * @return string 1632 | */ 1633 | function toString($collection) 1634 | { 1635 | $result = ''; 1636 | 1637 | foreach ($collection as $value) { 1638 | $result .= (string) $value; 1639 | } 1640 | 1641 | return $result; 1642 | } 1643 | 1644 | 1645 | /** 1646 | * Returns a lazy collection with items from $collection, but items with keys that are found in keys of $replacementMap 1647 | * are replaced by their values. 1648 | * 1649 | * @param array|Traversable $collection 1650 | * @param array|Traversable $replacementMap 1651 | * @return Collection 1652 | */ 1653 | function replaceByKeys($collection, $replacementMap) 1654 | { 1655 | $generatorFactory = function () use ($collection, $replacementMap) { 1656 | foreach ($collection as $key => $value) { 1657 | $newValue = getOrDefault($replacementMap, $key, $value); 1658 | yield $key => $newValue; 1659 | } 1660 | }; 1661 | 1662 | return new Collection($generatorFactory); 1663 | } 1664 | 1665 | /** 1666 | * Dumps a variable into scalar or array (recursively). 1667 | * 1668 | * - scalars are returned as they are, 1669 | * - array of class name => properties (name => value and only properties accessible for this class) 1670 | * is returned for objects, 1671 | * - arrays or Traversables are returned as arrays, 1672 | * - for anything else result of calling gettype($input) is returned 1673 | * 1674 | * If specified, $maxItemsPerCollection will only leave specified number of items in collection, 1675 | * appending a new element at end '>>>' if original collection was longer. 1676 | * 1677 | * If specified, $maxDepth will only leave specified n levels of nesting, replacing elements 1678 | * with '^^^' once the maximum nesting level was reached. 1679 | * 1680 | * If a collection with duplicate keys is encountered, the duplicate keys (except the first one) 1681 | * will be change into a format originalKey//duplicateCounter where duplicateCounter starts from 1682 | * 1 at the first duplicate. So [0 => 1, 0 => 2] will become [0 => 1, '0//1' => 2] 1683 | * 1684 | * @param mixed $input 1685 | * @param int|null $maxItemsPerCollection 1686 | * @param int|null $maxDepth 1687 | * @return array|mixed 1688 | */ 1689 | function dump($input, $maxItemsPerCollection = null, $maxDepth = null) 1690 | { 1691 | if (is_scalar($input)) { 1692 | return $input; 1693 | } 1694 | 1695 | if (is_array($input) || $input instanceof Traversable) { 1696 | if ($maxDepth === 0) { 1697 | return '^^^'; 1698 | } 1699 | 1700 | $normalizedProperties = []; 1701 | foreach ($input as $key => $value) { 1702 | if ($maxItemsPerCollection !== null && count($normalizedProperties) >= $maxItemsPerCollection) { 1703 | $normalizedProperties[] = '>>>'; 1704 | break; 1705 | } 1706 | 1707 | for ($affix = 0; true; $affix++) { 1708 | $betterKey = $affix ? "$key//$affix" : $key; 1709 | if (!array_key_exists($betterKey, $normalizedProperties)) { 1710 | $normalizedProperties[$betterKey] = dump( 1711 | $value, 1712 | $maxItemsPerCollection, 1713 | $maxDepth>0 ? $maxDepth-1 : null 1714 | ); 1715 | 1716 | break; 1717 | } 1718 | } 1719 | } 1720 | 1721 | return $normalizedProperties; 1722 | } 1723 | 1724 | if (is_object($input)) { 1725 | if ($maxDepth === 0) { 1726 | return '^^^'; 1727 | } 1728 | 1729 | $reflection = new \ReflectionObject($input); 1730 | $normalizedProperties = []; 1731 | foreach ($reflection->getProperties() as $property) { 1732 | $property->setAccessible(true); 1733 | $normalizedProperties[$property->getName()] = $property->getValue($input); 1734 | } 1735 | return [get_class($input) => dump($normalizedProperties, null, $maxDepth>0 ? $maxDepth-1 : null)]; 1736 | } 1737 | 1738 | return gettype($input); 1739 | } 1740 | 1741 | /** 1742 | * Calls dump on $input and then prints it using the var_export. Returns $input. 1743 | * 1744 | * @param mixed $input 1745 | * @param int|null $maxItemsPerCollection 1746 | * @param int|null $maxDepth 1747 | * @return mixed 1748 | */ 1749 | function printDump($input, $maxItemsPerCollection = null, $maxDepth = null) 1750 | { 1751 | var_export(dump($input, $maxItemsPerCollection, $maxDepth)); 1752 | return $input; 1753 | } 1754 | -------------------------------------------------------------------------------- /src/utility_functions.php: -------------------------------------------------------------------------------- 1 |