3117 lines
89 KiB
Lua
3117 lines
89 KiB
Lua
--- Utility-belt library for functional programming in Lua ([source](http://github.com/Yonaba/Moses))
|
|
-- @author [Roland Yonaba](http://github.com/Yonaba)
|
|
-- @copyright 2012-2018
|
|
-- @license [MIT](http://www.opensource.org/licenses/mit-license.php)
|
|
-- @release 2.1.0
|
|
-- @module moses
|
|
-- @set sort=true
|
|
|
|
local _MODULEVERSION = '2.1.0'
|
|
|
|
-- Internalisation
|
|
local next, type, pcall = next, type, pcall
|
|
local setmetatable, getmetatable = setmetatable, getmetatable
|
|
local t_insert, t_sort = table.insert, table.sort
|
|
local t_remove,t_concat = table.remove, table.concat
|
|
local randomseed, random, huge = math.randomseed, math.random, math.huge
|
|
local floor, max, min, ceil = math.floor, math.max, math.min, math.ceil
|
|
local wrap = coroutine.wrap
|
|
local yield = coroutine.yield
|
|
local rawget = rawget
|
|
local unpack = table.unpack or unpack
|
|
local pairs,ipairs = pairs,ipairs
|
|
local error = error
|
|
local clock = os.clock
|
|
local M = {}
|
|
|
|
|
|
-- ======== Private helpers
|
|
|
|
local function f_max(a,b) return a>b end
|
|
local function f_min(a,b) return a<b end
|
|
|
|
local function count(t) -- raw count of items in an map-table
|
|
local i = 0
|
|
for k,v in pairs(t) do i = i + 1 end
|
|
return i
|
|
end
|
|
|
|
local function extract(list,comp,transform,...) -- extracts value from a list
|
|
transform = transform or M.identity
|
|
local _ans
|
|
for k,v in pairs(list) do
|
|
if not _ans then _ans = transform(v,...)
|
|
else
|
|
local val = transform(v,...)
|
|
_ans = comp(_ans,val) and _ans or val
|
|
end
|
|
end
|
|
return _ans
|
|
end
|
|
|
|
local function partgen(t, n, f, pad) -- generates array partitions
|
|
for i = 0, #t, n do
|
|
local s = M.slice(t, i+1, i+n)
|
|
if #s>0 then
|
|
while (#s < n and pad) do s[#s+1] = pad end
|
|
f(s)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function partgen2(t, n, f, pad) -- generates overlapping array partitions
|
|
for i = 0, #t, n-1 do
|
|
local s = M.slice(t, i+1, i+n)
|
|
if #s>0 and i+1<#t then
|
|
while (#s < n and pad) do s[#s+1] = pad end
|
|
f(s)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function partgen3(t, n, f, pad) -- generates sliding array partitions
|
|
for i = 0, #t, 1 do
|
|
local s = M.slice(t, i+1, i+n)
|
|
if #s>0 and i+n<=#t then
|
|
while (#s < n and pad) do s[#s+1] = pad end
|
|
f(s)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function permgen(t, n, f) -- taken from PiL: http://www.lua.org/pil/9.3.html
|
|
if n == 0 then f(t) end
|
|
for i = 1,n do
|
|
t[n], t[i] = t[i], t[n]
|
|
permgen(t, n-1, f)
|
|
t[n], t[i] = t[i], t[n]
|
|
end
|
|
end
|
|
|
|
local function signum(a) return a>=0 and 1 or -1 end
|
|
|
|
-- Internal counter for unique ids generation
|
|
local unique_id_counter = -1
|
|
|
|
--- Operator functions
|
|
-- @section Operator functions
|
|
|
|
M.operator = {}
|
|
--- Returns a + b. <em>Aliased as `op.add`</em>.
|
|
-- @name operator.add
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a + b
|
|
M.operator.add = function(a,b) return a + b end
|
|
|
|
--- Returns a - b. <em>Aliased as `op.sub`</em>.
|
|
-- @name operator.sub
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a - b
|
|
M.operator.sub = function(a,b) return a - b end
|
|
|
|
--- Returns a * b. <em>Aliased as `op.mul`</em>.
|
|
-- @name operator.mul
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a * b
|
|
M.operator.mul = function(a,b) return a * b end
|
|
|
|
--- Returns a / b. <em>Aliased as `op.div`</em>.
|
|
-- @name operator.div
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a / b
|
|
M.operator.div = function(a,b) return a / b end
|
|
|
|
--- Returns a % b. <em>Aliased as `op.mod`</em>.
|
|
-- @name operator.mod
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a % b
|
|
M.operator.mod = function(a,b) return a % b end
|
|
|
|
--- Returns a ^ b. <em>Aliased as `op.exp`, `op.pow`</em>.
|
|
-- @name operator.exp
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a ^ b
|
|
M.operator.exp = function(a,b) return a ^ b end
|
|
M.operator.pow = M.operator.exp
|
|
|
|
--- Returns -a. <em>Aliased as `op.unm`, `op.neg`</em>.
|
|
-- @name operator.unm
|
|
-- @param a a value
|
|
-- @return -a
|
|
M.operator.unm = function(a) return -a end
|
|
M.operator.neg = M.operator.unm
|
|
|
|
--- Performs floor division (//) between `a` and `b`. It rounds the quotient towards minus infinity.
|
|
-- <em>Aliased as `op.floordiv`</em>.
|
|
-- @name operator.floordiv
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a // b
|
|
M.operator.floordiv = function(a, b) return floor(a/b) end
|
|
|
|
--- Performs integer division between `a` and `b`. <em>Aliased as `op.intdiv`</em>.
|
|
-- @name operator.intdiv
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a / b
|
|
M.operator.intdiv = function(a,b)
|
|
return a>=0 and floor(a/b) or ceil(a/b)
|
|
end
|
|
|
|
--- Checks if a equals b. <em>Aliased as `op.eq`</em>.
|
|
-- @name operator.eq
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a == b
|
|
M.operator.eq = function(a,b) return a == b end
|
|
|
|
--- Checks if a not equals b. <em>Aliased as `op.neq`</em>.
|
|
-- @name operator.neq
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a ~= b
|
|
M.operator.neq = function(a,b) return a ~= b end
|
|
|
|
--- Checks if a is strictly less than b. <em>Aliased as `op.lt`</em>.
|
|
-- @name operator.lt
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a < b
|
|
M.operator.lt = function(a,b) return a < b end
|
|
|
|
--- Checks if a is strictly greater than b. <em>Aliased as `op.gt`</em>.
|
|
-- @name operator.gt
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a > b
|
|
M.operator.gt = function(a,b) return a > b end
|
|
|
|
--- Checks if a is less or equal to b. <em>Aliased as `op.le`</em>.
|
|
-- @name operator.le
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a <= b
|
|
M.operator.le = function(a,b) return a <= b end
|
|
|
|
--- Checks if a is greater or equal to b. <em>Aliased as `op.ge`</em>.
|
|
-- @name operator.ge
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a >= b
|
|
M.operator.ge = function(a,b) return a >= b end
|
|
|
|
--- Returns logical a and b. <em>Aliased as `op.land`</em>.
|
|
-- @name operator.land
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a and b
|
|
M.operator.land = function(a,b) return a and b end
|
|
|
|
--- Returns logical a or b. <em>Aliased as `op.lor`</em>.
|
|
-- @name operator.lor
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a or b
|
|
M.operator.lor = function(a,b) return a or b end
|
|
|
|
--- Returns logical not a. <em>Aliased as `op.lnot`</em>.
|
|
-- @name operator.lnot
|
|
-- @param a a value
|
|
-- @return not a
|
|
M.operator.lnot = function(a) return not a end
|
|
|
|
--- Returns concatenation of a and b. <em>Aliased as `op.concat`</em>.
|
|
-- @name operator.concat
|
|
-- @param a a value
|
|
-- @param b a value
|
|
-- @return a .. b
|
|
M.operator.concat = function(a,b) return a..b end
|
|
|
|
--- Returns the length of a. <em>Aliased as `op.len`</em>.
|
|
-- @name operator.length
|
|
-- @param a a value
|
|
-- @return #a
|
|
M.operator.length = function(a) return #a end
|
|
M.operator.len = M.operator.length
|
|
|
|
--- Table functions
|
|
-- @section Table functions
|
|
|
|
--- Clears a table. All its values become nil.
|
|
-- @name clear
|
|
-- @param t a table
|
|
-- @return the given table, cleared.
|
|
function M.clear(t)
|
|
for k in pairs(t) do t[k] = nil end
|
|
return t
|
|
end
|
|
|
|
|
|
|
|
--- Iterates on key-value pairs, calling `f (v, k)` at every step.
|
|
-- <br/><em>Aliased as `forEach`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `forEachi`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `alleq`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `loop`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `collect`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `inject`, `foldl`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `injectr`, `foldr`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `mapr`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `maprr`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `any`, `some`, `contains`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `filter`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `discard`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `every`</em>
|
|
-- @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 <tt><</tt> 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.
|
|
-- <br/><em>Aliased as `takeWhile`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `rejectWhile`</em>
|
|
-- @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 <tt><</tt> 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.
|
|
-- <br/><em>Aliased as `pop`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `remove`</em>
|
|
-- @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]`.
|
|
-- <br/><em>Aliased as `rmRange`, `chop`</em>
|
|
-- @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)
|
|
ck = ((val~=prev) and (ck+1) or ck)
|
|
prev = (prev==nil) and val or prev
|
|
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.
|
|
-- <br/><em>Aliased as `M.sub`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `head`, `take` </em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `tail`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `without` and `diff`</em>
|
|
-- @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`.
|
|
-- <br/><em>Aliased as `symdiff`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `uniq`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `isuniq`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `transpose`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `transposeWith`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `intersperse`</em>
|
|
-- @name interpose
|
|
-- @param array an array
|
|
-- @param value a value
|
|
-- @return a new array
|
|
-- @see interleave
|
|
function M.interpose(array, value)
|
|
for k = #array, 2,-1 do
|
|
t_insert(array, k, value)
|
|
end
|
|
return array
|
|
end
|
|
|
|
--- Produces a flexible list of numbers. If one value is passed, will count from 1 to that value,
|
|
-- with a default step of 1 (or -1). If two values are passed, will count from the first one to the second one,
|
|
-- using a default step of 1 (or -1). A third value passed will be considered a step value.
|
|
-- @name range
|
|
-- @param[opt] from the initial value of the range
|
|
-- @param[optchain] to the final value of the range
|
|
-- @param[optchain] step the step of count. Defaults to 1 or -1.
|
|
-- @return a new array of numbers
|
|
function M.range(from, to, step)
|
|
if (from == nil) and (to == nil) and (step ==nil) then
|
|
return {}
|
|
elseif (from ~= nil) and (to == nil) and (step == nil) then
|
|
from, to, step = signum(from), from, signum(from)
|
|
elseif (from ~= nil) and (to ~= nil) and (step == nil) then
|
|
step = signum(to - from)
|
|
end
|
|
local _ranged = {from}
|
|
local steps = max(floor((to-from)/step),0)
|
|
for i=1,steps do _ranged[#_ranged+1] = from+step*i end
|
|
return _ranged
|
|
end
|
|
|
|
--- Creates an array list of `n` values, repeated.
|
|
-- @name rep
|
|
-- @param value a value to be repeated
|
|
-- @param n the number of repetitions of value.
|
|
-- @return a new array of `n` values
|
|
function M.rep(value, n)
|
|
local ret = {}
|
|
for i = 1, n do ret[i] = value end
|
|
return ret
|
|
end
|
|
|
|
--- Returns the powerset of array values. For instance, when given the set {1,2,3},
|
|
-- returns `{{},{1},{2},{3},{1,2},{2,3},{1,3},{1,2,3}}`.
|
|
-- @name powerset
|
|
-- @param array an array
|
|
-- @return an array
|
|
function M.powerset(array)
|
|
local n = #array
|
|
local powerset = {}
|
|
for i, v in ipairs(array) do
|
|
for j = 1, #powerset do
|
|
local set = powerset[j]
|
|
t_insert(powerset, M.push(M.slice(set), v))
|
|
end
|
|
t_insert(powerset, {v})
|
|
end
|
|
t_insert(powerset, {})
|
|
return powerset
|
|
end
|
|
|
|
--- Iterator returning partitions of an array. It returns arrays of length `n`
|
|
-- made of values from the given array. If the last partition has lower elements than `n` and
|
|
-- `pad` is supplied, it will be adjusted to `n` of elements with `pad` value.
|
|
-- @name partition
|
|
-- @param array an array
|
|
-- @param[opt] n the size of partitions. Defaults to 1.
|
|
-- @param[optchain] pads a value to adjust the last subsequence to the `n` elements
|
|
-- @return an iterator function
|
|
-- @see overlapping
|
|
-- @see aperture
|
|
function M.partition(array, n, pad)
|
|
if n<=0 then return end
|
|
return wrap(function()
|
|
partgen(array, n or 1, yield, pad)
|
|
end)
|
|
end
|
|
|
|
--- Iterator returning overlapping partitions of an array.
|
|
-- If the last subsequence has lower elements than `n` and `pad` is
|
|
-- supplied, it will be adjusted to `n` elements with `pad` value.
|
|
-- @name overlapping
|
|
-- @param array an array
|
|
-- @param[opt] n the size of partitions. Defaults to 2.
|
|
-- @param[optchain] pads a value to adjust the last subsequence to the `n` elements
|
|
-- @return an iterator function
|
|
-- @see partition
|
|
-- @see aperture
|
|
function M.overlapping(array, n, pad)
|
|
if n<=1 then return end
|
|
return wrap(function()
|
|
partgen2(array, n or 2, yield, pad)
|
|
end)
|
|
end
|
|
|
|
--- Iterator returning sliding partitions of an array.
|
|
-- <br/><em>Aliased as `sliding`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `join`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `average`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `always`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `cache`</em>
|
|
-- @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 <em>count</em> 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.
|
|
-- <br/><em>Aliased as `juxt`</em>
|
|
-- @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)`.
|
|
-- <br/><em>Aliased as `uid`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `iter`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `nAry`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `mirror`</em>
|
|
-- @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.
|
|
-- <br/><em>Aliased as `methods`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `choose`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `drop`</em>.
|
|
-- @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.
|
|
-- <br/><em>Aliased as `defaults`</em>.
|
|
-- @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`.
|
|
-- <br/><em>Aliased as `M.compare`, `M.matches`</em>
|
|
-- @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 <http://raw.githubusercontent.com/Yonaba/Moses/master/LICENSE>'
|
|
Moses._DESCRIPTION = 'utility-belt library for functional programming in Lua'
|
|
|
|
return Moses
|
|
|
|
end
|