{"id":18698285,"url":"https://github.com/ihor/nspl","last_synced_at":"2025-05-16T10:06:09.643Z","repository":{"id":45522684,"uuid":"47998458","full_name":"ihor/NSPL","owner":"ihor","description":"Non-Standard PHP Library - functional primitives toolbox and more","archived":false,"fork":false,"pushed_at":"2022-05-30T19:24:50.000Z","size":440,"stargazers_count":375,"open_issues_count":5,"forks_count":16,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-05-16T10:05:25.868Z","etag":null,"topics":["functional","lazy","lodash","php","ramda","underscore"],"latest_commit_sha":null,"homepage":"http://nspl.readthedocs.org","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/ihor.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}},"created_at":"2015-12-14T20:09:08.000Z","updated_at":"2025-04-12T09:33:35.000Z","dependencies_parsed_at":"2022-07-17T02:16:07.613Z","dependency_job_id":null,"html_url":"https://github.com/ihor/NSPL","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ihor%2FNSPL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ihor%2FNSPL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ihor%2FNSPL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ihor%2FNSPL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ihor","download_url":"https://codeload.github.com/ihor/NSPL/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509476,"owners_count":22082891,"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","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":["functional","lazy","lodash","php","ramda","underscore"],"created_at":"2024-11-07T11:27:40.234Z","updated_at":"2025-05-16T10:06:09.614Z","avatar_url":"https://github.com/ihor.png","language":"PHP","readme":"Non-standard PHP library (NSPL)\n===============================\nNon-standard PHP Library (NSPL) is a collection of modules that are meant to solve common day to day routine problems:\n\n - [nspl\\f](#nsplf) - provides functions that act on other functions. Helps to write code in functional programming paradigm\n - [nspl\\op](#nsplop) - provides functions that perform standard PHP operations and can be passed as callbacks to higher-order functions. Mimics Python's [operator](https://docs.python.org/2/library/operator.html) module\n - [nspl\\a](#nspla) - provides missing array functions which also can be applied to traversable sequences\n - [nspl\\a\\lazy](#nsplalazy) - provides lazy versions of functions from ```\\nspl\\a```\n - [nspl\\args](#nsplargs) - validates function arguments (will be moved into a separate package in version 2.0)\n - [nspl\\ds](#nsplds) - provides non-standard data structures and methods to work with them\n - [nspl\\rnd](#nsplrnd) - helps to pick random items from sequences of data\n\nNSPL aims to make code compact and less verbose but still clear and readable. Look at the following example:\n```php\n// get user ids\n$userIds = map(propertyGetter('id'), $users);\n\n// or sort them by age\n$sortedByAge = sorted($users, methodCaller('getAge'));\n\n// or check if they all are online\n$online = all($users, methodCaller('isOnline'));\n\n// or define new function as composition of the existing ones\n$flatMap = compose(rpartial(flatten, 1), map);\n```\n\nIn pure PHP it would look like this:\n```php\n// get user ids\n$userIds = array_map(function($user) { return $user-\u003eid; }, $users);\n\n// sort them by age, note that the following code modifies the original users array\nusort($users, function($user1, $user2) {\n    return $user1-\u003egetAge() - $user2-\u003egetAge();\n});\n\n// check if they all are online\n$online = true;\nforeach ($users as $user) {\n    if (!$user-\u003eisOnline()) {\n        $online = false;\n        break;\n    }\n}\n\n// define new function as composition of the existing ones\n$flatMap = function($function, $list) {\n    // note the inconsistency in array_map and array_reduce parameters\n    return array_reduce(array_map($function, $list), 'array_merge', []);\n};\n```\nYou can see more examples in the [library reference](#reference) below or [here](https://github.com/ihor/Nspl/blob/master/examples).\n\nInstallation\n------------\n#### Using [composer](https://getcomposer.org)\nDefine the following requirement in your composer.json file:\n```\n\"require\": {\n    \"ihor/nspl\": \"~1.3\"\n}\n```\nor execute the following in the command line:\n```\ncomposer require ihor/nspl\n```\n\nFor the latest version which contains way more functionality require version ```2.0.*-dev```\n\n#### Manually\nCheckout [the code](https://github.com/ihor/Nspl) and include ```autoload.php```:\n```php\ninclude 'path/to/nspl/autoload.php';\n```\n\nReference\n=========\nThis is documentation for the dev version ```2.0.*-dev``` which contains the latest changes. For the version ```1.3``` (latest stable version) documentation click [here](https://github.com/ihor/Nspl/tree/1.3).\n\nHere I assume that described functions are imported with [use function](http://php.net/manual/en/language.namespaces.importing.php):\n```php\nuse function nspl\\a\\zip;\n$pairs = zip([1, 2, 3], ['a', 'b', 'c']);\n```\nIf your PHP version is less than 5.6 you should import parent namespace and use functions with the namespace prefix:\n```php\nuse nspl\\a;\n$pairs = a\\zip([1, 2, 3], ['a', 'b', 'c']);\n```\n\n## Table of contents\n\n* [nspl\\f](#nsplf)\n    * [id](#idvalue)\n    * [apply](#applyfunction-array-args--)\n    * [partial](#partialfunction-arg1)\n    * [rpartial](#rpartialfunction-arg1)\n    * [ppartial](#ppartialfunction-array-args)\n    * [flipped](#flippedfunction)\n    * [compose](#composef-g)\n    * [pipe](#pipeinput-function1-function2)\n    * [curried](#curriedfunction-withoptionalargs--false)\n    * [uncurried](#uncurriedfunction)\n    * [memoized](#memoizedfunction)\n    * [throttled](#throttledfunction-wait)\n    * [Callbacks](#callbacks)\n* [nspl\\op](#nsplop)\n    * [Callbacks](#callbacks-1)\n    * [itemGetter](#itemgetterkey)\n    * [propertyGetter](#propertygetterproperty)\n    * [methodCaller](#methodcallermethod-array-args--array)\n    * [instanceCreator](#instancecreatorclass)\n* [nspl\\a](#nspla)\n    * [all](#allsequence-predicate)\n    * [any](#anysequence-predicate)\n    * [map](#mapfunction-sequence)\n    * [flatMap](#flatmapfunction-sequence)\n    * [zip](#zipsequence1-sequence2)\n    * [zipWith](#zipwithfunction-sequence1-sequence2)\n    * [reduce](#reducefunction-sequence-initial--0)\n    * [filter](#filterpredicate-sequence)\n    * [filterNot](#filternotpredicate-sequence)\n    * [take](#takesequence-n-step--1)\n    * [takeKeys](#takekeyssequence-array-keys)\n    * [takeWhile](#takewhilepredicate-sequence)\n    * [first](#firstsequence)\n    * [second](#secondsequence)\n    * [drop](#dropsequence-n)\n    * [dropKeys](#dropkeyssequence-array-keys)\n    * [dropWhile](#dropwhilepredicate-sequence)\n    * [last](#lastsequence)\n    * [partition](#partitionpredicate-sequence)\n    * [span](#spanpredicate-sequence)\n    * [indexed](#indexedsequence-by-keeplast--true-transform--null)\n    * [sorted](#sortedsequence-reversed--false-key--null-cmp--null)\n    * [keySorted](#keysortedsequence-reversed--false)\n    * [flatten](#flattensequence-depth--null)\n    * [pairs](#pairssequence-valuekey--false)\n    * [merge](#mergesequence1-sequence2)\n    * [reorder](#reorderarray-list-from-to)\n    * [value](#valuearray-key-default--null)\n    * [values](#valuessequence)\n    * [keys](#keyssequence)\n    * [in](#initem-sequence)\n    * [diff](#diffsequence1-sequence2)\n    * [intersect](#intersectsequence1-sequence2)\n    * [cartesianProduct](#cartesianproductsequences)\n    * [isList](#islistvar)\n    * [Chaining](#chaining)\n    * [Callbacks](#callbacks-2)\n* [nspl\\a\\lazy](#nsplalazy)\n* [nspl\\args](#nsplargs)\n    * [expects](#expectsconstraints-arg-atposition--null-otherwisethrow--invalidargumentexception)\n    * [expectsAll](#expectsallconstraints-array-args-array-atpositions---otherwisethrow--invalidargumentexception)\n    * [expectsOptional](#expectsoptionalconstraints-arg-atposition--null-otherwisethrow--invalidargumentexception)\n    * [Predefined constraints](#predefined-constraints)\n    * [Custom constraints](#custom-constraints)\n* [nspl\\ds](#nsplds)\n    * [DefaultArray](#defaultarray)\n    * [Set](#set)\n* [nspl\\rnd](#nsplrnd)\n    * [randomString](#length)\n    * [choice](#choicesequence)\n    * [weightedChoice](#weightedchoiceweightpairs)\n    * [sample](#samplepopulation-length-preservekeys--false)\n* [nspl](#nspl)\n    * [getType](#gettypevar)\n\n## nspl\\f\n\nProvides functions that act on other functions. Helps to write code in the functional programming paradigm.\n\n##### id($value)\n\nIdentity function. Returns passed value.\n\n```php\nassert(1 === id(1));\n```\n\n##### apply($function, array $args = [])\n\nApplies given function to arguments and returns the result\n```php\nassert([1, 3, 5, 7, 9] === apply('range', [1, 10, 2]));\n```\n\n##### partial($function, $arg1)\n\nReturns new partial function which will behave like ```$function``` with predefined *left* arguments passed to partial\n```php\n$sum = function($a, $b) { return $a + $b; };\n$inc = partial($sum, 1);\n```\n\n##### rpartial($function, $arg1)\n\nReturns new partial function which will behave like ```$function``` with predefined *right* arguments passed to rpartial\n```php\n$cube = rpartial('pow', 3);\n```\n\n##### ppartial($function, array $args)\n\nReturns new partial function which will behave like ```$function``` with predefined *positional* arguments passed to ppartial\n```php\n$oddNumbers = ppartial('range', array(0 =\u003e 1, 2 =\u003e 2));\n```\n\n##### flipped($function)\n\nReturns function which accepts arguments in the reversed order\n\n##### compose($f, $g)\n\nReturns new function which applies each given function to the result of another from right to left\n```compose(f, g, h)``` is the same as ```f(g(h(x)))```\n```php\nuse const \\nspl\\a\\flatten;\nuse const \\nspl\\a\\map;\nuse function \\nspl\\f\\compose;\nuse function \\nspl\\f\\partial;\nuse function \\nspl\\f\\rpartial;\n\n$flatMap = compose(rpartial(flatten, 1), map);\nassert(['hello', 'world', 'foo', 'bar'] === $flatMap(partial('explode', ' '), ['hello world', 'foo bar']));\n```\n\n##### pipe($input, $function1, $function2)\n\nPasses ```$input``` to composition of functions (functions have to be in the reversed order)\n```php\nuse const \\nspl\\op\\sum;\nuse const \\nspl\\a\\filter;\nuse const \\nspl\\a\\map;\nuse const \\nspl\\a\\reduce;\nuse function \\nspl\\f\\partial;\n\n$isEven = function($x) { return $x % 2 === 0; };\n$square = function($x) { return $x * $x; };\n\n// sum of squares of all even numbers less than 20\n$sum = pipe(\n    range(1, 20),\n    partial(filter, $isEven),\n    partial(map, $square),\n    partial(reduce, sum)\n);\n```\n\n\u003e **Tip**\n\u003e\n\u003e You can use [chaining](#chaining) to get rid of partials in sequence transformations:\n\u003e\n\u003e ```php\n\u003e use function \\nspl\\a\\with;\n\u003e\n\u003e $sum = with(range(1, 20))\n\u003e    -\u003efilter($isEven)\n\u003e    -\u003emap($square)\n\u003e    -\u003ereduce(sum);\n\u003e ```\n\n##### curried($function, $withOptionalArgs = false)\n\nReturns a [curried](https://en.wikipedia.org/wiki/Currying) version of the function. If you are going to curry a function which reads args with ```func_get_args()``` then pass the number of args as the 2nd argument.\n\nIf the second argument is true, then curry function with optional args otherwise curry it only with required args. Alternatively, you can pass the exact number of args you want to curry.\n\n##### uncurried($function)\n\nReturns normal (uncurried) version of a [curried function](https://en.wikipedia.org/wiki/Currying)\n\n##### memoized($function)\n\nReturns memoized ```$function``` which returns the cached result when the same inputs occur again\n```php\n$f = function($arg) {\n    echo sprintf(\"Performing heavy calculations with '%s'\\n\", $arg);\n    return $arg;\n};\n\n$memoized = memoized($f);\necho $memoized('Hello world!') . \"\\n\";\necho $memoized('Hello world!') . \"\\n\";\n```\nwhich outputs\n```\nPerforming heavy calculations with 'Hello world!'\nHello world!\nHello world!\n```\n\n##### throttled($function, $wait)\n\nReturns throttled version of the passed function, that, when invoked repeatedly, will only actually call the original function at most once per every wait milliseconds.\n```php\n$f = function() {\n    echo \"Invoked\\n\";\n};\n\n$throttled = throttled($f, 10);\n\n$startedAt = microtime(true);\ndo {\n    $throttled();\n} while((microtime(true) - $startedAt) * 1000 \u003c 30); // 30ms\n```\nwhich outputs\n```\nInvoked\nInvoked\nInvoked\n```\n\n##### Callbacks\n\n```nspl\\f``` provides all its functions as callbacks in its constants which have the same names as the functions.\n```php\nuse const \\nspl\\a\\map;\nuse const \\nspl\\a\\filter;\n\n$incListItems = partial(map, function($v) { return $v + 1; });\n$filterNumbers = partial(filter, 'is_numeric');\n```\n\nCheck more ```\\nspl\\f``` examples [here](https://github.com/ihor/Nspl/blob/master/examples/f.php).\n\n\n## nspl\\op\n\nClass ```nspl\\op``` provides functions that perform standard PHP operations and can be passed as callbacks to higher-order functions. Mimics Python's [operator](https://docs.python.org/2/library/operator.html) module. For example:\n\n```php\nuse const nspl\\op\\sum;\nuse function nspl\\a\\reduce;\n\nassert(6 === reduce(sum, [1, 2, 3]));\n```\n\n##### Callbacks\n\nThe module provides the following operations both as functions and callbacks. See an example below.\n\nFunction | Operation\n---------|-----------------------------------------------\nsum      | +\nsub      | -\nmul      | *\ndiv      | /\nmod      | %\ninc      | ++\ndec      | --\nneg      | -\nband     | \u0026\nbxor     | ^\nbor      | \u0026#124;\nbnot     | ~\nlshift   | \u003c\u003c\nrshift   | \u003e\u003e\nlt       | \u003c\nle       | \u003c=\neq       | ==\nidnt     | ===\nne       | !=\nnidnt    | !==\ngt       | \u003e\nge       | \u003e=\nand_     | \u0026\u0026\nor_      | \u0026#124;\u0026#124;\nxor_     | xor\nnot      | !\nconcat   | .\ninstanceof_   | instanceof\nint      | (int)\nbool     | (bool)\nfloat    | (float)\nstr      | (string)\narray_   | (array)\nobject   | (object)\n\n##### itemGetter($key)\nReturns a function that returns key value for a given array\n\n```php\nuse function nspl\\op\\itemGetter;\nuse function nspl\\a\\map;\n\nassert([2, 5, 8] === map(itemGetter(1), [[1, 2, 3], [4, 5, 6], [7, 8, 9]]));\n```\n\n##### propertyGetter($property)\nReturns a function that returns property value for a given object\n\n```php\n$userIds = map(propertyGetter('id'), $users);\n```\n\n##### methodCaller($method, array $args = array())\nReturns a function that returns method result for a given object on predefined arguments\n\n```php\n$userIds = map(methodCaller('getId'), $users);\n```\n\n##### instanceCreator($class)\nReturns a function that returns a new instance of a predefined class, passing its parameters to the constructor\n\n```php\n$users = map(instanceCreator(User::class), $usersData);\n```\n\nCheck more ```\\nspl\\op``` examples [here](https://github.com/ihor/Nspl/blob/master/examples/op.php).\n\n## nspl\\a\n\nProvides missing array functions which also can be applied to traversable sequences\n\n##### all($sequence, $predicate)\n\nReturns true if all of the ````$sequence``` items satisfy the predicate (or if the ```$sequence``` is empty). If the predicate was not passed returns true if all of the ```$sequence``` items are true.\n\n```php\nassert(true === all([true, true, true]));\n```\n\n##### any($sequence, $predicate)\n\nReturns true if any of the ```$sequence``` items satisfy the predicate. If the predicate was not passed returns true if any of the ```$sequence``` items are true.\n\n```php\nassert(true === any([true, false, false]));\n```\n\n##### map($function, $sequence)\n\nApplies function of one argument to each sequence item\n```php\nassert(['A', 'B', 'C'] === map('strtoupper', ['a', 'b', 'c']));\n```\n\n##### flatMap($function, $sequence)\n\nApplies function of one argument to each sequence item and flattens the result\n```php\n$duplicate = function($v) { return [$v, $v]; }\nassert(['hello', 'hello', 'world', 'world'] === flatMap($duplicate, ['hello', 'world']));\n```\n\n##### zip($sequence1, $sequence2)\n\nZips two or more sequences\n```php\nassert([[1, 'a'], [2, 'b'], [3, 'c']] === zip([1, 2, 3], ['a', 'b', 'c']));\n```\n\n##### zipWith($function, $sequence1, $sequence2)\n\nGeneralises zip by zipping with the function given as the first argument, instead of an array-creating function\n```php\nuse const \\nspl\\op\\sum;\n\nassert([101, 1002, 10003] === zipWith(sum, [1, 2, 3], [100, 1000, 10000]));\n```\n\n##### reduce($function, $sequence, $initial = 0)\n\nApplies function of two arguments cumulatively to the sequence items, from left to right to reduce the sequence to a single value\n```php\nassert(6 === reduce(function($a, $b) { return $a + $b; }, [1, 2, 3]));\n\n// Which is the same as\nuse const \\nspl\\op\\sum;\nassert(6 === reduce(sum, [1, 2, 3]));\n\n```\n\n##### filter($predicate, $sequence)\n\nReturns sequence items that satisfy the predicate\n```php\nassert([1, 2, 3] === filter('is_numeric', ['a', 1, 'b', 2, 'c', 3]));\n```\n\n##### filterNot($predicate, $sequence)\n\nReturns sequence items that don't satisfy the predicate\n```php\nassert(['a', 'b', 'c'] === filterNot('is_numeric', ['a', 1, 'b', 2, 'c', 3]));\n```\n\n##### take($sequence, $N, $step = 1)\n\nReturns the first N sequence items with the given step\n```php\nassert([1, 3, 5] === take([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 2));\n```\n\n##### takeKeys($sequence, array $keys)\n\nReturns sequence containing only the given keys\n```php\nassert(array('hello' =\u003e 1, 'world' =\u003e 2) === takeKeys(array('hello' =\u003e 1, 'world' =\u003e 2, 'foo' =\u003e 3), ['hello', 'world']));\n```\n\n##### takeWhile($predicate, $sequence)\n\nReturns the longest sequence prefix of all items which satisfy the predicate\n```php\nassert([1, 2, 3] === takeWhile('is_numeric', [1, 2, 3, 'a', 'b', 'c', 4, 5, 6]));\n```\n\n##### first($sequence)\n\nReturns the first sequence item\n```php\nassert(1 === first([1, 2, 3, 4, 5, 6, 7, 8, 9]));\n```\n\n##### second($sequence)\n\nReturns the second sequence item\n```php\nassert(2 === second([1, 2, 3, 4, 5, 6, 7, 8, 9]));\n```\n\n##### drop($sequence, $N)\n\nDrops the first N sequence items\n```php\nassert([7, 8, 9] === drop([1, 2, 3, 4, 5, 6, 7, 8, 9], 6));\n```\n\n##### dropKeys($sequence, array $keys)\n\nReturns array containing all keys except the given ones\n```php\nassert(array('hello' =\u003e 1, 'world' =\u003e 2) === dropKeys(array('hello' =\u003e 1, 'world' =\u003e 2, 'foo' =\u003e 3), ['foo']));\n```\n\n##### dropWhile($predicate, $sequence)\n\nDrops the longest sequence prefix of all items which satisfy the predicate\n```php\nassert(['a', 'b', 'c', 4, 5, 6] === dropWhile('is_numeric', [1, 2, 3, 'a', 'b', 'c', 4, 5, 6]));\n```\n\n##### last($sequence)\n\nReturns the last sequence item\n```php\nassert(9 === last([1, 2, 3, 4, 5, 6, 7, 8, 9]));\n```\n\n##### partition($predicate, $sequence)\n\nReturns two lists, one containing values for which the predicate returned true, and the other containing the items that returned false\n```php\nassert([[1, 2, 3], ['a', 'b', 'c']] === partition('is_numeric', ['a', 1, 'b', 2, 'c', 3]));\n```\n\n##### span($predicate, $sequence)\n\nReturns two lists, one containing values for which your predicate returned true until the predicate returned false, and the other containing all the items that left\n```php\nassert([[1], ['a', 2, 'b', 3, 'c']] === span('is_numeric', [1, 'a', 2, 'b', 3, 'c']));\n```\n\n##### indexed($sequence, $by, $keepLast = true, $transform = null)\n\nReturns array which contains indexed sequence items\n\n```$by``` is an array key or a function\nIf ```$keepLast``` is true only the last item with the key will be returned otherwise a list of items which share the same key value will be returned\n```$transform``` is a function that transforms list item after indexing\n\n```php\n$indexedById = indexed([\n    array('id' =\u003e 1, 'name' =\u003e 'John'),\n    array('id' =\u003e 2, 'name' =\u003e 'Kate'),\n    array('id' =\u003e 3, 'name' =\u003e 'Robert'),\n], 'id');\n```\n\n##### sorted($sequence, $reversed = false, $key = null, $cmp = null)\n\nReturns array which contains sorted items from the passed sequence\n\nIf ```$reversed``` is true then return reversed sorted sequence. If ```$reversed``` is not boolean and ```$key``` was not passed then acts as a ```$key``` parameter\n```$key``` is a function of one argument that is used to extract a comparison key from each item\n```$cmp``` is a function of two arguments which returns a negative number, zero or positive number depending on whether the first argument is smaller than, equal to, or larger than the second argument\n```php\nassert([1, 2, 3] === sorted([2, 3, 1]));\nassert(['c', 'b', 'a'] === sorted(['c', 'a', 'b'], true));\n\n$usersSortedByName = sorted($users, function($u) { return $u-\u003egetName(); });\n\n// Which is the same as\nuse function \\nspl\\op\\methodCaller;\n$usersSortedByName = sorted($users, methodCaller('getName'));\n```\n\nCheck more ```\\nspl\\a\\sorted``` examples [here](https://github.com/ihor/Nspl/blob/master/examples/a_sorted.php).\n\n##### keySorted($sequence, $reversed = false)\n\nReturns array which contains sequence items sorted by keys\n```php\nassert(array('a' =\u003e 1, 'b' =\u003e 2, 'c' =\u003e 3) === keySorted(array('b' =\u003e 2, 'c' =\u003e 3, 'a' =\u003e 1));\n```\n\n##### flatten($sequence, $depth = null)\n\nFlattens multidimensional sequence\n```php\nassert([1, 2, 3, 4, 5, 6, 7, 8, 9] === flatten([[1, [2, [3]]], [[[4, 5, 6]]], 7, 8, [9]]));\nassert([1, 2, [3], [4, 5, 6], 7, 8, 9] === flatten([[1, [2, [3]]], [[[4, 5, 6]]], 7, 8, [9]], 2));\n```\n\n##### pairs($sequence, $valueKey = false)\n\nReturns a list of (key, value) pairs. If ```$valueKey``` is true then returns (value, key) pairs.\n```php\nassert([['a', 'hello'], ['b', 'world'], ['c', 42]] === pairs(array('a' =\u003e 'hello', 'b' =\u003e 'world', 'c' =\u003e 42)));\n```\n\n##### merge($sequence1, $sequence2)\n\nReturns array containing ```$sequence1``` items and ```$sequence2``` items\n```php\nassert([1, 2, 3, 4, 5, 6] === merge([1, 2, 3], [4, 5, 6]));\n```\n\n##### reorder(array $list, $from, $to)\n\nMoves list item to another position\n```php\nassert([2, 0, 1] === reorder([0, 1, 2], 2, 0)); // move item from the 2nd position to the begining of the list\n```\n\n##### value($array, $key, $default = null)\n\nReturns array value by key if it exists otherwise returns the default value\n```php\n$data = array('a' =\u003e 1, 'b' =\u003e 2, 'c' =\u003e 3);\nassert(2 === value($data, 'b', -1));\nassert(-1 === value($data, 'd', -1));\n```\n\n##### values($sequence)\n\nReturns list of the sequence values\n```php\nassert([1, 2, 3] === values(array('a' =\u003e 1, 'b' =\u003e 2, 'c' =\u003e 3)));\n```\n\n##### keys($sequence)\n\nReturns list of the sequence keys\n```php\nassert(['a', 'b', 'c'] === keys(array('a' =\u003e 1, 'b' =\u003e 2, 'c' =\u003e 3)));\n```\n\n##### in($item, $sequence)\n\nChecks if the item is present in array or traversable object\n```php\nassert(true === in(1, [1, 2, 3]);\n```\n\n##### diff($sequence1, $sequence2)\n\nComputes the difference of arrays or traversable objects\n```php\nassert([1] === diff([1, 2, 3], new ArrayObject([2, 3, 4]));\n```\n\n##### intersect($sequence1, $sequence2)\n\nComputes the intersection of arrays or traversable objects\n```php\nassert([2, 3] === intersect([1, 2, 3], new ArrayObject([2, 3, 4]));\n```\n\n##### cartesianProduct($sequences)\n\nComputes the cartesian product of two or more arrays or traversable objects\n```php\nassert([\n    [1, 'a'],\n    [1, 'b'],\n    [2, 'a'],\n    [2, 'b'],\n], cartesianProduct([1, 2], ['a', 'b']));\n```\n\n```php\nassert([\n    array('hello' =\u003e 1, 'world' =\u003e 'a'),\n    array('hello' =\u003e 1, 'world' =\u003e 'b'),\n    array('hello' =\u003e 2, 'world' =\u003e 'a'),\n    array('hello' =\u003e 2, 'world' =\u003e 'b'),\n], cartesianProduct(array('hello' =\u003e [1, 2], 'world' =\u003e ['a', 'b'])));\n```\n\n##### isList($var)\n\nReturns true if the variable is a list\n\n##### Chaining\n\nIt is possible to chain array function calls using the `with` function:\n\n```php\nuse function nspl\\op\\sum;\n\n$square = function($n) { return $n * $n; };\n\n$isEven = function($n) { return $n % 2 === 0; };\n\n$sum = with(range(1, 5))\n  -\u003efilter($isEven)\n  -\u003emap($square)\n  -\u003ereduce(sum);\n\nassert(20 === $sum);\n```\n\n##### Callbacks\n\n```nspl\\a``` provides all its functions as callbacks in its constants which have the same names as the functions.\n```php\nuse const \\nspl\\a\\first;\nassert([1, 2, 3] === map(first, [[1, 'a'], [2, 'b'], [3, 'c']]));\n```\n\nCheck more ```\\nspl\\a``` examples [here](https://github.com/ihor/Nspl/blob/master/examples/a.php).\n\n## nspl\\a\\lazy\nProvides lazy versions of functions from [nspl\\a](#nspla)\n\nThis module might be useful when you don't need to process all the values from an array or any other traversable sequence. To understand how these lazy functions work let's have a look at the following example.\n\nLet's define a function which wraps a generator function and logs all the values it yields. It will help up us to see the order of function calls:\n```php\n// Calls generator function and logs the yielded values\nfunction logged(callable $generatorFunction)\n{\n    static $count = 1;\n    return function(...$args) use ($generatorFunction, \u0026$count) {\n        foreach ($generatorFunction(...$args) as $value) {\n            echo $count++ . '. ' .  (string) $generatorFunction . ' -\u003e ' . $value . \"\\n\";\n            yield $value;\n        };\n    };\n}\n```\n\nTo have some data to operate on, let's define a function which returns all natural numbers. Since it returns all the natural numbers it never terminates:\n```php\nfunction naturalNumbers()\n{\n    $current = 1;\n    while (true) yield $current++;\n}\nconst naturalNumbers = 'naturalNumbers';\n```\n\nAlso, let's define the operations we want to perform on those numbers:\n```php\n// Returns square of a number\nfunction square($n)\n{\n    return $n * $n;\n}\nconst square = 'square';\n\n// Checks if a number is even\nfunction isEven($n)\n{\n    return $n % 2 === 0;\n}\nconst isEven = 'isEven';\n```\n\nNow let's assume we want to take the first three even natural numbers and calculate their squares:\n```php\nuse const nspl\\a\\lazy\\{take, map, filter};\n\n$map = logged(map);\n$take = logged(take);\n$filter = logged(filter);\n$numbers = logged(naturalNumbers)();\n\n$evenNumbers = $filter(isEven, $numbers); // filter only even numbers\n$firstThreeEvenNumbers = $take($evenNumbers, 3); // take only first 3 even numbers\n$result = $map(square, $firstThreeEvenNumbers); // and calculate their squares\n\nforeach ($result as $value) {\n    echo \"\\nNext value is $value \\n\\n\";\n}\n```\n\nWhen we run this example we'll see the following output:\n```\n1. naturalNumbers -\u003e 1\n2. naturalNumbers -\u003e 2\n3. \\nspl\\a\\lazy\\filter -\u003e 2\n4. \\nspl\\a\\lazy\\take -\u003e 2\n5. \\nspl\\a\\lazy\\map -\u003e 4\n\nNext value is 4\n\n6. naturalNumbers -\u003e 3\n7. naturalNumbers -\u003e 4\n8. \\nspl\\a\\lazy\\filter -\u003e 4\n9. \\nspl\\a\\lazy\\take -\u003e 4\n10. \\nspl\\a\\lazy\\map -\u003e 16\n\nNext value is 16\n\n11. naturalNumbers -\u003e 5\n12. naturalNumbers -\u003e 6\n13. \\nspl\\a\\lazy\\filter -\u003e 6\n14. \\nspl\\a\\lazy\\take -\u003e 6\n15. \\nspl\\a\\lazy\\map -\u003e 36\n\nNext value is 36\n```\n\nIf we used regular non-lazy versions of these functions, we would generate all the natural numbers, then filtered only even numbers, then took only the first three of them and then calculated their squares. Instead of that, you see that functions were called one by one passing the result to the next function until we completed the full cycle:\n 1. We took the first natural number – 1. It wasn't even, so we skipped it\n 2. We took the next one – 2, it was even\n 3. So it passed the ```filter``` function\n 4. It was the first number we took, so it passed through the ```take``` function as well\n 5. Then we calculated its square and printed the result\n\nThe same repeated on steps 6-10 and 11-15. On step 14 the ```take``` function took the last third number. So after step 15,  when ```map``` requested the next value ```take``` didn't yield anything, and the whole iteration was finished.\n\nCheck this example [here](https://github.com/ihor/Nspl/blob/master/examples/a_lazy.php).\n\nIt possible to rewrite the code above using [chaining](#chaining):\n```php\n$result = with(naturalNumbers())\n    -\u003efilter(isEven)\n    -\u003etake(3)\n    -\u003emap(square);\n```\n\n\u003e **Tip**\n\u003e\n\u003e Note that while functions from ```\\nspl\\a\\lazy``` allow you to avoid redundant computations, in case when you need to process all sequence values, functions from ```\\nspl\\a``` will do the job faster.\n\n## nspl\\args\n\nHelps to validate function arguments\n\n##### expects($constraints, $arg, $atPosition = null, $otherwiseThrow = '\\InvalidArgumentException')\n\nChecks that argument satisfies the required constraints otherwise throws the corresponding exception.\n\n```$constraints``` are callable(s) which return(s) true if the argument satisfies the requirements or it also might contain the required class name(s)\nIf ```$atPosition``` is null, then the position is calculated automatically comparing given argument to the actual arguments passed to the function\n```$otherwiseThrow``` defines exception which will be thrown if the given argument is invalid, it can be the exception class or exception object\n\n```php\nuse const \\nspl\\args\\int;\nuse const \\nspl\\args\\string;\nuse const \\nspl\\args\\arrayAccess;\nuse function \\nspl\\args\\expects;\n\nfunction nth($sequence, $n)\n{\n    expects([arrayAccess, string], $sequence);\n    expects(int, $n);\n\n    return $sequence[$n];\n}\n\nnth('hello world', 'blah');\n```\n\nOutputs:\n```\nInvalidArgumentException: Argument 2 passed to nth() must be integer, string 'blah' given in /path/to/example.php on line 17\n\nCall Stack:\n    0.0002     230304   1. {main}() /path/to/example.php:0\n    0.0023     556800   2. sqr() /path/to/example.php:17\n```\n\n##### expectsAll($constraints, array $args, array $atPositions = [], $otherwiseThrow = '\\InvalidArgumentException')\n\nChecks that all specified arguments satisfy the required constraints otherwise throws the corresponding exception.\n\n```php\nuse const \\nspl\\args\\numeric;\nuse function \\nspl\\args\\expects;\n\nfunction sum($x, $y)\n{\n    expectsAll(numeric, [$x, $y]);\n\n    return $x + $y;\n}\n```\n\n##### expectsOptional($constraints, $arg, $atPosition = null, $otherwiseThrow = '\\InvalidArgumentException')\n\nChecks that argument is null or satisfies the required constraints otherwise throws the corresponding exception.\n\n```php\nfunction splitBy($string, $separator = ' ', $limit = null)\n{\n    expectsAll(string, [$string, $separator]);\n    expectsOptional(int, $limit);\n\n    return explode($separator, $string, $limit);\n}\n```\n\n##### Predefined constraints\n\nThe module provides predefined constraints. Which can be one of the two types:\n- OR-constraints which are evaluated with ```or``` operator (e.g. ```expects([int, string], $arg)``` evaluates as ```$arg``` has to be an ```int``` or a ```string```)\n- AND-constraints which are evaluated with ```and``` operator (e.g. ```expects([string, longerThan(3), shorterThan(10)], $arg)``` evaluates as ```$arg``` has to be a string longer than 3 characters and shorter than 10 characters). If you want to evaluate several AND-constraints as they were OR-constraints you can use ```any``` constraint. If you want to evaluate several OR-constraints as they were AND-constraints you can use ```all``` constraint\n\nCallback                            | Explanation                                                            | Type\n------------------------------------|------------------------------------------------------------------------|----------\nbool                                | Checks that argument is a bool                                         | OR\nint                                 | Checks that argument is an int                                         | OR\nfloat                               | Checks that argument is a float                                        | OR\nnumeric                             | Checks that argument is numeric                                        | OR\nstring                              | Checks that argument is a string                                       | OR\narray_                              | Checks that argument is an array                                          | OR\nobject                              | Checks that argument is an object                                         | OR\ncallable_                           | Checks that argument is callable                                       | OR\narrayKey                            | Checks that argument can be an array key                               | OR\ntraversable                         | Checks that argument can be traversed with foreach                     | OR\narrayAccess                         | Checks that argument supports array index access                       | OR\nnonEmpty                            | Checks that argument is not empty                                      | AND\npositive                            | Checks that argument is positive (\u003e 0)                                 | AND\nnonNegative                         | Checks that argument is not negative (\u003e= 0)                            | AND\nnonZero                             | Checks that argument is not zero (!== 0)                               | AND\nany(constraint1, ..., constraintN)  | Checks constraints as if they were OR-constraints                      | AND\nall(constraint1, ..., constraintN)  | Checks constraints as if they were AND-constraints                     | AND\nnot(constraint1, ..., constraintN)  | Checks that argument does't satisfy all listed constraints             | AND\nvalues(value1, ..., valueN)         | Checks that argument is one of the specified values                    | AND\nlongerThan($threshold)              | Checks that string argument is longer than given threshold             | AND\nshorterThan($threshold)             | Checks that string argument is shorter than given threshold            | AND\nbiggerThan($threshold)              | Checks that number is bigger than given threshold                      | AND\nsmallerThan($threshold)             | Checks that number is smaller than given threshold                     | AND\nhasKey($key)                        | Checks that argument supports array index access and has given key     | AND\nhasKeys($key1, ..., $keyN)          | Checks that argument supports array index access and has given keys    | AND\nhasMethod($method)                  | Checks that argument is an object and has given method                 | AND\nhasMethods($method1, ..., $methodN) | Checks that argument is an object and has given methods                | AND\n\n\n```php\nfunction setUsername($username)\n{\n    expects([string, longerThan(3), shorterThan(10)], $username);\n    // ...\n}\n\nfunction setState($state)\n{\n    expects(values('running', 'idle', 'stopped'), $state);\n    // ...\n}\n```\n\nDuck-typing example:\n```php\nclass Service\n{\n    // ...\n    public function setCache($cache)\n    {\n        expects(withMethods('set', 'get'), $cache);\n        $this-\u003ecache = $cache;\n    }\n    // ....\n}\n```\n\n##### Custom constraints\n\nIt is possible to use custom constraints. Just define a new function which returns true when argument satisfies the constraint:\n```php\nfunction even($value)\n{\n    return is_int($value) \u0026\u0026 $value %2 === 0;\n}\n\nfunction half($number)\n{\n    expects('even', $number);\n    return $number / 2;\n}\n```\nor we can make it more convenient to use introducing a constant:\n```php\nconst even = 'even';\n\nfunction half($number)\n{\n    expects(even, $number);\n    return $number / 2;\n}\n\nhalf('pie');\n```\nOutputs:\n```\nInvalidArgumentException: Argument 1 passed to half() must be even, string 'pie' given in /path/to/example.php on line 25\n\nCall Stack:\n    0.0009     253640   1. {main}() /path/to/example.php:0\n    0.0123     673984   2. half() /path/to/example.php:25\n```\n\nIf you need to create a constraint which takes arguments, you must create a callable object which implements ```\\nspl\\args\\Constraint``` interface. It contains two methods:\n- ```__invoke($value)``` - returns true if the value satisfies the constraint\n- ```__toString()``` - returns text which will be used in the exception when the value doesn't satisfy the constraint. The text must contain a message which goes after \"must\" in the exception message.\n\n\n## nspl\\ds\n\nProvides non-standard data structures and methods to work with them\n\n##### DefaultArray\n\nArray with a default value for missing keys. If you pass a function as default value it will be called without arguments to provide a default value for the given key, this value will be inserted in the array for the key, and returned.\nUsing DefaultArray turns this code:\n```php\n$a = array();\nforeach([1, 2, 1, 1, 3, 3, 3] as $v) {\n    if (!isset($a[$v])) {\n        $a[$v] = 0;\n    }\n    ++$a[$v];\n}\n```\ninto this:\n```php\n$a = defaultarray(0);\nforeach([1, 2, 1, 1, 3, 3, 3] as $v) {\n    ++$a[$v];\n}\n```\n\n##### defaultarray($default, $data = array())\n\nReturns new DefaultArray\n\n##### Set\n\nAn array-like collection that contains no duplicate elements. It supports basic set operations which take other sets, arrays and traversable objects as arguments\n\n```php\n$set = set(1, 2);\n\n$set-\u003eadd('hello');\n$set[] = 'world';\n\n$set-\u003edelete('hello');\n\n$array = [1, 2, 3];\n$intersection = $set-\u003eintersection($array);\n\n$anotherSet = Set::fromArray([1, 2, 3]);\n$difference = $set-\u003edifference($anotherSet);\n\n$iterator = new \\ArrayIterator([1, 2, 3]);\n$union = $set-\u003eunion($iterator);\n\n$isSubset = $set-\u003eisSubset([1, 2, 'hello', 'world']);\n\n$isSuperset = $set-\u003eisSuperset([1, 2]);\n```\n\n##### set\n\nReturns new Set\n\nCheck more ```\\nspl\\ds``` examples [here](https://github.com/ihor/Nspl/blob/master/examples/ds.php).\n\n## nspl\\rnd\n\n##### randomString($length)\n\nReturns a random alpha-numeric string of the given length\n\n##### choice($sequence)\n\nReturns a random item from a non-empty sequence\n\n##### weightedChoice($weightPairs)\n\nReturns a random item from a non-empty sequence of items with associated weights presented as pairs (item, weight)\n\n```php\nuse function \\nspl\\rnd\\weightedChoice;\nuse function \\nspl\\a\\pairs;\n\n$nextPet = weightedChoice([['cat', 20], ['hamster', 30], ['dog', 50]]);\n$nextFavouriteColor = weightedChoice(pairs(array(\n    'red' =\u003e 0.2,\n    'green' =\u003e 0.3,\n    'blue' =\u003e 0.5,\n)));\n```\n\n##### sample($population, $length, $preserveKeys = false)\n\nReturns a k length list of unique items chosen from the population sequence\n\nCheck more ```\\nspl\\rnd``` examples [here](https://github.com/ihor/Nspl/blob/master/examples/rnd.php).\n\n## nspl\n\n##### getType($var)\n\nReturns the variable type or its class name if it is an object\n\n\nRoadmap\n=======\n\n- Rewrite library using the latest features from PHP 7.2\n- Move `nspl\\args` into a separate module\n\nContributing\n============\n\nThis project uses [semantic versioning](http://semver.org/) to tag releases. Please submit your pull requests to the latest release branch where the issue was introduced.\n\nFeedback\n========\n\nThere are no mailing lists or discussion groups yet. Please use GitHub issues and pull request or follow me on Twitter [@IhorBurlachenko](https://twitter.com/IhorBurlachenko)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fihor%2Fnspl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fihor%2Fnspl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fihor%2Fnspl/lists"}