├── LICENSE ├── composer.json ├── phpcs.xml └── src ├── Iterator └── CombineIterator.php ├── IteratorPipeline ├── Pipeline.php ├── PipelineBuilder.php ├── PipelineBuilder │ └── Stub.php └── Traits │ ├── AggregationTrait.php │ ├── FilteringTrait.php │ ├── FindingTrait.php │ ├── MappingTrait.php │ ├── SortingTrait.php │ └── TypeHandlingTrait.php └── functions ├── iterable_after.php ├── iterable_apply.php ├── iterable_average.php ├── iterable_before.php ├── iterable_chunk.php ├── iterable_cleanup.php ├── iterable_column.php ├── iterable_concat.php ├── iterable_count.php ├── iterable_expect_type.php ├── iterable_fill.php ├── iterable_filter.php ├── iterable_find.php ├── iterable_find_key.php ├── iterable_first.php ├── iterable_flatten.php ├── iterable_flip.php ├── iterable_group.php ├── iterable_has_all.php ├── iterable_has_any.php ├── iterable_has_none.php ├── iterable_keys.php ├── iterable_last.php ├── iterable_map.php ├── iterable_map_keys.php ├── iterable_max.php ├── iterable_min.php ├── iterable_project.php ├── iterable_reduce.php ├── iterable_reshape.php ├── iterable_reverse.php ├── iterable_separate.php ├── iterable_slice.php ├── iterable_sort.php ├── iterable_sort_keys.php ├── iterable_sum.php ├── iterable_to_array.php ├── iterable_to_iterator.php ├── iterable_to_traversable.php ├── iterable_type_cast.php ├── iterable_type_check.php ├── iterable_unique.php ├── iterable_unwind.php ├── iterable_values.php └── iterable_walk.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jasny 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 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "improved/iterable", 3 | "description": "Functions to interact with arrays, iterators and other traversable objects", 4 | "license": "MIT", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Arnold Daniels", 9 | "email": "arnold@jasny.net", 10 | "homepage": "http://www.jasny.net" 11 | } 12 | ], 13 | "support": { 14 | "issues": "https://github.com/improved-php-library/iterable/issues", 15 | "source": "https://github.com/improved-php-library/iterable" 16 | }, 17 | "require": { 18 | "php": ">=7.2.0", 19 | "improved/type": "~0.1.0" 20 | }, 21 | "require-dev": { 22 | "jasny/php-code-quality": "~2.6.0 || ~2.7.0", 23 | "phpstan/phpstan": "~0.12.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Improved\\Iterator\\": "src/Iterator/", 28 | "Improved\\IteratorPipeline\\": "src/IteratorPipeline/" 29 | }, 30 | "files": [ 31 | "src/functions/iterable_after.php", 32 | "src/functions/iterable_apply.php", 33 | "src/functions/iterable_average.php", 34 | "src/functions/iterable_after.php", 35 | "src/functions/iterable_before.php", 36 | "src/functions/iterable_chunk.php", 37 | "src/functions/iterable_cleanup.php", 38 | "src/functions/iterable_column.php", 39 | "src/functions/iterable_concat.php", 40 | "src/functions/iterable_count.php", 41 | "src/functions/iterable_expect_type.php", 42 | "src/functions/iterable_fill.php", 43 | "src/functions/iterable_filter.php", 44 | "src/functions/iterable_find.php", 45 | "src/functions/iterable_find_key.php", 46 | "src/functions/iterable_first.php", 47 | "src/functions/iterable_flatten.php", 48 | "src/functions/iterable_flip.php", 49 | "src/functions/iterable_group.php", 50 | "src/functions/iterable_has_all.php", 51 | "src/functions/iterable_has_any.php", 52 | "src/functions/iterable_has_none.php", 53 | "src/functions/iterable_keys.php", 54 | "src/functions/iterable_last.php", 55 | "src/functions/iterable_map_keys.php", 56 | "src/functions/iterable_map.php", 57 | "src/functions/iterable_max.php", 58 | "src/functions/iterable_min.php", 59 | "src/functions/iterable_project.php", 60 | "src/functions/iterable_reduce.php", 61 | "src/functions/iterable_reshape.php", 62 | "src/functions/iterable_reverse.php", 63 | "src/functions/iterable_separate.php", 64 | "src/functions/iterable_slice.php", 65 | "src/functions/iterable_sort_keys.php", 66 | "src/functions/iterable_sort.php", 67 | "src/functions/iterable_sum.php", 68 | "src/functions/iterable_to_array.php", 69 | "src/functions/iterable_to_iterator.php", 70 | "src/functions/iterable_to_traversable.php", 71 | "src/functions/iterable_type_check.php", 72 | "src/functions/iterable_type_cast.php", 73 | "src/functions/iterable_unique.php", 74 | "src/functions/iterable_unwind.php", 75 | "src/functions/iterable_values.php", 76 | "src/functions/iterable_walk.php" 77 | ] 78 | }, 79 | "provide": { 80 | "ext-improved_iterable": "*" 81 | }, 82 | "autoload-dev": { 83 | "psr-4": { 84 | "Improved\\Tests\\": "tests/" 85 | } 86 | }, 87 | "scripts": { 88 | "test": [ 89 | "phpstan analyse", 90 | "phpunit", 91 | "phpcs -p src" 92 | ], 93 | "build": [ 94 | "composer dump-autoload -o", 95 | "php bin/build-functions-const.php" 96 | ] 97 | }, 98 | "config": { 99 | "preferred-install": "dist", 100 | "sort-packages": true, 101 | "optimize-autoloader": true 102 | }, 103 | "minimum-stability": "dev", 104 | "prefer-stable": true 105 | } 106 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Jasny coding standard. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Iterator/CombineIterator.php: -------------------------------------------------------------------------------- 1 | keys = iterable_to_iterator($keys); 33 | $this->values = iterable_to_iterator($values); 34 | } 35 | 36 | /** 37 | * Get the current value. 38 | * 39 | * @return mixed 40 | */ 41 | public function current() 42 | { 43 | return $this->values->current(); 44 | } 45 | 46 | /** 47 | * Get the current key. 48 | * 49 | * @return mixed 50 | */ 51 | public function key() 52 | { 53 | return $this->keys->current(); 54 | } 55 | 56 | /** 57 | * Forward to the next element. 58 | * 59 | * @return void 60 | */ 61 | public function next(): void 62 | { 63 | $this->keys->next(); 64 | $this->values->next(); 65 | } 66 | 67 | /** 68 | * Checks if the iterator is valid. 69 | * 70 | * @return bool 71 | */ 72 | public function valid(): bool 73 | { 74 | return $this->keys->valid(); 75 | } 76 | 77 | /** 78 | * Checks if the iterator is valid. 79 | * 80 | * @return void 81 | */ 82 | public function rewind(): void 83 | { 84 | $this->keys->rewind(); 85 | $this->values->rewind(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Pipeline.php: -------------------------------------------------------------------------------- 1 | iterable = $iterable; 35 | } 36 | 37 | /** 38 | * Define the next step via a callback that returns an array or Traversable object. 39 | * 40 | * @param callable $callback 41 | * @param mixed ...$args 42 | * @return self 43 | */ 44 | public function then(callable $callback, ...$args): self 45 | { 46 | $this->iterable = i\type_check( 47 | $callback($this->iterable, ...$args), 48 | 'iterable', 49 | new \UnexpectedValueException("Expected step to return an array or Traversable, %s returned") 50 | ); 51 | 52 | return $this->iterable instanceof Pipeline ? $this->iterable : $this; 53 | } 54 | 55 | 56 | /** 57 | * Get iterator. 58 | * 59 | * @return \Iterator 60 | */ 61 | public function getIterator(): \Iterator 62 | { 63 | return i\iterable_to_iterator($this->iterable); 64 | } 65 | 66 | /** 67 | * Get iterable as array. 68 | * 69 | * @return array 70 | */ 71 | public function toArray(): array 72 | { 73 | return i\iterable_to_array($this->iterable, true); 74 | } 75 | 76 | /** 77 | * Traverse over the iterator, not capturing the values. 78 | * This is particularly useful after `apply()`. 79 | * 80 | * @return void 81 | */ 82 | public function walk(): void 83 | { 84 | i\iterable_walk($this->iterable); 85 | } 86 | 87 | 88 | /** 89 | * Factory method 90 | * 91 | * @param iterable $iterable 92 | * @return static 93 | */ 94 | final public static function with(iterable $iterable): self 95 | { 96 | return $iterable instanceof static ? $iterable : new static($iterable); 97 | } 98 | 99 | /** 100 | * Factory method for PipelineBuilder 101 | * 102 | * @return PipelineBuilder 103 | */ 104 | public static function build(): PipelineBuilder 105 | { 106 | return new PipelineBuilder(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/IteratorPipeline/PipelineBuilder.php: -------------------------------------------------------------------------------- 1 | steps = array_merge($this->steps, $callback->steps); 42 | } else { 43 | $copy->steps[] = [$callback, $args]; 44 | } 45 | 46 | return $copy; 47 | } 48 | 49 | 50 | /** 51 | * Add a stub, which does nothing but can be replaced later. 52 | * 53 | * @param string $name 54 | * @return static 55 | * @throw \BadMethodCallException if stub already exists 56 | */ 57 | public function stub(string $name): self 58 | { 59 | $hasStub = i\iterable_has_any($this->steps, function ($step) use ($name) { 60 | return $step[0] instanceof Stub && $step[0]->getName() === $name; 61 | }); 62 | 63 | if ($hasStub) { 64 | throw new \BadMethodCallException("Pipeline builder already has '$name' stub"); 65 | } 66 | 67 | return $this->then(new Stub($name)); 68 | } 69 | 70 | /** 71 | * Get a pipeline builder where a stub is replaced. 72 | * 73 | * @param string $name 74 | * @param callable $callable 75 | * @param mixed ...$args 76 | * @return static 77 | */ 78 | public function unstub(string $name, callable $callable, ...$args): self 79 | { 80 | $index = i\iterable_find_key($this->steps, function ($step) use ($name) { 81 | return $step[0] instanceof Stub && $step[0]->getName() === $name; 82 | }); 83 | 84 | if ($index === null) { 85 | throw new \BadMethodCallException("Pipeline builder doesn't have '$name' stub"); 86 | } 87 | 88 | $clone = clone $this; 89 | $clone->steps[$index] = [$callable, $args]; 90 | 91 | return $clone; 92 | } 93 | 94 | 95 | /** 96 | * Create a new pipeline 97 | * 98 | * @param iterable $iterable 99 | * @return Pipeline 100 | */ 101 | public function with(iterable $iterable): Pipeline 102 | { 103 | $pipeline = new Pipeline($iterable); 104 | 105 | foreach ($this->steps as [$callback, $args]) { 106 | $pipeline = $pipeline->then($callback, ...$args); 107 | } 108 | 109 | return $pipeline; 110 | } 111 | 112 | /** 113 | * Invoke the builder. 114 | * 115 | * @param iterable $iterable 116 | * @return array 117 | */ 118 | public function __invoke(iterable $iterable): array 119 | { 120 | return $this->with($iterable)->toArray(); 121 | } 122 | 123 | 124 | /** 125 | * Use another iterator as keys and the current iterator as values. 126 | * 127 | * @param iterable $keys Keys will be turned into an array. 128 | * @return static 129 | */ 130 | public function setKeys(iterable $keys) 131 | { 132 | $combine = function ($values, $keys) { 133 | return new CombineIterator($keys, $values); 134 | }; 135 | 136 | return $this->then($combine, i\iterable_to_array($keys)); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/IteratorPipeline/PipelineBuilder/Stub.php: -------------------------------------------------------------------------------- 1 | name = $name; 26 | } 27 | 28 | /** 29 | * Get the name of the stub. 30 | * 31 | * @return string 32 | */ 33 | public function getName() 34 | { 35 | return $this->name; 36 | } 37 | 38 | 39 | /** 40 | * Invoke stub 41 | * 42 | * @param iterable $iterable 43 | * @return iterable 44 | */ 45 | public function __invoke(iterable $iterable) 46 | { 47 | return $iterable; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Traits/AggregationTrait.php: -------------------------------------------------------------------------------- 1 | iterable); 27 | } 28 | 29 | /** 30 | * Reduce all elements to a single value using a callback. 31 | * 32 | * @param callable $callback 33 | * @param mixed $initial 34 | * @return mixed 35 | */ 36 | public function reduce(callable $callback, $initial = null) 37 | { 38 | return i\iterable_reduce($this->iterable, $callback, $initial); 39 | } 40 | 41 | /** 42 | * Calculate the sum of all numbers. 43 | * If no elements are present, the result is 0. 44 | * 45 | * @return int|float 46 | */ 47 | public function sum() 48 | { 49 | return i\iterable_sum($this->iterable); 50 | } 51 | 52 | /** 53 | * Return the arithmetic mean. 54 | * If no elements are present, the result is NAN. 55 | * 56 | * @return float 57 | */ 58 | public function average(): float 59 | { 60 | return i\iterable_average($this->iterable); 61 | } 62 | 63 | /** 64 | * Concatenate all elements into a single string. 65 | * 66 | * @param string $glue 67 | * @return string 68 | */ 69 | public function concat(string $glue = ''): string 70 | { 71 | return i\iterable_concat($this->iterable, $glue); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Traits/FilteringTrait.php: -------------------------------------------------------------------------------- 1 | then("Improved\iterable_filter", $matcher); 31 | } 32 | 33 | /** 34 | * Filter out `null` values from iteratable. 35 | * 36 | * @return static 37 | */ 38 | public function cleanup() 39 | { 40 | return $this->then("Improved\iterable_cleanup"); 41 | } 42 | 43 | /** 44 | * Filter on unique elements. 45 | * 46 | * @param callable|null $grouper If provided, filtering will be based on return value. 47 | * @return static 48 | */ 49 | public function unique(?callable $grouper = null) 50 | { 51 | return $this->then("Improved\iterable_unique", $grouper); 52 | } 53 | 54 | /** 55 | * Filter our duplicate keys. 56 | * Unlike associative arrays, the keys of iterators don't have to be unique. 57 | * 58 | * @return static 59 | */ 60 | public function uniqueKeys() 61 | { 62 | return $this->then("Improved\iterable_unique", function ($value, $key) { 63 | return $key; 64 | }); 65 | } 66 | 67 | /** 68 | * Get only the first elements of an iterator. 69 | * 70 | * @param int $size 71 | * @return static 72 | */ 73 | public function limit(int $size) 74 | { 75 | return $this->then("Improved\iterable_slice", 0, $size); 76 | } 77 | 78 | /** 79 | * Get a limited subset of the elements using an offset. 80 | * 81 | * @param int $offset 82 | * @param int|null $size size limit 83 | * @return static 84 | */ 85 | public function slice(int $offset, ?int $size = null) 86 | { 87 | return $this->then("Improved\iterable_slice", $offset, $size); 88 | } 89 | 90 | /** 91 | * Get elements until a match is found. 92 | * 93 | * @param callable $matcher 94 | * @param bool $include 95 | * @return static 96 | */ 97 | public function before(callable $matcher, bool $include = false) 98 | { 99 | return $this->then("Improved\iterable_before", $matcher, $include); 100 | } 101 | 102 | /** 103 | * Get elements after a match is found. 104 | * 105 | * @param callable $matcher 106 | * @param bool $include 107 | * @return static 108 | */ 109 | public function after(callable $matcher, bool $include = false) 110 | { 111 | return $this->then("Improved\iterable_after", $matcher, $include); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Traits/FindingTrait.php: -------------------------------------------------------------------------------- 1 | iterable, $required); 29 | } 30 | 31 | /** 32 | * Get the last value of an iterable. 33 | * 34 | * @param bool $required Throw RangeException instead of returning null for empty iterable 35 | * @return mixed 36 | */ 37 | public function last(bool $required = false) 38 | { 39 | return i\iterable_last($this->iterable, $required); 40 | } 41 | 42 | 43 | /** 44 | * Get the first value that matches a condition. 45 | * Returns null if no element is found. 46 | * 47 | * @param callable $matcher 48 | * @return mixed 49 | */ 50 | public function find(callable $matcher) 51 | { 52 | return i\iterable_find($this->iterable, $matcher); 53 | } 54 | 55 | /** 56 | * Get the first value that matches a condition and return the key. 57 | * Returns null if no element is found. 58 | * 59 | * @param callable $matcher 60 | * @return mixed 61 | */ 62 | public function findKey(callable $matcher) 63 | { 64 | return i\iterable_find_key($this->iterable, $matcher); 65 | } 66 | 67 | 68 | /** 69 | * Check if any element matches the condition. 70 | * 71 | * @param callable $matcher 72 | * @return bool 73 | */ 74 | public function hasAny(callable $matcher): bool 75 | { 76 | return i\iterable_has_any($this->iterable, $matcher); 77 | } 78 | 79 | /** 80 | * Check if all elements match the condition. 81 | * 82 | * @param callable $matcher 83 | * @return bool 84 | */ 85 | public function hasAll(callable $matcher): bool 86 | { 87 | return i\iterable_has_all($this->iterable, $matcher); 88 | } 89 | 90 | /** 91 | * Check that no elements match the condition. 92 | * 93 | * @param callable $matcher 94 | * @return bool 95 | */ 96 | public function hasNone(callable $matcher): bool 97 | { 98 | return i\iterable_has_none($this->iterable, $matcher); 99 | } 100 | 101 | 102 | /** 103 | * Get the minimal value according to a given comparator. 104 | * Returns null for an empty iterable. 105 | * 106 | * @param callable|null $compare 107 | * @return mixed 108 | */ 109 | public function min(?callable $compare = null) 110 | { 111 | return i\iterable_min($this->iterable, $compare); 112 | } 113 | 114 | /** 115 | * Get the maximal value according to a given comparator. 116 | * Returns null for an empty iterable. 117 | * 118 | * @param callable|null $compare 119 | * @return mixed 120 | */ 121 | public function max(?callable $compare = null) 122 | { 123 | return i\iterable_max($this->iterable, $compare); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Traits/MappingTrait.php: -------------------------------------------------------------------------------- 1 | then("Improved\iterable_map", $callback); 34 | } 35 | 36 | /** 37 | * Map the key of each element to a new key using a callback function. 38 | * 39 | * @param callable $callback 40 | * @return static 41 | */ 42 | public function mapKeys(callable $callback) 43 | { 44 | return $this->then("Improved\iterable_map_keys", $callback); 45 | } 46 | 47 | /** 48 | * Apply a callback to each element of an iterator. 49 | * Any value returned by the callback is ignored. 50 | * 51 | * @param callable $callback 52 | * @return static 53 | */ 54 | public function apply(callable $callback) 55 | { 56 | return $this->then("Improved\iterable_apply", $callback); 57 | } 58 | 59 | /** 60 | * Divide iterable into chunks of specified size. 61 | * 62 | * @param int $size 63 | * @return static 64 | */ 65 | public function chunk(int $size) 66 | { 67 | return $this->then("Improved\iterable_chunk", $size); 68 | } 69 | 70 | /** 71 | * Group elements of an iterator, with the group name as key and an array of elements as value. 72 | * 73 | * @param callable $grouping 74 | * @return static 75 | */ 76 | public function group(callable $grouping) 77 | { 78 | return $this->then("Improved\iterable_group", $grouping); 79 | } 80 | 81 | /** 82 | * Walk through all sub-iterables and concatenate them. 83 | * 84 | * @param bool $preserveKeys 85 | * @return static 86 | */ 87 | public function flatten(bool $preserveKeys = false) 88 | { 89 | return $this->then("Improved\iterable_flatten", $preserveKeys); 90 | } 91 | 92 | /** 93 | * Deconstruct an iterable property/item for each element. The result is one element for each item in the iterable 94 | * property. 95 | * 96 | * @param string $column 97 | * @param string|null $mapKey The name of a new property to hold the array index of the element 98 | * @param bool $preserveKeys Preserve the keys of the iterable (will result in duplicate keys) 99 | * @return static 100 | */ 101 | public function unwind(string $column, ?string $mapKey = null, bool $preserveKeys = false) 102 | { 103 | return $this->then("Improved\iterable_unwind", $column, $mapKey, $preserveKeys); 104 | } 105 | 106 | /** 107 | * Set all values of the iterable. 108 | * 109 | * @param mixed $value 110 | * @return static 111 | */ 112 | public function fill($value) 113 | { 114 | return $this->then("Improved\iterable_fill", $value); 115 | } 116 | 117 | /** 118 | * Return the values from a single column / property. 119 | * Create key/value pairs by specifying the key. 120 | * 121 | * @param string|int|null $valueColumn null to keep values 122 | * @param string|int|null $keyColumn null to keep keys 123 | * @return static 124 | */ 125 | public function column($valueColumn, $keyColumn = null) 126 | { 127 | return $this->then("Improved\iterable_column", $valueColumn, $keyColumn); 128 | } 129 | 130 | /** 131 | * Project each element of an iterator to an associated (or numeric) array. 132 | * Each element should be an array or object. 133 | * 134 | * @param array $mapping [new key => old key, ...] 135 | * @return static 136 | */ 137 | public function project(array $mapping) 138 | { 139 | return $this->then("Improved\iterable_project", $mapping); 140 | } 141 | 142 | /** 143 | * Reshape each element of an iterator, adding or removing properties or keys. 144 | * 145 | * @param array $columns [key => bool|string|int, ...] 146 | * @return static 147 | */ 148 | public function reshape(array $columns) 149 | { 150 | return $this->then("Improved\iterable_reshape", $columns); 151 | } 152 | 153 | 154 | /** 155 | * Keep the values, drop the keys. The keys become an incremental number. 156 | * 157 | * @return static 158 | */ 159 | public function values() 160 | { 161 | return $this->then("Improved\iterable_values"); 162 | } 163 | 164 | /** 165 | * Use the keys as values. The keys become an incremental number. 166 | * 167 | * @return static 168 | */ 169 | public function keys() 170 | { 171 | return $this->then("Improved\iterable_keys"); 172 | } 173 | 174 | /** 175 | * Use another iterator as keys and the current iterator as values. 176 | * 177 | * @param iterable $keys 178 | * @return static 179 | */ 180 | public function setKeys(iterable $keys) 181 | { 182 | $combine = function ($values, $keys) { 183 | return new CombineIterator($keys, $values); 184 | }; 185 | 186 | return $this->then($combine, $keys); 187 | } 188 | 189 | /** 190 | * Use values as keys and visa versa. 191 | * 192 | * @return static 193 | */ 194 | public function flip() 195 | { 196 | return $this->then("Improved\iterable_flip"); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Traits/SortingTrait.php: -------------------------------------------------------------------------------- 1 | then("Improved\iterable_sort", $compare, $preserveKeys); 34 | } 35 | 36 | /** 37 | * Sort all elements of an iterator based on the key. 38 | * 39 | * @param callable|int $compare SORT_* flags as binary set or callback comparator function 40 | * @return static 41 | */ 42 | public function sortKeys($compare) 43 | { 44 | return $this->then("Improved\iterable_sort_keys", $compare); 45 | } 46 | 47 | /** 48 | * Reverse order of elements of an iterable. 49 | * 50 | * @return static 51 | */ 52 | public function reverse() 53 | { 54 | return $this->then("Improved\iterable_reverse"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/IteratorPipeline/Traits/TypeHandlingTrait.php: -------------------------------------------------------------------------------- 1 | then("Improved\iterable_expect_type", $type, $error); 35 | } 36 | 37 | /** 38 | * Validate that a value has a specific type. 39 | * 40 | * @param string|string[] $type 41 | * @param \Throwable|null $throwable 42 | * @return static 43 | */ 44 | public function typeCheck($type, ?\Throwable $throwable = null) 45 | { 46 | return $this->then("Improved\iterable_type_check", $type, $throwable); 47 | } 48 | 49 | /** 50 | * Cast a value to the specific type or throw an error. 51 | * 52 | * @param string $type 53 | * @param \Throwable|null $throwable 54 | * @return static 55 | */ 56 | public function typeCast(string $type, ?\Throwable $throwable = null) 57 | { 58 | return $this->then("Improved\iterable_type_cast", $type, $throwable); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/functions/iterable_after.php: -------------------------------------------------------------------------------- 1 | $value) { 20 | $matched = $found || (bool)call_user_func($matcher, $value, $key); 21 | 22 | if ($found || ($matched && $including)) { 23 | yield $key => $value; 24 | } 25 | 26 | $found = $matched; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/functions/iterable_apply.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | call_user_func($callback, $value, $key); 18 | 19 | yield $key => $value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/functions/iterable_average.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | $matched = (bool)call_user_func($matcher, $value, $key); 19 | 20 | if (!$matched || $including) { 21 | yield $key => $value; 22 | } 23 | 24 | if ($matched) { 25 | return; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/functions/iterable_chunk.php: -------------------------------------------------------------------------------- 1 | valid(); $i++) { 18 | yield $iterator->key() => $iterator->current(); 19 | 20 | $iterator->next(); 21 | } 22 | }; 23 | 24 | $iterator = iterable_to_iterator($iterable); 25 | $iterator->rewind(); 26 | 27 | while ($iterator->valid()) { 28 | yield $generate($iterator, $size); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/functions/iterable_cleanup.php: -------------------------------------------------------------------------------- 1 | $value) { 16 | if (isset($key) && isset($value)) { 17 | yield $key => $value; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/functions/iterable_column.php: -------------------------------------------------------------------------------- 1 | $value) { 21 | switch (true) { 22 | case is_array($value) || (is_object($value) && $value instanceof \ArrayAccess): 23 | $key = isset($keyColumn) ? ($value[$keyColumn] ?? null) : $key; 24 | $value = isset($valueColumn) ? ($value[$valueColumn] ?? null) : $value; 25 | break; 26 | case is_object($value) && !$value instanceof \DateTimeInterface: 27 | $key = isset($keyColumn) ? ($value->$keyColumn ?? null) : $key; 28 | $value = isset($valueColumn) ? ($value->$valueColumn ?? null) : $value; 29 | break; 30 | default: 31 | $key = isset($keyColumn) ? null : $key; 32 | $value = isset($valueColumn) ? null : $value; 33 | break; 34 | } 35 | 36 | yield $key => $value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/functions/iterable_concat.php: -------------------------------------------------------------------------------- 1 | $_) { 17 | yield $key => $value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/functions/iterable_filter.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | if ((bool)call_user_func($matcher, $value, $key)) { 18 | yield $key => $value; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/functions/iterable_find.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | if ((bool)call_user_func($matcher, $value, $key)) { 19 | return $value; 20 | } 21 | } 22 | 23 | return null; 24 | } 25 | -------------------------------------------------------------------------------- /src/functions/iterable_find_key.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | if ((bool)call_user_func($matcher, $value, $key)) { 19 | return $key; 20 | } 21 | } 22 | 23 | return null; 24 | } 25 | -------------------------------------------------------------------------------- /src/functions/iterable_first.php: -------------------------------------------------------------------------------- 1 | $element) { 19 | if (!is_iterable($element)) { 20 | yield ($preserveKeys ? $topKey : $counter++) => $element; 21 | continue; 22 | } 23 | 24 | foreach ($element as $key => $item) { 25 | yield ($preserveKeys ? $key : $counter++) => $item; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/functions/iterable_flip.php: -------------------------------------------------------------------------------- 1 | $value) { 16 | yield $value => $key; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/functions/iterable_group.php: -------------------------------------------------------------------------------- 1 | $value) { 20 | $group = call_user_func($grouping, $value, $key); 21 | 22 | $index = array_search($group, $groups, true); 23 | 24 | if ($index === false) { 25 | $index = array_push($groups, $group) - 1; 26 | } 27 | 28 | $values[$index][] = $value; 29 | } 30 | 31 | unset($iterable); 32 | 33 | foreach ($groups as $index => $group) { 34 | yield $group => $values[$index]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/functions/iterable_has_all.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | if (!(bool)call_user_func($matcher, $value, $key)) { 18 | return false; 19 | } 20 | } 21 | 22 | return true; 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/iterable_has_any.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | if ((bool)call_user_func($matcher, $value, $key)) { 18 | return true; 19 | } 20 | } 21 | 22 | return false; 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/iterable_has_none.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | if ((bool)call_user_func($matcher, $value, $key)) { 18 | return false; 19 | } 20 | } 21 | 22 | return true; 23 | } 24 | -------------------------------------------------------------------------------- /src/functions/iterable_keys.php: -------------------------------------------------------------------------------- 1 | $value) { 16 | yield $key; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/functions/iterable_last.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | yield $key => call_user_func($callback, $value, $key); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/functions/iterable_map_keys.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | yield call_user_func($callback, $value, $key) => $value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/functions/iterable_max.php: -------------------------------------------------------------------------------- 1 | $max) ? $value : $max; 22 | } 23 | } else { 24 | foreach ($iterable as $value) { 25 | $max = ($first || call_user_func($compare, $max, $value) < 0) ? $value : $max; 26 | $first = false; 27 | } 28 | } 29 | 30 | return $max; 31 | } 32 | -------------------------------------------------------------------------------- /src/functions/iterable_min.php: -------------------------------------------------------------------------------- 1 | 0) ? $value : $min; 26 | $first = false; 27 | } 28 | } 29 | 30 | return $min; 31 | } 32 | -------------------------------------------------------------------------------- /src/functions/iterable_project.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | $projected = []; 18 | 19 | if (is_array($value) || $value instanceof \ArrayAccess) { 20 | foreach ($mapping as $to => $from) { 21 | $projected[$to] = $value[$from] ?? null; 22 | } 23 | } elseif (is_object($value) && !$value instanceof \DateTimeInterface) { 24 | foreach ($mapping as $to => $from) { 25 | $projected[$to] = $value->$from ?? null; 26 | } 27 | } else { 28 | $projected = array_fill_keys(array_keys($mapping), null); 29 | } 30 | 31 | yield $key => $projected; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/functions/iterable_reduce.php: -------------------------------------------------------------------------------- 1 | $value) { 20 | $result = call_user_func($callback, $result, $value, $key); 21 | } 22 | 23 | return $result; 24 | } 25 | -------------------------------------------------------------------------------- /src/functions/iterable_reshape.php: -------------------------------------------------------------------------------- 1 | $to) { 26 | if (isset($value[$from])) { 27 | $value[$to] = $value[$from]; 28 | } 29 | } 30 | 31 | foreach ($remove as $key => $null) { 32 | if (isset($value[$key])) { 33 | unset($value[$key]); 34 | } 35 | } 36 | }; 37 | 38 | $shapeObject = function ($value) use ($change, $remove): void { 39 | foreach ($change as $from => $to) { 40 | if (isset($value->$from)) { 41 | $value->$to = $value->$from; 42 | } 43 | } 44 | 45 | foreach ($remove as $key => $null) { 46 | unset($value->$key); 47 | } 48 | }; 49 | 50 | foreach ($iterable as $key => $value) { 51 | if (is_array($value) || $value instanceof \ArrayAccess) { 52 | $shapeArray($value); 53 | } elseif (is_object($value) && !$value instanceof \DateTimeInterface) { 54 | $shapeObject($value); 55 | } 56 | 57 | yield $key => $value; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/functions/iterable_reverse.php: -------------------------------------------------------------------------------- 1 | $value) { 22 | array_unshift($values, $value); 23 | } 24 | } else { 25 | /** @var array $keys */ 26 | $keys = []; 27 | $values = []; 28 | 29 | foreach ($iterable as $key => $value) { 30 | array_unshift($keys, $key); 31 | array_unshift($values, $value); 32 | } 33 | } 34 | 35 | unset($iterable); 36 | 37 | foreach ($values as $index => $value) { 38 | $key = isset($keys) ? $keys[$index] : $index; 39 | yield $key => $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/functions/iterable_separate.php: -------------------------------------------------------------------------------- 1 | array, 'values' => array] 12 | */ 13 | function iterable_separate(iterable $iterable): array 14 | { 15 | if (is_array($iterable)) { 16 | return ['keys' => array_keys($iterable), 'values' => array_values($iterable)]; 17 | } 18 | 19 | $keys = []; 20 | $values = []; 21 | 22 | foreach ($iterable as $key => $value) { 23 | $keys[] = $key; 24 | $values[] = $value; 25 | } 26 | 27 | return compact('keys', 'values'); 28 | } 29 | -------------------------------------------------------------------------------- /src/functions/iterable_slice.php: -------------------------------------------------------------------------------- 1 | $value) { 21 | if ($counter === $end) { 22 | return; 23 | } 24 | 25 | if ($counter >= $offset) { 26 | yield $key => $value; 27 | } 28 | 29 | $counter++; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/functions/iterable_sort.php: -------------------------------------------------------------------------------- 1 | $keys, 'values' => $values] = $preserveKeys 23 | ? iterable_separate($iterable) 24 | : ['keys' => null, 'values' => iterable_to_array($iterable, false)]; 25 | 26 | if (!is_array($values)) { 27 | throw new \UnexpectedValueException("Values should be an array"); // @codeCoverageIgnore 28 | } 29 | 30 | isset($comparator) 31 | ? uasort($values, $comparator) 32 | : asort($values, $flags); 33 | 34 | $counter = 0; 35 | unset($iterable); 36 | 37 | foreach ($values as $index => $value) { 38 | $key = isset($keys) ? $keys[$index] : $counter++; 39 | yield $key => $value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/functions/iterable_sort_keys.php: -------------------------------------------------------------------------------- 1 | $value) { 25 | $keys[] = $key; 26 | $values[] = $value; 27 | } 28 | 29 | unset($iterable); 30 | 31 | isset($comparator) 32 | ? uasort($keys, $comparator) 33 | : asort($keys, $flags); 34 | 35 | foreach ($keys as $index => $key) { 36 | yield $key => $values[$index]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/functions/iterable_sum.php: -------------------------------------------------------------------------------- 1 | toArray(); 23 | break; 24 | case method_exists($iterable, 'getArrayCopy'): 25 | /** @var \ArrayObject $iterable Duck typing */ 26 | $iterable = $iterable->getArrayCopy(); 27 | break; 28 | 29 | case $iterable instanceof \IteratorAggregate: 30 | return iterable_to_array($iterable->getIterator(), $preserveKeys); // recursion 31 | default: 32 | return iterator_to_array($iterable, $preserveKeys === true); 33 | } 34 | 35 | return $preserveKeys === false ? array_values($iterable) : $iterable; 36 | } 37 | -------------------------------------------------------------------------------- /src/functions/iterable_to_iterator.php: -------------------------------------------------------------------------------- 1 | getIterator(); 21 | } 22 | 23 | if (!$iterable instanceof \Iterator) { 24 | $iterable = new \IteratorIterator($iterable); 25 | } 26 | 27 | return $iterable; 28 | } 29 | -------------------------------------------------------------------------------- /src/functions/iterable_to_traversable.php: -------------------------------------------------------------------------------- 1 | $var) { 20 | $valid = type_is($var, $type); 21 | $casted = $valid ? $var : Internal\type_cast_var($var, $type); 22 | 23 | if (!$valid && !isset($casted)) { 24 | /** @var \TypeError $error */ 25 | $error = Internal\type_check_error($var, $type, $throwable, $msg, [type_describe($key, true)]); 26 | throw $error; 27 | } 28 | 29 | yield $key => $casted; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/functions/iterable_type_check.php: -------------------------------------------------------------------------------- 1 | $var) { 20 | if (!type_is($var, $type)) { 21 | /** @var \TypeError $error */ 22 | $error = Internal\type_check_error($var, $type, $throwable, $msg, [type_describe($key, true)]); 23 | throw $error; 24 | } 25 | 26 | yield $key => $var; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/functions/iterable_unique.php: -------------------------------------------------------------------------------- 1 | $value) { 20 | $entry = isset($serialize) ? call_user_func($serialize, $value, $key) : $value; 21 | 22 | if (is_string($entry) ? isset($fastMap[$entry]) : in_array($entry, $slowMap, true)) { 23 | continue; 24 | } 25 | 26 | if (is_string($entry)) { 27 | $fastMap[$entry] = true; 28 | } else { 29 | $slowMap[] = $entry; 30 | } 31 | 32 | yield $key => $value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/functions/iterable_unwind.php: -------------------------------------------------------------------------------- 1 | $value], $mapKey === null ? [] : [$mapKey => $key]); 23 | }; 24 | 25 | $setArrayAccess = function ($element, $value, $key) use ($column, $mapKey) { 26 | $copy = clone $element; 27 | 28 | $copy[$column] = $value; 29 | if ($mapKey !== null) { 30 | $copy[$mapKey] = $key; 31 | } 32 | 33 | return $copy; 34 | }; 35 | 36 | $setObject = function ($element, $value, $key) use ($column, $mapKey) { 37 | $copy = clone $element; 38 | 39 | $copy->$column = $value; 40 | if ($mapKey !== null) { 41 | $copy->$mapKey = $key; 42 | } 43 | 44 | return $copy; 45 | }; 46 | 47 | foreach ($iterable as $topKey => $element) { 48 | $set = null; 49 | $iterated = false; 50 | 51 | if (is_array($element)) { 52 | $value = $element[$column] ?? null; 53 | $set = $setArray; 54 | } elseif ($element instanceof \ArrayAccess) { 55 | $value = $element[$column] ?? null; 56 | $set = $setArrayAccess; 57 | } elseif (is_object($element) && !$element instanceof \DateTimeInterface) { 58 | $value = $element->$column ?? null; 59 | $set = $setObject; 60 | } else { 61 | $value = null; 62 | } 63 | 64 | if (!is_iterable($value) || $set === null) { 65 | yield ($preserveKeys ? $topKey : $counter++) => $element; 66 | continue; 67 | } 68 | 69 | foreach ($value as $key => $item) { 70 | $iterated = true; 71 | yield ($preserveKeys ? $topKey : $counter++) => $set($element, $item, $key); 72 | } 73 | 74 | if (!$iterated) { 75 | yield ($preserveKeys ? $topKey : $counter++) => $set($element, null, null); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/functions/iterable_values.php: -------------------------------------------------------------------------------- 1 |