# Yonaba/Moses

14171d2 Sep 16, 2018
6 contributors

### Users who have contributed to this file

3115 lines (2862 sloc) 88.6 KB
Aliased as `forEach`. -- @name each -- @param t a table -- @param f a function, prototyped as `f (v, k)` -- @see eachi function M.each(t, f) for index,value in pairs(t) do f(value, index) end end --- Iterates on integer key-value pairs, calling `f(v, k)` every step. -- Only applies to values located at integer keys. The table can be a sparse array. -- Iteration will start from the lowest integer key found to the highest one. --
Aliased as `forEachi`. -- @name eachi -- @param t a table -- @param f a function, prototyped as `f (v, k)` -- @see each function M.eachi(t, f) local lkeys = M.sort(M.select(M.keys(t), M.isInteger)) for k, key in ipairs(lkeys) do f(t[key], key) end end --- Collects values at given keys and return them wrapped in an array. -- @name at -- @param t a table -- @param ... A variable number of keys to collect values -- @return an array-list of values function M.at(t, ...) local values = {} for i, key in ipairs({...}) do values[#values+1] = t[key] end return values end --- Adjusts the value at a given key using a function or a value. In case `f` is a function, -- it should be prototyped `f(v)`. It does not mutate the given table, but rather -- returns a new array. In case the given `key` does not exist in `t`, it throws an error. -- @param t a table -- @param key a key -- @param f a function, prototyped as `f(v)` or a value function M.adjust(t, key, f) if (t[key] == nil) then error("key not existing in table") end local _t = M.clone(t) _t[key] = type(f) == 'function' and f(_t[key]) or f return _t end --- Counts occurrences of a given value in a table. Uses @{isEqual} to compare values. -- @name count -- @param t a table -- @param[opt] val a value to be searched in the table. If not given, the @{size} of the table will be returned -- @return the count of occurrences of the given value -- @see countf -- @see size function M.count(t, val) if val == nil then return M.size(t) end local count = 0 for k, v in pairs(t) do if M.isEqual(v, val) then count = count + 1 end end return count end --- Counts the number of values passing a predicate test. Same as @{count}, but uses an iterator. -- Returns the count for values passing the test `f (v, k)` -- @name countf -- @param t a table -- @param f an iterator function, prototyped as `f (v, k)` -- @return the count of values validating the predicate -- @see count -- @see size function M.countf(t, f) local count = 0 for k, v in pairs(t) do if f(v, k) then count = count + 1 end end return count end --- Checks if all values in a collection are equal. Uses an optional `comp` function which is used -- to compare values and defaults to @{isEqual} when not given. --
Aliased as `alleq`. -- @name allEqual -- @param t a table -- @param[opt] comp a comparison function. Defaults to `isEqual` -- @return `true` when all values in `t` are equal, `false` otherwise. -- @see isEqual function M.allEqual(t, comp) local k, pivot = next(t) for k, v in pairs(t) do if comp then if not comp(pivot, v) then return false end else if not M.isEqual(pivot, v) then return false end end end return true end --- Loops `n` times through a table. In case `n` is omitted, it will loop forever. -- In case `n` is lower or equal to 0, it returns an empty function. --
Aliased as `loop`. -- @name cycle -- @param t a table -- @param[opt] n the number of loops -- @return an iterator function yielding value-key pairs from the passed-in table. function M.cycle(t, n) n = n or 1 if n<=0 then return M.noop end local k, fk local i = 0 while true do return function() k = k and next(t,k) or next(t) fk = not fk and k or fk if n then i = (k==fk) and i+1 or i if i > n then return end end return t[k], k end end end --- Maps `f (v, k)` on value-key pairs, collects and returns the results. --
Aliased as `collect`. -- @name map -- @param t a table -- @param f an iterator function, prototyped as `f (v, k)` -- @return a table of results function M.map(t, f) local _t = {} for index,value in pairs(t) do local k, kv, v = index, f(value, index) _t[v and kv or k] = v or kv end return _t end --- Reduces a table, left-to-right. Folds the table from the first element to the last element -- to a single value, using a given iterator and an initial state. -- The iterator takes a state and a value and returns a new state. --
Aliased as `inject`, `foldl`. -- @name reduce -- @param t a table -- @param f an iterator function, prototyped as `f (state, value)` -- @param[opt] state an initial state of reduction. Defaults to the first value in the table. -- @return the final state of reduction -- @see best -- @see reduceRight -- @see reduceBy function M.reduce(t, f, state) for k,value in pairs(t) do if state == nil then state = value else state = f(state,value) end end return state end --- Returns the best value passing a selector function. Acts as a special case of -- @{reduce}, using the first value in `t` as an initial state. It thens folds the given table, -- testing each of its values `v` and selecting the value passing the call `f(state,v)` every time. -- @name best -- @param t a table -- @param f an iterator function, prototyped as `f (state, value)` -- @return the final state of reduction -- @see reduce -- @see reduceRight -- @see reduceBy function M.best(t, f) local _, state = next(t) for k,value in pairs(t) do if state == nil then state = value else state = f(state,value) and state or value end end return state end --- Reduces values in a table passing a given predicate. Folds the table left-to-right, considering -- only values validating a given predicate. -- @name reduceBy -- @param t a table -- @param f an iterator function, prototyped as `f (state, value)` -- @param pred a predicate function `pred (v, k)` to select values to be considered for reduction -- @param[opt] state an initial state of reduction. Defaults to the first value in the table of selected values. -- @param[optchain] ... optional args to be passed to `pred` -- @return the final state of reduction -- @see reduce -- @see best -- @see reduceRight function M.reduceBy(t, f, pred, state) return M.reduce(M.select(t, pred), f, state) end --- Reduces a table, right-to-left. Folds the table from the last element to the first element -- to single value, using a given iterator and an initial state. -- The iterator takes a state and a value, and returns a new state. --
Aliased as `injectr`, `foldr`. -- @name reduceRight -- @param t a table -- @param f an iterator function, prototyped as `f (state, value)` -- @param[opt] state an initial state of reduction. Defaults to the last value in the table. -- @return the final state of reduction -- @see reduce -- @see best -- @see reduceBy function M.reduceRight(t, f, state) return M.reduce(M.reverse(t),f,state) end --- Reduces a table while saving intermediate states. Folds the table left-to-right -- using a given iterator and an initial state. The iterator takes a state and a value, -- and returns a new state. The result is an array of intermediate states. --
Aliased as `mapr` -- @name mapReduce -- @param t a table -- @param f an iterator function, prototyped as `f (state, value)` -- @param[opt] state an initial state of reduction. Defaults to the first value in the table. -- @return an array of states -- @see mapReduceRight function M.mapReduce(t, f, state) local _t = {} for i,value in pairs(t) do _t[i] = not state and value or f(state,value) state = _t[i] end return _t end --- Reduces a table while saving intermediate states. Folds the table right-to-left -- using a given iterator and an initial state. The iterator takes a state and a value, -- and returns a new state. The result is an array of intermediate states. --
Aliased as `maprr` -- @name mapReduceRight -- @param t a table -- @param f an iterator function, prototyped as `f (state, value)` -- @param[opt] state an initial state of reduction. Defaults to the last value in the table. -- @return an array of states -- @see mapReduce function M.mapReduceRight(t, f, state) return M.mapReduce(M.reverse(t),f,state) end --- Performs a linear search for a value in a table. It does not work for nested tables. -- The given value can be a function prototyped as `f (v, value)` which should return true when -- any v in the table equals the value being searched. --
Aliased as `any`, `some`, `contains` -- @name include -- @param t a table -- @param value a value to search for -- @return a boolean : `true` when found, `false` otherwise -- @see detect function M.include(t, value) local _iter = (type(value) == 'function') and value or M.isEqual for k,v in pairs(t) do if _iter(v,value) then return true end end return false end --- Performs a linear search for a value in a table. Returns the key of the value if found. -- The given value can be a function prototyped as `f (v, value)` which should return true when -- any v in the table equals the value being searched. This function is similar to @{find}, -- which is mostly meant to work with array. -- @name detect -- @param t a table -- @param value a value to search for -- @return the key of the value when found or __nil__ -- @see include -- @see find function M.detect(t, value) local _iter = (type(value) == 'function') and value or M.isEqual for key,arg in pairs(t) do if _iter(arg,value) then return key end end end --- Returns all values having specified keys `props`. -- @name where -- @param t a table -- @param props a set of keys -- @return an array of values from the passed-in table -- @see findWhere function M.where(t, props) local r = M.select(t, function(v) for key in pairs(props) do if v[key] ~= props[key] then return false end end return true end) return #r > 0 and r or nil end --- Returns the first value having specified keys `props`. -- @name findWhere -- @param t a table -- @param props a set of keys -- @return a value from the passed-in table -- @see where function M.findWhere(t, props) local index = M.detect(t, function(v) for key in pairs(props) do if props[key] ~= v[key] then return false end end return true end) return index and t[index] end --- Selects and returns values passing an iterator test. --
Aliased as `filter`. -- @name select -- @param t a table -- @param f an iterator function, prototyped as `f (v, k)` -- @return the selected values -- @see reject function M.select(t, f) local _t = {} for index,value in pairs(t) do if f(value,index) then _t[#_t+1] = value end end return _t end --- Clones a table while dropping values passing an iterator test. --
Aliased as `discard` -- @name reject -- @param t a table -- @param f an iterator function, prototyped as `f (v, k)` -- @return the remaining values -- @see select function M.reject(t, f) local _t = {} for index,value in pairs (t) do if not f(value,index) then _t[#_t+1] = value end end return _t end --- Checks if all values in a table are passing an iterator test. --
Aliased as `every` -- @name all -- @param t a table -- @param f an iterator function, prototyped as `f (v, k)` -- @return `true` if all values passes the predicate, `false` otherwise function M.all(t, f) for index,value in pairs(t) do if not f(value,index) then return false end end return true end --- Invokes a method on each value in a table. -- @name invoke -- @param t a table -- @param method a function, prototyped as `f (v, k)` -- @return the result of the call `f (v, k)` -- @see pluck function M.invoke(t, method) return M.map(t, function(v, k) if (type(v) == 'table') then if v[method] then if M.isCallable(v[method]) then return v[method](v,k) else return v[method] end else if M.isCallable(method) then return method(v,k) end end elseif M.isCallable(method) then return method(v,k) end end) end --- Extracts values in a table having a given key. -- @name pluck -- @param t a table -- @param key a key, will be used to index in each value: `value[key]` -- @return an array of values having the given key function M.pluck(t, key) local _t = {} for k, v in pairs(t) do if v[key] then _t[#_t+1] = v[key] end end return _t end --- Returns the max value in a collection. If a `transform` function is passed, it will -- be used to evaluate values by which all objects will be sorted. -- @name max -- @param t a table -- @param[opt] transform a transformation function, prototyped as `transform (v, k)`, defaults to @{identity} -- @return the max value found -- @see min function M.max(t, transform) return extract(t, f_max, transform) end --- Returns the min value in a collection. If a `transform` function is passed, it will -- be used to evaluate values by which all objects will be sorted. -- @name min -- @param t a table -- @param[opt] transform a transformation function, prototyped as `transform (v, k)`, defaults to @{identity} -- @return the min value found -- @see max function M.min(t, transform) return extract(t, f_min, transform) end --- Checks if two tables are the same. It compares if both tables features the same values, -- but not necessarily at the same keys. -- @name same -- @param a a table -- @param b another table -- @return `true` or `false` function M.same(a, b) return M.all(a, function(v) return M.include(b,v) end) and M.all(b, function(v) return M.include(a,v) end) end --- Sorts a table, in-place. If a comparison function is given, it will be used to sort values. -- @name sort -- @param t a table -- @param[opt] comp a comparison function prototyped as `comp (a, b)`, defaults to < operator. -- @return the given table, sorted. -- @see sortBy function M.sort(t, comp) t_sort(t, comp) return t end --- Iterates on values with respect to key order. Keys are sorted using `comp` function -- which defaults to `math.min`. It returns upon each call a `key, value` pair. -- @name sortedk -- @param t a table -- @param[opt] comp a comparison function. Defaults to `<` operator -- @return an iterator function -- @see sortedv function M.sortedk(t, comp) local keys = M.keys(t) t_sort(keys, comp) local i = 0 return function () i = i + 1 return keys[i], t[keys[i]] end end --- Iterates on values with respect to values order. Values are sorted using `comp` function -- which defaults to `math.min`. It returns upon each call a `key, value` pair. -- @name sortedv -- @param t a table -- @param[opt] comp a comparison function. Defaults to `<` operator -- @return an iterator function -- @see sortedk function M.sortedv(t, comp) local keys = M.keys(t) comp = comp or f_min t_sort(keys, function(a,b) return comp(t[a],t[b]) end) local i = 0 return function () i = i + 1 return keys[i], t[keys[i]] end end --- Sorts a table in-place using a transform. Values are ranked in a custom order of the results of -- running `transform (v)` on all values. `transform` may also be a string name property sort by. -- `comp` is a comparison function. -- @name sortBy -- @param t a table -- @param[opt] transform a `transform` function to sort elements prototyped as `transform (v)`. Defaults to @{identity} -- @param[optchain] comp a comparison function, defaults to the `<` operator -- @return a new array of sorted values -- @see sort function M.sortBy(t, transform, comp) local f = transform or M.identity if (type(transform) == 'string') then f = function(t) return t[transform] end end comp = comp or f_min t_sort(t, function(a,b) return comp(f(a), f(b)) end) return t end --- Splits a table into subsets groups. -- @name groupBy -- @param t a table -- @param iter an iterator function, prototyped as `iter (v, k)` -- @return a table of subsets groups function M.groupBy(t, iter) local _t = {} for k,v in pairs(t) do local _key = iter(v,k) if _t[_key] then _t[_key][#_t[_key]+1] = v else _t[_key] = {v} end end return _t end --- Groups values in a collection and counts them. -- @name countBy -- @param t a table -- @param iter an iterator function, prototyped as `iter (v, k)` -- @return a table of subsets groups names paired with their count function M.countBy(t, iter) local stats = {} for i,v in pairs(t) do local key = iter(v,i) stats[key] = (stats[key] or 0)+1 end return stats end --- Counts the number of values in a collection. If being passed more than one argument -- it will return the count of all passed-in arguments. -- @name size -- @param[opt] ... Optional variable number of arguments -- @return a count -- @see count -- @see countf function M.size(...) local args = {...} local arg1 = args[1] return (type(arg1) == 'table') and count(args[1]) or count(args) end --- Checks if all the keys of `other` table exists in table `t`. It does not -- compares values. The test is not commutative, i.e table `t` may contains keys -- not existing in `other`. -- @name containsKeys -- @param t a table -- @param other another table -- @return `true` or `false` -- @see sameKeys function M.containsKeys(t, other) for key in pairs(other) do if not t[key] then return false end end return true end --- Checks if both given tables have the same keys. It does not compares values. -- @name sameKeys -- @param tA a table -- @param tB another table -- @return `true` or `false` -- @see containsKeys function M.sameKeys(tA, tB) for key in pairs(tA) do if not tB[key] then return false end end for key in pairs(tB) do if not tA[key] then return false end end return true end --- Array functions -- @section Array functions --- Samples `n` random values from an array. If `n` is not specified, returns a single element. -- It uses internally @{shuffle} to shuffle the array before sampling values. If `seed` is passed, -- it will be used for shuffling. -- @name sample -- @param array an array -- @param[opt] n a number of elements to be sampled. Defaults to 1. -- @param[optchain] seed an optional seed for shuffling -- @return an array of selected values -- @see sampleProb function M.sample(array, n, seed) n = n or 1 if n == 0 then return {} end if n == 1 then if seed then randomseed(seed) end return {array[random(1, #array)]} end return M.slice(M.shuffle(array, seed), 1, n) end --- Return elements from a sequence with a given probability. It considers each value independently. -- Providing a seed will result in deterministic sampling. Given the same seed it will return the same sample -- every time. -- @name sampleProb -- @param array an array -- @param prob a probability for each element in array to be selected -- @param[opt] seed an optional seed for deterministic sampling -- @return an array of selected values -- @see sample function M.sampleProb(array, prob, seed) if seed then randomseed(seed) end local t = {} for k, v in ipairs(array) do if random() < prob then t[#t+1] = v end end return t end --- Returns the n-top values satisfying a predicate. It takes a comparison function -- `comp` used to sort array values, and then picks the top n-values. It leaves the original array untouched. -- @name nsorted -- @param array an array -- @param[opt] n a number of values to retrieve. Defaults to 1. -- @param[optchain] comp a comparison function. Defaults to `<` operator. -- @return an array of top n values function M.nsorted(array, n, comp) comp = comp or f_min n = n or 1 local values, count = {}, 0 for k, v in M.sortedv(array, comp) do if count < n then count = count + 1 values[count] = v end end return values end --- Returns a shuffled copy of a given array. If a seed is provided, it will -- be used to init the built-in pseudo random number generator (using `math.randomseed`). -- @name shuffle -- @param array an array -- @param[opt] seed a seed -- @return a shuffled copy of the given array function M.shuffle(array, seed) if seed then randomseed(seed) end local _shuffled = {} for index, value in ipairs(array) do local randPos = floor(random()*index)+1 _shuffled[index] = _shuffled[randPos] _shuffled[randPos] = value end return _shuffled end --- Converts a list of arguments to an array. -- @name pack -- @param ... a list of arguments -- @return an array of all passed-in args function M.pack(...) return {...} end --- Looks for the first occurrence of a given value in an array. Returns the value index if found. -- Uses @{isEqual} to compare values. -- @name find -- @param array an array of values -- @param value a value to lookup for -- @param[opt] from the index from where the search will start. Defaults to 1. -- @return the index of the value if found in the array, `nil` otherwise. -- @see detect function M.find(array, value, from) for i = from or 1, #array do if M.isEqual(array[i], value) then return i end end end --- Returns an array where values are in reverse order. The passed-in array should not be sparse. -- @name reverse -- @param array an array -- @return a reversed array function M.reverse(array) local _array = {} for i = #array,1,-1 do _array[#_array+1] = array[i] end return _array end --- Replaces elements in a given array with a given value. In case `i` and `j` are given -- it will only replaces values at indexes between `[i,j]`. In case `j` is greater than the array -- size, it will append new values, increasing the array size. -- @name fill -- @param array an array -- @param value a value -- @param[opt] i the index from which to start replacing values. Defaults to 1. -- @param[optchain] j the index where to stop replacing values. Defaults to the array size. -- @return the original array with values changed function M.fill(array, value, i, j) j = j or M.size(array) for i = i or 1, j do array[i] = value end return array end --- Returns an array of `n` zeros. -- @name zeros -- @param n a number -- @return an array -- @see ones -- @see vector function M.zeros(n) return M.fill({}, 0, 1, n) end --- Returns an array of `n` 1's. -- @name ones -- @param n a number -- @return an array -- @see zeros -- @see vector function M.ones(n) return M.fill({}, 1, 1, n) end --- Returns an array of `n` times a given value. -- @name vector -- @param value a value -- @param n a number -- @return an array -- @see zeros -- @see ones function M.vector(value, n) return M.fill({}, value, 1, n) end --- Collects values from a given array. The passed-in array should not be sparse. -- This function collects values as long as they satisfy a given predicate and returns on the first falsy test. --
Aliased as `takeWhile` -- @name selectWhile -- @param array an array -- @param f an iterator function prototyped as `f (v, k)` -- @return a new table containing all values collected -- @see dropWhile function M.selectWhile(array, f) local t = {} for i,v in ipairs(array) do if f(v,i) then t[i] = v else break end end return t end --- Collects values from a given array. The passed-in array should not be sparse. -- This function collects values as long as they do not satisfy a given predicate and returns on the first truthy test. --
Aliased as `rejectWhile` -- @name dropWhile -- @param array an array -- @param f an iterator function prototyped as `f (v, k)` -- @return a new table containing all values collected -- @see selectWhile function M.dropWhile(array, f) local _i for i,v in ipairs(array) do if not f(v, i) then _i = i break end end if (_i == nil) then return {} end return M.rest(array,_i) end --- Returns the index at which a value should be inserted. This index is evaluated so -- that it maintains the sort. If a comparison function is passed, it will be used to sort -- values. -- @name sortedIndex -- @param array an array -- @param the value to be inserted -- @param[opt] comp an comparison function prototyped as `f (a, b)`, defaults to < operator. -- @param[optchain] sort whether or not the passed-in array should be sorted -- @return number the index at which the passed-in value should be inserted function M.sortedIndex(array, value, comp, sort) local _comp = comp or f_min if (sort == true) then t_sort(array,_comp) end for i = 1,#array do if not _comp(array[i],value) then return i end end return #array+1 end --- Returns the index of the first occurrence of value in an array. -- @name indexOf -- @param array an array -- @param value the value to search for -- @return the index of the passed-in value -- @see lastIndexOf function M.indexOf(array, value) for k = 1,#array do if array[k] == value then return k end end end --- Returns the index of the last occurrence of value in an array. -- @name lastIndexOf -- @param array an array -- @param value the value to search for -- @return the index of the last occurrence of the passed-in value or __nil__ -- @see indexOf function M.lastIndexOf(array, value) local key = M.indexOf(M.reverse(array),value) if key then return #array-key+1 end end --- Returns the first index at which a predicate returns true. -- @name findIndex -- @param array an array -- @param pred a predicate function prototyped as `pred (v, k)` -- @return the index found or __nil__ -- @see findLastIndex function M.findIndex(array, pred) for k = 1, #array do if pred(array[k],k) then return k end end end --- Returns the last index at which a predicate returns true. -- @name findLastIndex -- @param array an array -- @param pred a predicate function prototyped as `pred (k, v)` -- @return the index found or __nil__ -- @see findIndex function M.findLastIndex(array, pred) local key = M.findIndex(M.reverse(array),pred) if key then return #array-key+1 end end --- Adds all passed-in values at the top of an array. The last elements will bubble to the -- top of the given array. -- @name addTop -- @param array an array -- @param ... a variable number of arguments -- @return the passed-in array with new values added -- @see prepend -- @see push function M.addTop(array, ...) for k,v in ipairs({...}) do t_insert(array,1,v) end return array end --- Adds all passed-in values at the top of an array. As opposed to @{addTop}, it preserves the order -- of the passed-in elements. -- @name prepend -- @param array an array -- @param ... a variable number of arguments -- @return the passed-in array with new values added -- @see addTop -- @see push function M.prepend(array, ...) return M.append({...}, array) end --- Pushes all passed-in values at the end of an array. -- @name push -- @param array an array -- @param ... a variable number of arguments -- @return the passed-in array with new added values -- @see addTop -- @see prepend function M.push(array, ...) local args = {...} for k,v in ipairs({...}) do array[#array+1] = v end return array end --- Removes and returns the values at the top of a given array. --
Aliased as `pop` -- @name shift -- @param array an array -- @param[opt] n the number of values to be popped. Defaults to 1. -- @return the popped values -- @see unshift function M.shift(array, n) n = min(n or 1, #array) local ret = {} for i = 1, n do local retValue = array[1] ret[#ret + 1] = retValue t_remove(array,1) end return unpack(ret) end --- Removes and returns the values at the end of a given array. -- @name unshift -- @param array an array -- @param[opt] n the number of values to be unshifted. Defaults to 1. -- @return the values -- @see shift function M.unshift(array, n) n = min(n or 1, #array) local ret = {} for i = 1, n do local retValue = array[#array] ret[#ret + 1] = retValue t_remove(array) end return unpack(ret) end --- Removes all provided values in a given array. --
Aliased as `remove` -- @name pull -- @param array an array -- @param ... a variable number of values to be removed from the array -- @return the passed-in array with values removed function M.pull(array, ...) local values = {...} for i = #array, 1, -1 do local remval = false for k, rmValue in ipairs(values) do if (remval == false) then if M.isEqual(array[i], rmValue) then t_remove(array, i) remval = true end end end end return array end --- Removes values at an index within the range `[start, finish]`. --
Aliased as `rmRange`, `chop` -- @name removeRange -- @param array an array -- @param[opt] start the lower bound index, defaults to the first index in the array. -- @param[optchain] finish the upper bound index, defaults to the array length. -- @return the passed-in array with values removed function M.removeRange(array, start, finish) start = start or 1 finish = finish or #array if start > finish then error("start cannot be greater than finish.") end for i = finish, start, -1 do t_remove(array, i) end return array end --- Chunks together consecutive values. Values are chunked on the basis of the return -- value of a provided predicate `f (v, k)`. Consecutive elements which return -- the same value are chunked together. Leaves the first argument untouched if it is not an array. -- @name chunk -- @param array an array -- @param f an iterator function prototyped as `f (v, k)` -- @return a table of chunks (arrays) -- @see zip function M.chunk(array, f) local ch, ck, prev, val = {}, 0 for k,v in ipairs(array) do val = f(v, k) prev = (prev==nil) and val or prev ck = ((val~=prev) and (ck+1) or ck) if not ch[ck] then ch[ck] = {array[k]} else ch[ck][#ch[ck]+1] = array[k] end prev = val end return ch end --- Slices values indexed within `[start, finish]` range. --
Aliased as `M.sub` -- @name slice -- @param array an array -- @param[opt] start the lower bound index, defaults to the first index in the array. -- @param[optchain] finish the upper bound index, defaults to the array length. -- @return a new array of sliced values function M.slice(array, start, finish) local t = {} for k = start or 1, finish or #array do t[#t+1] = array[k] end return t end --- Returns the first N values in an array. --
Aliased as `head`, `take` -- @name first -- @param array an array -- @param[opt] n the number of values to be collected, defaults to 1. -- @return a new array -- @see initial -- @see last -- @see rest function M.first(array, n) n = n or 1 local t = {} for k = 1, n do t[k] = array[k] end return t end --- Returns all values in an array excluding the last N values. -- @name initial -- @param array an array -- @param[opt] n the number of values to be left, defaults to the array length. -- @return a new array -- @see first -- @see last -- @see rest function M.initial(array, n) local l = #array n = n and l-(min(n,l)) or l-1 local t = {} for k = 1, n do t[k] = array[k] end return t end --- Returns the last N values in an array. -- @name last -- @param array an array -- @param[opt] n the number of values to be collected, defaults to the array length. -- @return a new array -- @see first -- @see initial -- @see rest function M.last(array, n) local l = #array n = n and l-min(n-1,l-1) or 2 local t = {} for k = n, l do t[#t+1] = array[k] end return t end --- Returns all values after index. --
Aliased as `tail` -- @name rest -- @param array an array -- @param[opt] index an index, defaults to 1 -- @return a new array -- @see first -- @see initial -- @see last function M.rest(array, index) local t = {} for k = index or 1, #array do t[#t+1] = array[k] end return t end --- Returns the value at a given index. -- @name nth -- @param array an array -- @param index an index -- @return the value at the given index function M.nth(array, index) return array[index] end --- Returns all truthy values (removes `falses` and `nils`). -- @name compact -- @param array an array -- @return a new array function M.compact(array) local t = {} for k,v in pairs(array) do if v then t[#t+1] = v end end return t end --- Flattens a nested array. Passing `shallow` will only flatten at the first level. -- @name flatten -- @param array an array -- @param[opt] shallow specifies the flattening depth. Defaults to `false`.` -- @return a flattened array function M.flatten(array, shallow) shallow = shallow or false local new_flattened local _flat = {} for key,value in ipairs(array) do if type(value) == 'table' then new_flattened = shallow and value or M.flatten (value) for k,item in ipairs(new_flattened) do _flat[#_flat+1] = item end else _flat[#_flat+1] = value end end return _flat end --- Returns values from an array not present in all passed-in args. --
Aliased as `without` and `diff` -- @name difference -- @param array an array -- @param another array -- @return a new array -- @see union -- @see intersection -- @see symmetricDifference function M.difference(array, array2) if not array2 then return M.clone(array) end return M.select(array,function(value) return not M.include(array2,value) end) end --- Returns the duplicate-free union of all passed in arrays. -- @name union -- @param ... a variable number of arrays arguments -- @return a new array -- @see difference -- @see intersection -- @see symmetricDifference function M.union(...) return M.unique(M.flatten({...})) end --- Returns the intersection of all passed-in arrays. -- Each value in the result is present in each of the passed-in arrays. -- @name intersection -- @param ... a variable number of array arguments -- @return a new array -- @see difference -- @see union -- @see symmetricDifference function M.intersection(...) local arg = {...} local array = arg[1] t_remove(arg, 1) local _intersect = {} for i,value in ipairs(array) do if M.all(arg,function(v) return M.include(v,value) end) then _intersect[#_intersect+1] = value end end return _intersect end --- Checks if all passed in arrays are disjunct. -- @name disjoint -- @param ... a variable number of arrays -- @return `true` if the intersection of all arrays is not empty, `false` otherwise. -- @see intersection function M.disjoint(...) return (#M.intersection(...) == 0) end --- Performs a symmetric difference. Returns values from `array` not present in `array2` and also values -- from `array2` not present in `array`. --
Aliased as `symdiff` -- @name symmetricDifference -- @param array an array -- @param array2 another array -- @return a new array -- @see difference -- @see union -- @see intersection function M.symmetricDifference(array, array2) return M.difference( M.union(array, array2), M.intersection(array,array2) ) end --- Produces a duplicate-free version of a given array. --
Aliased as `uniq` -- @name unique -- @param array an array -- @return a new array, duplicate-free -- @see isunique -- @see duplicates function M.unique(array) local ret = {} for i = 1, #array do if not M.find(ret, array[i]) then ret[#ret+1] = array[i] end end return ret end --- Checks if a given array contains distinct values. Such an array is made of distinct elements, -- which only occur once in this array. --
Aliased as `isuniq` -- @name isunique -- @param array an array -- @return `true` if the given array is unique, `false` otherwise. -- @see unique -- @see duplicates function M.isunique(array) return #array == #(M.unique(array)) end --- Returns an array list of all duplicates in array. -- @name duplicates -- @param array an array -- @return an array-list of duplicates -- @see unique function M.duplicates(array) local dict = M.invert(array) local dups = {} for k, v in ipairs(array) do if dict[v] ~= k and not M.find(dups, v) then dups[#dups+1] = v end end return dups end --- Merges values of each of the passed-in arrays in subsets. -- Only values indexed with the same key in the given arrays are merged in the same subset. --
Aliased as `transpose` -- @name zip -- @param ... a variable number of array arguments -- @return a new array -- @see zipWith function M.zip(...) local args = {...} local n = M.max(args, function(array) return #array end) local _ans = {} for i = 1,n do if not _ans[i] then _ans[i] = {} end for k, array in ipairs(args) do if (array[i]~= nil) then _ans[i][#_ans[i]+1] = array[i] end end end return _ans end --- Merges values using a given function. -- Only values indexed with the same key in the given arrays are merged in the same subset. -- Function `f` is used to combine values. --
Aliased as `transposeWith` -- @name zipWith -- @param f a function -- @param ... a variable number of array arguments -- @return a flat array of results -- @see zip function M.zipWith(f, ...) local args = {...} local n = M.max(args, function(array) return #array end) local _ans = {} for i = 1,n do _ans[i] = f(unpack(M.pluck(args,i))) end return _ans end --- Clones array and appends values from another array. -- @name append -- @param array an array -- @param other an array -- @return a new array function M.append(array, other) local t = {} for i,v in ipairs(array) do t[i] = v end for i,v in ipairs(other) do t[#t+1] = v end return t end --- Interleaves arrays. It returns a single array made of values from all -- passed in arrays in their given order, interleaved. -- @name interleave -- @param ... a variable list of arrays -- @return a new array -- @see interpose function M.interleave(...) local args = {...} local n = M.max(args, M.size) local t = {} for i = 1, n do for k, array in ipairs(args) do if array[i] then t[#t+1] = array[i] end end end return t end --- Interposes value in-between consecutive pair of values in array. --
Aliased as `sliding` -- @name aperture -- @param array an array -- @param[opt] n the size of partitions. Defaults to 2 (and then behaves like @{pairwise}) -- @return an iterator function -- @see partition -- @see overlapping -- @see pairwise function M.aperture(array, n) if n<=1 then return end return wrap(function() partgen3(array, n or 2, yield) end) end --- Iterator returning sliding pairs of an array. -- @name pairwise -- @param array an array -- @return an iterator function -- @see overlapping function M.pairwise(array) return M.aperture(array, 2) end --- Iterator returning the permutations of an array. It returns arrays made of all values -- from the passed-in array, with values permuted. -- @name permutation -- @param array an array -- @return an iterator function function M.permutation(array) return wrap(function() permgen(array, #array, yield) end) end --- Concatenates values in a given array. Handles booleans as well. If `sep` string is -- passed, it will be used as a separator. Passing `i` and `j` will result in concatenating -- only values within `[i, j]` range. --
Aliased as `join` -- @name concat -- @param array a given array -- @param[opt] sep a separator string, defaults to the empty string `''`. -- @param[optchain] i the starting index, defaults to 1. -- @param[optchain] j the final index, defaults to the array length. -- @return a string function M.concat(array, sep, i, j) return t_concat(M.map(array,tostring),sep,i,j) end --- Returns all possible pairs built from given arrays. -- @name xprod -- @param array a first array -- @param array2 a second array -- @return an array list of all pairs function M.xprod(array, array2) local p = {} for i, v1 in ipairs(array) do for j, v2 in ipairs(array2) do p[#p+1] = {v1, v2} end end return p end --- Creates pairs from value and array. Value is always prepended to the pair. -- @name xpairs -- @param valua a value -- @param array an array -- @return an array list of all pairs function M.xpairs(value, array) local xpairs = {} for k, v in ipairs(array) do xpairs[k] = {value, v} end return xpairs end --- Creates pairs from value and array. Value is always appended as the last item to the pair. -- @name xpairsRight -- @param valua a value -- @param array an array -- @return an array list of all pairs function M.xpairsRight(value, array) local xpairs = {} for k, v in ipairs(array) do xpairs[k] = {v, value} end return xpairs end --- Returns the sum of array values. -- @name sum -- @param array a given array -- @return the sum of array values function M.sum(array) local s = 0 for k, v in ipairs(array) do s = s + v end return s end --- Returns the product of array values. -- @name product -- @param array a given array -- @return the product of array values function M.product(array) local p = 1 for k, v in ipairs(array) do p = p * v end return p end --- Returns the mean of an array of numbers. --
Aliased as `average` -- @name mean -- @param array an array of numbers -- @return a number -- @see sum -- @see product -- @see median function M.mean(array) return M.sum(array)/(#array) end --- Returns the median of an array of numbers. -- @name median -- @param array an array of numbers -- @return a number -- @see sum -- @see product -- @see mean function M.median(array) local t = M.sort(M.clone(array)) local n = #t if n == 0 then return elseif n==1 then return t[1] end local mid = ceil(n/2) return n%2==0 and (t[mid] + t[mid+1])/2 or t[mid] end --- Utility functions -- @section Utility functions --- The no operation function. -- @name noop -- @return nothing function M.noop() return end --- Returns the passed-in value. This function is used internally -- as a default iterator. -- @name identity -- @param value a value -- @return the passed-in value function M.identity(value) return value end --- Calls `f` with the supplied arguments. Returns the results of `f(...)`. -- @name call -- @param f a function -- @param[opt] ... a vararg list of args to `f` -- @return the result of `f(...)` call. function M.call(f, ...) return f(...) end --- Creates a constant function which returns the same output on every call. --
Aliased as `always` -- @name constant -- @param value a constant value -- @return a constant function function M.constant(value) return function() return value end end --- Returns a function which applies `specs` on args. This function produces an object having -- the same structure than `specs` by mapping each property to the result of calling its -- associated function with the supplied arguments -- @name applySpec -- @param specs a table -- @return a function function M.applySpec(specs) return function (...) local spec = {} for i, f in pairs(specs) do spec[i] = f(...) end return spec end end --- Threads `value` through a series of functions. If a function expects more than one args, -- it can be specified using an array list, where the first item is the function and the following -- are the remaining args neeeded. The value is used as the first input. -- @name thread -- @param value a value -- @param ... a vararg list of functions or arrays -- @return a value -- @see threadRight function M.thread(value, ...) local state = value local arg = {...} for k, t in ipairs(arg) do if type(t) == 'function' then state = t(state) elseif type(t) == 'table' then local f = t[1] t_remove(t, 1) state = M.reduce(t, f, state) end end return state end --- Threads `value` through a series of functions. If a function expects more than one args, -- it can be specified using an array list, where the first item is the function and the following -- are the remaining args neeeded. The value is used as the last input. -- @name threadRight -- @param value a value -- @param ... a vararg list of functions or arrays -- @return a value -- @see thread function M.threadRight(value, ...) local state = value local arg = {...} for k, t in ipairs(arg) do if type(t) == 'function' then state = t(state) elseif type(t) == 'table' then local f = t[1] t_remove(t, 1) t_insert(t, state) state = M.reduce(t, f) end end return state end --- Returns a dispatching function. When called with arguments, this function invokes each of its functions -- in the passed-in order and returns the results of the first non-nil evaluation. -- @name dispatch -- @param ... a vararg list of functions -- @return a dispatch function function M.dispatch(...) local funcs = {...} return function (...) for k, f in ipairs(funcs) do local r = {f(...)} if #r > 0 then return unpack(r) end end end end --- Memoizes a given function by caching the computed result. -- Useful for speeding-up slow-running functions. --
Aliased as `cache` -- @name memoize -- @param f a function -- @return a new function function M.memoize(f) local _cache = setmetatable({},{__mode = 'kv'}) return function (key) if (_cache[key] == nil) then _cache[key] = f(key) end return _cache[key] end end --- Builds a list from a seed value. Accepts an iterator function, which -- returns either nil to stop iteration or two values : the value to add to the list -- of results and the seed to be used in the next call to the iterator function. -- @name unfold -- @param f an iterator function -- @param seed a seed value -- @return an array of values function M.unfold(f, seed) local t, result = {} while true do result, seed = f(seed) if result ~= nil then t[#t+1] = result else break end end return t end --- Returns a version of `f` that runs only once. Successive calls to `f` -- will keep yielding the same output, no matter what the passed-in arguments are. -- It can be used to initialize variables. -- @name once -- @param f a function -- @return a new function -- @see before -- @see after function M.once(f) local _internal = 0 local _args = {} return function(...) _internal = _internal+1 if _internal <= 1 then _args = {...} end return f(unpack(_args)) end end --- Returns a version of `f` that will run no more than count times. Next calls will -- keep yielding the results of the count-th call. -- @name before -- @param f a function -- @param count a count -- @return a new function -- @see once -- @see after function M.before(f, count) local _internal = 0 local _args = {} return function(...) _internal = _internal+1 if _internal <= count then _args = {...} end return f(unpack(_args)) end end --- Returns a version of `f` that runs on the `count-th` call. -- Useful when dealing with asynchronous tasks. -- @name after -- @param f a function -- @param count the number of calls before `f` will start running. -- @return a new function -- @see once -- @see before function M.after(f, count) local _limit,_internal = count, 0 return function(...) _internal = _internal+1 if _internal >= _limit then return f(...) end end end --- Composes functions. Each passed-in function consumes the return value of the function that follows. -- In math terms, composing the functions `f`, `g`, and `h` produces the function `f(g(h(...)))`. -- @name compose -- @param ... a variable number of functions -- @return a new function -- @see pipe function M.compose(...) -- See: https://github.com/Yonaba/Moses/pull/15#issuecomment-139038895 local f = M.reverse {...} return function (...) local first, _temp = true for i, func in ipairs(f) do if first then first = false _temp = func(...) else _temp = func(_temp) end end return _temp end end --- Pipes a value through a series of functions. In math terms, -- given some functions `f`, `g`, and `h` in that order, it returns `f(g(h(value)))`. -- @name pipe -- @param value a value -- @param ... a variable number of functions -- @return the result of the composition of function calls. -- @see compose function M.pipe(value, ...) return M.compose(...)(value) end --- Returns the logical complement of a given function. For a given input, the returned -- function will output `false` if the original function would have returned `true`, -- and vice-versa. -- @name complement -- @param f a function -- @return the logical complement of the given function `f`. function M.complement(f) return function(...) return not f(...) end end --- Calls a sequence of passed-in functions with the same argument. -- Returns a sequence of results. --
Aliased as `juxt` -- @name juxtapose -- @param value a value -- @param ... a variable number of functions -- @return a list of results function M.juxtapose(value, ...) local res = {} for i, func in ipairs({...}) do res[i] = func(value) end return unpack(res) end --- Wraps `f` inside of the `wrapper` function. It passes `f` as the first argument to `wrapper`. -- This allows the wrapper to execute code before and after `f` runs, -- adjust the arguments, and execute it conditionally. -- @name wrap -- @param f a function to be wrapped, prototyped as `f (...)` -- @param wrapper a wrapper function, prototyped as `wrapper (f, ...)` -- @return the results function M.wrap(f, wrapper) return function (...) return wrapper(f,...) end end --- Runs `iter` function `n` times. Collects the results of each run and returns them in an array. -- @name times -- @param iter an iterator function, prototyped as `iter (i)` -- @param[opt] n the number of times `iter` should be called. Defaults to 1. -- @return table an array of results function M.times(iter, n) local results = {} for i = 1, (n or 1) do results[i] = iter(i) end return results end --- Binds `v` to be the first argument to `f`. Calling `f (...)` will result to `f (v, ...)`. -- @name bind -- @param f a function -- @param v a value -- @return a function -- @see bind2 -- @see bindn -- @see bindall function M.bind(f, v) return function (...) return f(v,...) end end --- Binds `v` to be the second argument to `f`. Calling `f (a, ...)` will result to `f (a, v, ...)`. -- @name bind2 -- @param f a function -- @param v a value -- @return a function -- @see bind -- @see bindn -- @see bindall function M.bind2(f, v) return function (t, ...) return f(t, v, ...) end end --- Binds `...` to be the N-first arguments to function `f`. -- Calling `f (a1, a2, ..., aN)` will result to `f (..., a1, a2, ...,aN)`. -- @name bindn -- @param f a function -- @param ... a variable number of arguments -- @return a function -- @see bind -- @see bind2 -- @see bindall function M.bindn(f, ...) local args = {...} return function (...) return f(unpack(M.append(args,{...}))) end end --- Binds methods to object. As such, whenever any of these methods is invoked, it -- always receives the object as its first argument. -- @name bindall -- @param obj an abject -- @param ... a variable number of method names -- @return the passed-in object with all methods bound to the object itself. -- @see bind -- @see bind2 -- @see bindn function M.bindall(obj, ...) local methodNames = {...} for i, methodName in ipairs(methodNames) do local method = obj[methodName] if method then obj[methodName] = M.bind(method, obj) end end return obj end --- Returns a function which iterate over a set of conditions. It invokes each predicate, -- passing it given values. It returns the value of the corresponding function of the first -- predicate to return a non-nil value. -- @name cond -- @param conds an array list of predicate-function pairs -- @return the result of invoking `f(...)` of the first predicate to return a non-nil value function M.cond(conds) return function(...) for k, condset in ipairs(conds) do if condset[1](...) then return condset[2](...) end end end end --- Returns a validation function. Given a set of functions, the validation function evaluates -- to `true` only when all its funcs returns `true`. -- @name both -- @param ... an array list of functions -- @return `true` when all given funcs returns true with input, false otherwise function M.both(...) local funcs = {...} return function (...) for k, f in ipairs(funcs) do if not f(...) then return false end end return true end end --- Returns a validation function. Given a set of functions, the validation function evaluates -- to `true` when at least one of its funcs returns `true`. -- @name either -- @param ... an array list of functions -- @return `true` when one of the given funcs returns `true` with input, `false` otherwise function M.either(...) local funcs = {...} return function (...) for k, f in ipairs(funcs) do if f(...) then return true end end return false end end --- Returns a validation function. Given a set of functions, the validation function evaluates -- to `true` when neither of its func return `true`. -- @name neither -- @param ... an array list of functions -- @return `true` when neither of the given funcs returns `true` with input, `false` otherwise function M.neither(...) local funcs = {...} return function (...) for k, f in ipairs(funcs) do if f(...) then return false end end return true end end --- Generates an unique ID for the current session. If given a string `template`, it -- will use this template for output formatting. Otherwise, if `template` is a function, it -- will evaluate `template (id)`. --
Aliased as `uid`. -- @name uniqueId -- @param[opt] template either a string or a function template to format the ID -- @return value an ID function M.uniqueId(template) unique_id_counter = unique_id_counter + 1 if template then if type(template) == 'string' then return template:format(unique_id_counter) elseif type(template) == 'function' then return template(unique_id_counter) end end return unique_id_counter end --- Produces an iterator which repeatedly apply a function `f` onto an input. -- Yields `value`, then `f(value)`, then `f(f(value))`, continuously. --
Aliased as `iter`. -- @name iterator -- @param f a function -- @param value an initial input to `f` -- @param[opt] n the number of times the iterator should run -- @return an iterator function function M.iterator(f, value, n) local cnt = 0 return function() cnt = cnt + 1 if n and cnt > n then return end value = f(value) return value end end --- Consumes the first `n` values of a iterator then returns it. -- @name skip -- @param iter an iterator function -- @param[opt] n a number. Defaults to 1. -- @return the given iterator function M.skip(iter, n) for i = 1, (n or 1) do if iter() == nil then return end end return iter end --- Iterates over an iterator and returns its values in an array. -- @name tabulate -- @param ... an iterator function (returning a generator, a state and a value) -- @return an array of results function M.tabulate(...) local r = {} for v in ... do r[#r+1] = v end return r end --- Returns the length of an iterator. It consumes the iterator itself. -- @name iterlen -- @param ... an iterator function (returning a generator, a state and a value) -- @return the iterator length function M.iterlen(...) local l = 0 for v in ... do l = l + 1 end return l end --- Casts value as an array if it is not one. -- @name castArray -- @param value a value -- @return an array containing the given value function M.castArray(value) return (type(value)~='table') and {value} or value end --- Creates a function of `f` with arguments flipped in reverse order. -- @name flip -- @param f a function -- @return a function function M.flip(f) return function(...) return f(unpack(M.reverse({...}))) end end --- Returns a function that gets the nth argument. -- If n is negative, the nth argument from the end is returned. -- @name nthArg -- @param n a number -- @return a function function M.nthArg(n) return function (...) local args = {...} return args[(n < 0) and (#args + n + 1) or n] end end --- Returns a function which accepts up to one arg. It ignores any additional arguments. -- @name unary -- @param f a function -- @return a function -- @see ary function M.unary(f) return function (...) local args = {...} return f(args[1]) end end --- Returns a function which accepts up to `n` args. It ignores any additional arguments. --
Aliased as `nAry`. -- @name ary -- @param f a function -- @param[opt] n a number. Defaults to 1. -- @return a function -- @see unary function M.ary(f, n) n = n or 1 return function (...) local args = {...} local fargs = {} for i = 1, n do fargs[i] = args[i] end return f(unpack(fargs)) end end --- Returns a function with an arity of 0. The new function ignores any arguments passed to it. -- @name noarg -- @param f a function -- @return a new function function M.noarg(f) return function () return f() end end --- Returns a function which runs with arguments rearranged. Arguments are passed to the -- returned function in the order of supplied `indexes` at call-time. -- @name rearg -- @param f a function -- @param indexes an array list of indexes -- @return a function function M.rearg(f, indexes) return function(...) local args = {...} local reargs = {} for i, arg in ipairs(indexes) do reargs[i] = args[arg] end return f(unpack(reargs)) end end --- Creates a function that runs transforms on all arguments it receives. -- @name over -- @param ... a set of functions which will receive all arguments to the returned function -- @return a function -- @see overEvery -- @see overSome -- @see overArgs function M.over(...) local transforms = {...} return function(...) local r = {} for i,transform in ipairs(transforms) do r[#r+1] = transform(...) end return r end end --- Creates a validation function. The returned function checks if *all* of the given predicates return -- truthy when invoked with the arguments it receives. -- @name overEvery -- @param ... a list of predicate functions -- @return a new function -- @see over -- @see overSome -- @see overArgs function M.overEvery(...) local f = M.over(...) return function(...) return M.reduce(f(...),function(state,v) return state and v end) end end --- Creates a validation function. The return function checks if *any* of a given predicates return -- truthy when invoked with the arguments it receives. -- @name overSome -- @param ... a list of predicate functions -- @return a new function -- @see over -- @see overEvery -- @see overArgs function M.overSome(...) local f = M.over(...) return function(...) return M.reduce(f(...),function(state,v) return state or v end) end end --- Creates a function that invokes `f` with its arguments transformed. 1rst arguments will be passed to -- the 1rst transform, 2nd arg to the 2nd transform, etc. Remaining arguments will not be transformed. -- @name overArgs -- @param f a function -- @param ... a list of transforms funcs prototyped as `f (v)` -- @return the result of running `f` with its transformed arguments -- @see over -- @see overEvery -- @see overSome function M.overArgs(f,...) local _argf = {...} return function(...) local _args = {...} for i = 1,#_argf do local func = _argf[i] if _args[i] then _args[i] = func(_args[i]) end end return f(unpack(_args)) end end --- Converges two functions into one. -- @name converge -- @param f a function -- @param g a function -- @param h a function -- @return a new version of function f function M.converge(f, g, h) return function(...) return f(g(...),h(...)) end end --- Partially apply a function by filling in any number of its arguments. -- One may pass a string `'M'` as a placeholder in the list of arguments to specify an argument -- that should not be pre-filled, but left open to be supplied at call-time. -- @name partial -- @param f a function -- @param ... a list of partial arguments to `f` -- @return a new version of function f having some of it original arguments filled -- @see partialRight -- @see curry function M.partial(f,...) local partial_args = {...} return function (...) local n_args = {...} local f_args = {} for k,v in ipairs(partial_args) do f_args[k] = (v == '_') and M.shift(n_args) or v end return f(unpack(M.append(f_args,n_args))) end end --- Similar to @{partial}, but from the right. -- @name partialRight -- @param f a function -- @param ... a list of partial arguments to `f` -- @return a new version of function f having some of it original arguments filled -- @see partialRight -- @see curry function M.partialRight(f,...) local partial_args = {...} return function (...) local n_args = {...} local f_args = {} for k = 1,#partial_args do f_args[k] = (partial_args[k] == '_') and M.shift(n_args) or partial_args[k] end return f(unpack(M.append(n_args, f_args))) end end --- Curries a function. If the given function `f` takes multiple arguments, it returns another version of -- `f` that takes a single argument (the first of the arguments to the original function) and returns a new -- function that takes the remainder of the arguments and returns the result. -- @name curry -- @param f a function -- @param[opt] n_args the number of arguments expected for `f`. Defaults to 2. -- @return a curried version of `f` -- @see partial -- @see partialRight function M.curry(f, n_args) n_args = n_args or 2 local _args = {} local function scurry(v) if n_args == 1 then return f(v) end if v ~= nil then _args[#_args+1] = v end if #_args < n_args then return scurry else local r = {f(unpack(_args))} _args = {} return unpack(r) end end return scurry end --- Returns the execution time of `f (...)` and its returned values. -- @name time -- @param f a function -- @param[opt] ... optional args to `f` -- @return the execution time and the results of `f (...)` function M.time(f, ...) local stime = clock() local r = {f(...)} return clock() - stime, unpack(r) end --- Object functions -- @section Object functions --- Returns the keys of the object properties. -- @name keys -- @param obj an object -- @return an array function M.keys(obj) local keys = {} for key in pairs(obj) do keys[#keys+1] = key end return keys end --- Returns the values of the object properties. -- @name values -- @param obj an object -- @return an array of values function M.values(obj) local values = {} for key, value in pairs(obj) do values[#values+1] = value end return values end --- Returns the value at a given path in an object. -- Path is given as a vararg list of keys. -- @name path -- @param obj an object -- @param ... a vararg list of keys -- @return a value or nil function M.path(obj, ...) local value, path = obj, {...} for i, p in ipairs(path) do if (value[p] == nil) then return end value = value[p] end return value end --- Spreads object under property path onto provided object. -- It is similar to @{flattenPath}, but removes object under the property path. -- @name spreadPath -- @param obj an object -- @param ... a property path given as a vararg list -- @return the passed-in object with changes -- @see flattenPath function M.spreadPath(obj, ...) local path = {...} for _, p in ipairs(path) do if obj[p] then for k, v in pairs(obj[p]) do obj[k] = v obj[p][k] = nil end end end return obj end --- Flattens object under property path onto provided object. -- It is similar to @{spreadPath}, but preserves object under the property path. -- @name flattenPath -- @param obj an object -- @param ... a property path given as a vararg list -- @return the passed-in object with changes -- @see spreadPath function M.flattenPath(obj, ...) local path = {...} for _, p in ipairs(path) do if obj[p] then for k, v in pairs(obj[p]) do obj[k] = v end end end return obj end --- Converts key-value pairs to an array-list of `[k, v]` pairs. -- @name kvpairs -- @param obj an object -- @return an array list of key-value pairs -- @see toObj function M.kvpairs(obj) local t = {} for k,v in pairs(obj) do t[#t+1] = {k,v} end return t end --- Converts an array list of `[k,v]` pairs to an object. Keys are taken -- from the 1rst column in the `[k,v]` pairs sequence, associated with values in the 2nd -- column. -- @name toObj -- @param kvpairs an array-list of `[k,v]` pairs -- @return an object -- @see kvpairs function M.toObj(kvpairs) local obj = {} for k, v in ipairs(kvpairs) do obj[v[1]] = v[2] end return obj end --- Swaps keys with values. Produces a new object where previous keys are now values, -- while previous values are now keys. --
Aliased as `mirror` -- @name invert -- @param obj a given object -- @return a new object function M.invert(obj) local _ret = {} for k, v in pairs(obj) do _ret[v] = k end return _ret end --- Returns a function that will return the key property of any passed-in object. -- @name property -- @param key a key property name -- @return a function which should accept an object as argument -- @see propertyOf function M.property(key) return function(obj) return obj[key] end end --- Returns a function which will return the value of an object property. -- @name propertyOf -- @param obj an object -- @return a function which should accept a key property argument -- @see property function M.propertyOf(obj) return function(key) return obj[key] end end --- Converts any given value to a boolean -- @name toBoolean -- @param value a value. Can be of any type -- @return `true` if value is true, `false` otherwise (false or nil). function M.toBoolean(value) return not not value end --- Extends an object properties. It copies the properties of extra passed-in objects -- into the destination object, and returns the destination object. The last objects -- will override properties of the same name. -- @name extend -- @param destObj a destination object -- @param ... a list of objects -- @return the destination object extended function M.extend(destObj, ...) local sources = {...} for k, source in ipairs(sources) do if type(source) == 'table' then for key, value in pairs(source) do destObj[key] = value end end end return destObj end --- Returns a sorted list of all methods names found in an object. If the given object -- has a metatable implementing an `__index` field pointing to another table, will also recurse on this -- table if `recurseMt` is provided. If `obj` is omitted, it defaults to the library functions. --
Aliased as `methods`. -- @name functions -- @param[opt] obj an object. Defaults to Moses library functions. -- @return an array-list of methods names function M.functions(obj, recurseMt) obj = obj or M local _methods = {} for key, value in pairs(obj) do if type(value) == 'function' then _methods[#_methods+1] = key end end if recurseMt then local mt = getmetatable(obj) if mt and mt.__index then local mt_methods = M.functions(mt.__index, recurseMt) for k, fn in ipairs(mt_methods) do _methods[#_methods+1] = fn end end end return _methods end --- Clones a given object properties. If `shallow` is passed will also clone nested array properties. -- @name clone -- @param obj an object -- @param[opt] shallow whether or not nested array-properties should be cloned, defaults to false. -- @return a copy of the passed-in object function M.clone(obj, shallow) if type(obj) ~= 'table' then return obj end local _obj = {} for i,v in pairs(obj) do if type(v) == 'table' then if not shallow then _obj[i] = M.clone(v,shallow) else _obj[i] = v end else _obj[i] = v end end return _obj end --- Invokes interceptor with the object, and then returns object. -- The primary purpose of this method is to "tap into" a method chain, in order to perform operations -- on intermediate results within the chain. -- @name tap -- @param obj an object -- @param f an interceptor function, should be prototyped as `f (obj)` -- @return the passed-in object function M.tap(obj, f) f(obj) return obj end --- Checks if a given object implements a property. -- @name has -- @param obj an object -- @param key a key property to be checked -- @return `true` or `false` function M.has(obj, key) return obj[key]~=nil end --- Returns an object copy having white-listed properties. --
Aliased as `choose`. -- @name pick -- @param obj an object -- @param ... a variable number of string keys -- @return the filtered object function M.pick(obj, ...) local whitelist = M.flatten {...} local _picked = {} for key, property in pairs(whitelist) do if (obj[property])~=nil then _picked[property] = obj[property] end end return _picked end --- Returns an object copy without black-listed properties. --
Aliased as `drop`. -- @name omit -- @param obj an object -- @param ... a variable number of string keys -- @return the filtered object function M.omit(obj, ...) local blacklist = M.flatten {...} local _picked = {} for key, value in pairs(obj) do if not M.include(blacklist,key) then _picked[key] = value end end return _picked end --- Applies a template to an object, preserving non-nil properties. --
Aliased as `defaults`. -- @name template -- @param obj an object -- @param[opt] template a template object. If `nil`, leaves `obj` untouched. -- @return the passed-in object filled function M.template(obj, template) if not template then return obj end for i, v in pairs(template) do if not obj[i] then obj[i] = v end end return obj end --- Performs a deep comparison test between two objects. Can compare strings, functions -- (by reference), nil, booleans. Compares tables by reference or by values. If `useMt` -- is passed, the equality operator `==` will be used if one of the given objects has a -- metatable implementing `__eq`. --
Aliased as `M.compare`, `M.matches` -- @name isEqual -- @param objA an object -- @param objB another object -- @param[opt] useMt whether or not `__eq` should be used, defaults to false. -- @return `true` or `false` -- @see allEqual function M.isEqual(objA, objB, useMt) local typeObjA = type(objA) local typeObjB = type(objB) if typeObjA~=typeObjB then return false end if typeObjA~='table' then return (objA==objB) end local mtA = getmetatable(objA) local mtB = getmetatable(objB) if useMt then if (mtA or mtB) and (mtA.__eq or mtB.__eq) then return mtA.__eq(objA, objB) or mtB.__eq(objB, objA) or (objA==objB) end end if M.size(objA)~=M.size(objB) then return false end local vB for i,vA in pairs(objA) do vB = objB[i] if vB == nil or not M.isEqual(vA, vB, useMt) then return false end end for i in pairs(objB) do if objA[i] == nil then return false end end return true end --- Invokes an object method. It passes the object itself as the first argument. if `method` is not -- callable, will return `obj[method]`. -- @name result -- @param obj an object -- @param method a string key to index in object `obj`. -- @return the returned value of `method (obj)` call function M.result(obj, method) if obj[method] then if M.isCallable(obj[method]) then return obj[method](obj) else return obj[method] end end if M.isCallable(method) then return method(obj) end end --- Checks if the given arg is a table. -- @name isTable -- @param t a value to be tested -- @return `true` or `false` function M.isTable(t) return type(t) == 'table' end --- Checks if the given argument is callable. Assumes `obj` is callable if -- it is either a function or a table having a metatable implementing `__call` metamethod. -- @name isCallable -- @param obj an object -- @return `true` or `false` function M.isCallable(obj) return ((type(obj) == 'function') or ((type(obj) == 'table') and getmetatable(obj) and getmetatable(obj).__call~=nil) or false) end --- Checks if the given argument is an array. Assumes `obj` is an array -- if is a table with consecutive integer keys starting at 1. -- @name isArray -- @param obj an object -- @return `true` or `false` function M.isArray(obj) if not (type(obj) == 'table') then return false end -- Thanks @Wojak and @Enrique García Cota for suggesting this -- See : http://love2d.org/forums/viewtopic.php?f=3&t=77255&start=40#p163624 local i = 0 for k in pairs(obj) do i = i + 1 if obj[i] == nil then return false end end return true end --- Checks if the given object is iterable with `pairs` (or `ipairs`). -- @name isIterable -- @param obj an object -- @return `true` if the object can be iterated with `pairs` (or `ipairs`), `false` otherwise function M.isIterable(obj) return M.toBoolean((pcall(pairs, obj))) end --- Extends Lua's `type` function. It returns the type of the given object and also recognises -- file userdata -- @name type -- @param obj an object -- @return the given object type function M.type(obj) local tp = type(obj) if tp == 'userdata' then local mt = getmetatable(obj) if mt == getmetatable(io.stdout) then return 'file' end end return tp end --- Checks if the given pbject is empty. If `obj` is a string, will return `true` -- if `#obj == 0`. Otherwise, if `obj` is a table, will return whether or not this table -- is empty. If `obj` is `nil`, it will return true. -- @name isEmpty -- @param[opt] obj an object -- @return `true` or `false` function M.isEmpty(obj) if (obj == nil) then return true end if type(obj) == 'string' then return #obj==0 end if type(obj) == 'table' then return next(obj)==nil end return true end --- Checks if the given argument is a string. -- @name isString -- @param obj an object -- @return `true` or `false` function M.isString(obj) return type(obj) == 'string' end --- Checks if the given argument is a function. -- @name isFunction -- @param obj an object -- @return `true` or `false` function M.isFunction(obj) return type(obj) == 'function' end --- Checks if the given argument is nil. -- @name isNil -- @param obj an object -- @return `true` or `false` function M.isNil(obj) return obj==nil end --- Checks if the given argument is a number. -- @name isNumber -- @param obj an object -- @return `true` or `false` -- @see isNaN function M.isNumber(obj) return type(obj) == 'number' end --- Checks if the given argument is NaN (see [Not-A-Number](http://en.wikipedia.org/wiki/NaN)). -- @name isNaN -- @param obj an object -- @return `true` or `false` -- @see isNumber function M.isNaN(obj) return type(obj) == 'number' and obj~=obj end --- Checks if the given argument is a finite number. -- @name isFinite -- @param obj an object -- @return `true` or `false` function M.isFinite(obj) if type(obj) ~= 'number' then return false end return obj > -huge and obj < huge end --- Checks if the given argument is a boolean. -- @name isBoolean -- @param obj an object -- @return `true` or `false` function M.isBoolean(obj) return type(obj) == 'boolean' end --- Checks if the given argument is an integer. -- @name isInteger -- @param obj an object -- @return `true` or `false` function M.isInteger(obj) return type(obj) == 'number' and floor(obj)==obj end -- Aliases do -- Table functions aliases M.forEach = M.each M.forEachi = M.eachi M.update = M.adjust M.alleq = M.allEqual M.loop = M.cycle M.collect = M.map M.inject = M.reduce M.foldl = M.reduce M.injectr = M.reduceRight M.foldr = M.reduceRight M.mapr = M.mapReduce M.maprr = M.mapReduceRight M.any = M.include M.some = M.include M.contains = M.include M.filter = M.select M.discard = M.reject M.every = M.all -- Array functions aliases M.takeWhile = M.selectWhile M.rejectWhile = M.dropWhile M.pop = M.shift M.remove = M.pull M.rmRange = M.removeRange M.chop = M.removeRange M.sub = M.slice M.head = M.first M.take = M.first M.tail = M.rest M.without = M.difference M.diff = M.difference M.symdiff = M.symmetricDifference M.xor = M.symmetricDifference M.uniq = M.unique M.isuniq = M.isunique M.transpose = M.zip M.part = M.partition M.perm = M.permutation M.transposeWith = M.zipWith M.intersperse = M.interpose M.sliding = M.aperture M.mirror = M.invert M.join = M.concat M.average = M.mean -- Utility functions aliases M.always = M.constant M.cache = M.memoize M.juxt = M.juxtapose M.uid = M.uniqueid M.iter = M.iterator M.nAry = M.ary -- Object functions aliases M.methods = M.functions M.choose = M.pick M.drop = M.omit M.defaults = M.template M.compare = M.isEqual M.matches = M.isEqual end -- Setting chaining and building interface do -- Wrapper to Moses local f = {} -- Will be returned upon requiring, indexes into the wrapper local Moses = {} Moses.__index = f -- Wraps a value into an instance, and returns the wrapped object local function new(value) return setmetatable({_value = value, _wrapped = true}, Moses) end setmetatable(Moses,{ __call = function(self,v) return new(v) end, -- Calls returns to instantiation __index = function(t,key,...) return f[key] end -- Redirects to the wrapper }) --- Returns a wrapped object. Calling library functions as methods on this object -- will continue to return wrapped objects until @{obj:value} is used. Can be aliased as `M(value)`. -- @class function -- @name chain -- @param value a value to be wrapped -- @return a wrapped object function Moses.chain(value) return new(value) end --- Extracts the value of a wrapped object. Must be called on an chained object (see @{chain}). -- @class function -- @name obj:value -- @return the value previously wrapped function Moses:value() return self._value end -- Register chaining methods into the wrapper f.chain, f.value = Moses.chain, Moses.value -- Register all functions into the wrapper for fname,fct in pairs(M) do if fname ~= 'operator' then -- Prevents from wrapping op functions f[fname] = function(v, ...) local wrapped = type(v) == 'table' and rawget(v,'_wrapped') or false if wrapped then local _arg = v._value local _rslt = fct(_arg,...) return new(_rslt) else return fct(v,...) end end end end -- Exports all op functions f.operator = M.operator f.op = M.operator --- Imports all library functions into a context. -- @name import -- @param[opt] context a context. Defaults to `_ENV or `_G`` (current environment). -- @param[optchain] noConflict if supplied, will not import conflicting functions in the destination context. -- @return the passed-in context f.import = function(context, noConflict) context = context or _ENV or _G local funcs = M.functions() for k, fname in ipairs(funcs) do if rawget(context, fname)~= nil then if not noConflict then rawset(context, fname, M[fname]) end else rawset(context, fname, M[fname]) end end return context end -- Descriptive tags Moses._VERSION = 'Moses v'.._MODULEVERSION Moses._URL = 'http://github.com/Yonaba/Moses' Moses._LICENSE = 'MIT ' Moses._DESCRIPTION = 'utility-belt library for functional programming in Lua' return Moses end