{"id":24568986,"url":"https://github.com/zicht/itertools","last_synced_at":"2025-10-13T06:33:35.670Z","repository":{"id":46042267,"uuid":"70049938","full_name":"zicht/itertools","owner":"zicht","description":"Library - Python-inspired library for declaratively (functionally) processing (lists of) data","archived":false,"fork":false,"pushed_at":"2023-05-08T07:17:29.000Z","size":571,"stargazers_count":10,"open_issues_count":5,"forks_count":1,"subscribers_count":6,"default_branch":"release/3.x","last_synced_at":"2025-09-20T09:36:35.361Z","etag":null,"topics":["closure","filter","fluent-interface","iterator","itertools","manipulation","packagist","php","php-library","sort"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zicht.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-10-05T10:21:25.000Z","updated_at":"2022-02-25T08:15:53.000Z","dependencies_parsed_at":"2025-04-22T17:23:30.491Z","dependency_job_id":"c54c719e-fe2c-47fa-a176-b641e1637d8c","html_url":"https://github.com/zicht/itertools","commit_stats":null,"previous_names":[],"tags_count":105,"template":false,"template_full_name":null,"purl":"pkg:github/zicht/itertools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zicht%2Fitertools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zicht%2Fitertools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zicht%2Fitertools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zicht%2Fitertools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zicht","download_url":"https://codeload.github.com/zicht/itertools/tar.gz/refs/heads/release/3.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zicht%2Fitertools/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279013971,"owners_count":26085429,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["closure","filter","fluent-interface","iterator","itertools","manipulation","packagist","php","php-library","sort"],"created_at":"2025-01-23T14:55:55.576Z","updated_at":"2025-10-13T06:33:35.637Z","avatar_url":"https://github.com/zicht.png","language":"PHP","readme":"[![Build Status](https://scrutinizer-ci.com/g/zicht/itertools/badges/build.png?b=master)](https://scrutinizer-ci.com/g/zicht/itertools/build-status/master)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/zicht/itertools/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/zicht/itertools/?branch=master) \n[![Code Coverage](https://scrutinizer-ci.com/g/zicht/itertools/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/zicht/itertools/?branch=master)\n\n# Zicht Iterator Tools Library\nThe Iterator Tools, or itertools for short, are a collection of\nconvenience tools to handle sequences of data such as arrays,\niterators, and strings.  Some of the naming and API is based on the\nPython itertools.\n\n[Examples](#examples)\n\nCommon operations include:\n- [mapping](#mapping): `map` and `mapBy`\n- [filtering](#filtering): `filter`, `difference`\n- [sorting](#sorting): `sorted`\n- [grouping](#grouping): `groupBy`\n- [reducing](#reducing): `accumulate`, `collapse`, and `reduce`\n\n## Usage\nIn order to use the available itertools filters/functions via Twig, simply add this service definition in your `config/services.yaml`\n\n```yaml\nZicht\\Itertools\\twig\\Extension:\n  tags: ['twig.extension']\n```\n\n## Scripts\n- unit test: `composer test`\n- lint test: `composer lint`\n\n## Example data\nThe examples below will use the following data to illustrate how\nvarious Iterator tools work:\n\n```php\n$words = ['Useful', 'Goonies', 'oven', 'Bland', 'notorious'];\n$numbers = [1, 3, 2, 5, 4];\n$vehicles = [\n    [\n        'id' =\u003e 1,\n        'type' =\u003e 'car', \n        'wheels' =\u003e 4, \n        'colors' =\u003e ['red', 'green', 'blue'], \n        'is_cool' =\u003e false, \n        'price' =\u003e 20000,\n    ],\n    [\n        'id' =\u003e 2,\n        'type' =\u003e 'bike', \n        'wheels' =\u003e 2, \n        'colors' =\u003e ['red', 'green', 'blue'], \n        'is_cool' =\u003e false, \n        'price' =\u003e 600,\n    ],\n    [\n        'id' =\u003e 5,\n        'type' =\u003e 'unicicle', \n        'wheels' =\u003e 1, \n        'colors' =\u003e ['red'], \n        'is_cool' =\u003e true, \n        'price' =\u003e 150,\n    ],\n    [\n        'id' =\u003e 9,\n        'type' =\u003e 'car', \n        'wheels' =\u003e 8, \n        'colors' =\u003e ['blue'], \n        'is_cool' =\u003e true, \n        'price' =\u003e 100000,\n    ],\n];\n```\n\n## Examples\nWith the example data above, this is how you could use itertools to get all unique colors of the cars in alphabetical order:\n\n```php\nuse Zicht\\Itertools\\util\\Filters;\nuse function Zicht\\Itertools\\iterable;\n\n$vehicles = iterable($vehicles)\n    -\u003efilter(Filters::equals('car', 'type')) // {[vehicle...], [vehicle...]}\n    -\u003emap('colors') // {0: ['red', 'green', 'blue'], 1: ['blue']}\n    -\u003ecollapse() // {0: 'red', 1: 'green', 2: 'blue', 3: 'blue'}\n    -\u003eunique() // {0: 'red', 1: 'green', 2: 'blue'}\n    -\u003esorted(); // {2: 'blue', 1: 'green', 0: 'red'}\n```\n\nYou can achieve the same in Twig:\n\n```twig\n{% for vehicle_color in vehicles\n    |it.filter(it.filters.equals('car', 'type'))\n    |it.map('colors')\n    |it.collapse\n    |it.unique\n    |it.sorted\n%}\n    {{ vehicle_color }}\n{% endfor %}\n```\n\n## Getter strategy\nMany itertools can be passed a `$strategy` parameter.  This parameter\nis used to obtain a value from the elements in the collection.  The\n`$strategy` can be one of three things:\n\n1. null, in which case the element itself is returned.  For example:\n\n    ```php\n    use function Zicht\\Itertools\\iterable;\n\n    $result = iterable($words)-\u003emap(null);\n    var_dump($result);\n    // {0: 'Useful', 1: 'Goonies', 2: 'oven', 3: 'Bland', 4: 'notorious'}\n    ```\n\n    Or in Twig:\n\n    ```twig\n   {{ dump(word|it.map) }}\n    ```\n\n2. a closure, in which case the closure is called with the element\n   value and key as parameters to be used to compute a return value.\n   For example:\n\n    ```php\n    use function Zicht\\Itertools\\iterable;\n\n    $getDouble = fn($value, $key) =\u003e 2 * $value;\n    $result = iterable($numbers)-\u003emap($getDouble);\n    var_dump($result);\n    // {0: 2, 1: 6, 2: 4, 3: 10, 4: 8}\n    ```\n\n   Or in Twig:\n    ```twig\n   {{ dump(numbers|it.map(num =\u003e 2 * num)) }}\n    ```\n\n3. a string, in which case this string is used to create a closure\n   that tries to find public properties, methods, or array indexes.\n   For example:\n\n    ```php\n    use function Zicht\\Itertools\\iterable;\n\n    $result = iterable($vehicles)-\u003emap('type');\n    var_dump($result);\n    // {0: 'car', 1: 'bike', 2: 'unicicle', 3: 'car'}\n    ```\n\n   Or in Twig:\n\n    ```twig\n   {{ dump(word|it.map) }}\n    ```\n\n   The string can consist of multiple dot separated words, allowing\n   access to nested properties, methods, and array indexes.\n\n   If one of the words in the string can not be resolved into an\n   existing property, method, or array index, the value `null` will be\n   returned.  For example:\n\n    ```php\n    use function Zicht\\Itertools\\iterable;\n\n    $result = iterable($vehicles)-\u003emap('colors.2');\n    var_dump($result);\n    // {0: 'blue', 1: 'blue', 2: null, 3: null}\n    ```\n\n   Or in Twig:\n\n    ```twig\n   {{ dump(vehicles|it.map('colors.2')) }}\n    ```\n\n## Fluent interface\nOne way to use the Iterator Tools is to convert the array, Iterator,\nstring, etc into an `IterableIterator`.  This class provides a fluent\ninterface to all of the common operations.  For example:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$result = iterable($vehicles)-\u003efilter('is_cool')-\u003emapBy('id')-\u003emap('type');\nvar_dump($result);\n// {5: 'unicicle', 9: 'car'}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(vehicles|it.filter('is_cool').mapBy('id').map('type')) }}\n```\n\n## Mapping\nMapping converts one collection into another collection of equal\nlength.  Using `map` allows manipulation of the elements while `mapBy`\nallows manipulation of the collection keys.\n\nFor example, we can use a closure to create a title for each element\nin `$vehicles`:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$getTitle = fn($value, $key) =\u003e sprintf('%s with %s wheels', $value['type'], $value['wheels']);\n$titles = iterable($vehicles)-\u003emap($getTitle);\nvar_dump($titles);\n// {0: 'car with 4 wheels', ..., 3: 'car with 8 wheels'}\n```\n\nUsing the string [getter strategy](#getter-strategy) we can easily get\nthe types for each element in `$vehicles` mapped by the vehicle\nidentifiers.  For example:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$types = iterable($vehicles)-\u003emapBy('id')-\u003emap('type');\nvar_dump($types);\n// {1: 'car', 2: 'bike', 5: 'unicicle', 9: 'car'}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(vehicles|it.mapBy('id').map('type')) }}\n```\n\n\nThere are several common mapping closures available\nin [mappings.php](src/Zicht/Itertools/util/Mappings.php).  Calling these\nfunctions returns a closure that can be passed to `map` and `mapBy`.\nFor example:\n\n```php\nuse Zicht\\Itertools\\util\\Mappings;\nuse function Zicht\\Itertools\\iterable;\n\n$lengths = iterable($words)-\u003emap(Mappings::length());\nvar_dump($lengths);\n// {0: 6, 1: 3, 2: 4, 3: 5, 4: 9}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(words|it.map(it.mappings.length)) }}\n```\n\n## Filtering\nFiltering converts one collection into another, possibly shorter,\ncollection.  Using `filter` each element in the collection is\nevaluated, the elements that are considered `empty` will be rejected,\nwhile the elements that are not `empty` will be allowd to pass through\nthe filter.\n\nFor example, we can use a closure to determine if an element is\nexpensive, the `filter` will then only allow the expensive elements\nthrough:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$isExpensive = fn($value, $key) =\u003e $value['price'] \u003e= 10000;\n$expensiveTypes = iterable($vehicles)-\u003efilter($isExpensive)-\u003emap('type');\nvar_dump($expensiveTypes);\n// {1: 'car', 9: 'car'}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(vehicles|it.filter(vehicle =\u003e vehicle.price \u003e= 10000).map('type')) }}\n```\n\nUsing the string [getter strategy](#getter-strategy) we can get only\nthe `$vehicles` that are considered to be cool.  For example:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$coolVehicleTypes = iterable($vehicles)-\u003efilter('is_cool')-\u003emap('type');\nvar_dump($coolVehicleTypes);\n// {5: 'unicicle', 9: 'car'}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(vehicles|it.filter('is_cool').map('type')) }}\n```\n\nThere are several common filter closures available\nin [filters.php](src/Zicht/Itertools/util/Filters.php).  Calling these\nfunction returns a closure that can be passed to `filter`.  For\nexample:\n\n```php\nuse Zicht\\Itertools\\util\\Filters;\nuse function Zicht\\Itertools\\iterable;\n\n$movieWords = iterable($words)-\u003efilter(Filters::in(['Shining', 'My little pony', 'Goonies']));\nvar_dump($movieWords);\n// {1: 'Goonies'}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(words|it.filter(it.filters.in(['Shining', \"My little pony', 'Goonies'])) }}\n```\n\n## Sorting\n`sorted` converts one collection into another collection of equal size\nbut with the elements possibly reordered.\n\nFor example, using the `null` [getter strategy](#getter-strategy),\nwhich is the default, we will sort using the element values in\nascending order:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$ordered = iterable($numbers)-\u003esorted();\nvar_dump($ordered);\n// {0: 1, 2: 2, 1: 3, 4: 4, 3: 5}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(numbers|it.sorted }}\n```\n\nThe sorting algorithm will preserve the keys and is guaranteed to be\nstable.  I.e. when elements are sorted using the same value, then the\nsorted order is guaranteed to be the same as the order of the input\nelements.  This is contrary to the standard PHP sorting functions.\n\nUsing the closure [getter strategy](#getter-strategy) the returned\nvalue is used to determine the order.  The closure is called exactly\nonce per element, and the resulting values must be comparable.  For\nexample:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$getLower = fn($value, $key) =\u003e strtolower($value);\n$ordered = iterable($words)-\u003esorted($getLower);\nvar_dump($ordered);\n// {3: 'Bland', 1: 'Goonies', 2: 'oven', 0: 'Useful', 4: 'notorious'};\n```\n\nThe [mappings.php](src/Zicht/Itertools/util/Mappings.php) provides a\nmapping closure which returns a random number.  This can be used to\nsort a collection in a random order.  For example:\n\n```php\nuse Zicht\\Itertools\\util\\Mappings;\nuse function Zicht\\Itertools\\iterable;\n\n$randomized = iterable($words)-\u003esorted(Mappings::random());\nvar_dump($randomized);\n// {... randomly ordere words ...}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(words|it.sorted(it.mappings.random)) }}\n```\n\n## Grouping\n`groupBy` converts one collection into one or more collections that\ngroup the elements together on a specific criteria.\n\nFor example, using the string [getter strategy](#getter-strategy) we\ncan group all the `$vehicles` of the same type together:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$vehiclesByType = iterable($vehicles)-\u003egroupBy('type');\nvar_dump($vehiclesByType);\n// {'bike': {1: [...]}, 'car': {0: [...], 3: [...]} 'unicicle': {2: [...]}}\n```\n\nOr in Twig:\n\n```twig\n{{ dump(vehicles|it.groupBy('type')) }}\n```\n\nNot that the original keys of the vehicles are still part of the\nresulting groups, and the elements within each group keep the order\nthat they had in the input, i.e. it uses the stable sorting provided\nby [`sorted`](#sorting).\n\n## Reducing\n`reduce` converts a collection into a single value by calling a\nclosure of two arguments cumulatively to the elements in the\ncollection, from left to right.\n\nFor example, without any arguments `reduce` will add all elements of\nthe collection together:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$sum = iterable($numbers)-\u003ereduce();\nvar_dump($sum);\n// 15\n```\n\nOr in Twig:\n\n```twig\n{{ dump(numbers|it.reduce) }}\n```\n\nIn the above example, the default closure that is used looks like this:\n\n```php\npublic static function add($a, $b): \\Closure\n{\n    return $a + $b;\n}\n```\n\nGiven that `$numbers` consists of the elements {1, 3, 2, 5, 4}, the\n`add` closure is called four times:\n\n```php\n$sum = Reductions::add(Reductions::add(Reductions::add(Reductions::add((1, 3), 2), 5), 4));\nvar_dump($sum);\n// 15\n```\n\nThere are several common reduction closures available\nin [reductions.php](src/Zicht/Itertools/util/Reductions.php).  Calling\nthese functions returns a closure that can be passed to `reduction`.\nFor example:\n\n```php\nuse Zicht\\Itertools\\util\\Reductions;\nuse function Zicht\\Itertools\\iterable;\n\n$scentence = iterable($words)-\u003ereduce(Reductions::join(' - '));\nvar_dump($scentence);\n// 'Useful - Goonies - oven - Bland - notorious'\n```\n\nOr in Twig:\n\n```twig\n{{ dump(words|it.reduce(it.reductions.join(' - ')) }}\n```\n\nAnother common reduction is chaining multiple lists together into one list.\nWe call this process collapse.  This process can also be achieved using `reduce`\nand `chain` together, however, because it is used frequently the `collapse` helper\nmakes its usage easier, for example:\n\n```php\nuse function Zicht\\Itertools\\iterable;\n\n$flat = iterable([['one', 'two'], ['three']])-\u003ecollapse();\nvar_dump($flat);\n// {0: 'one', 1: 'two', 0: 'three'}\n```\n\nOr in Twig:\n\n```twig\n{% set data = [['one', 'two'], ['three']] %}\n{{ dump(data|it.collapse) }}\n```\n\n# Maintainer(s)\n* Boudewijn Schoon \u003cboudewijn@zicht.nl\u003e\n* Virginia Meijer \u003cvirginia@zicht.nl\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzicht%2Fitertools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzicht%2Fitertools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzicht%2Fitertools/lists"}