2022-07-20 00:11:03 -04:00
|
|
|
local M = {}
|
|
|
|
|
|
|
|
local err = require 'err'
|
|
|
|
|
|
|
|
local __math_floor = math.floor
|
2022-07-23 19:54:49 -04:00
|
|
|
local __table_insert = table.insert
|
2023-09-28 23:25:11 -04:00
|
|
|
local __string_format = string.format
|
2022-07-20 00:11:03 -04:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- zippy functions
|
|
|
|
|
|
|
|
-- TODO generalize to arbitrary number of sequences
|
|
|
|
M.zip_with = function(f, seq1, seq2)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, #seq1 do
|
|
|
|
r[i] = f(seq1[i], seq2[i])
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.zip = function(...)
|
|
|
|
local seqs = {...}
|
|
|
|
local imax = math.min(table.unpack(M.map(function(t) return #t end, seqs)))
|
|
|
|
local jmax = #seqs
|
|
|
|
local r = {}
|
|
|
|
for i = 1, imax do
|
|
|
|
r[i] = {}
|
|
|
|
for j = 1, jmax do
|
|
|
|
r[i][j] = seqs[j][i]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.unzip = function(seqs)
|
|
|
|
return M.zip(table.unpack(seqs))
|
|
|
|
end
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- reductions
|
|
|
|
|
|
|
|
M.reduce = function(f, init, seq)
|
|
|
|
if seq == nil then
|
|
|
|
return init
|
|
|
|
else
|
|
|
|
local r = init
|
|
|
|
for i = 1, #seq do
|
|
|
|
r = f(r, seq[i])
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-28 23:25:11 -04:00
|
|
|
M.member = function(x, seq)
|
|
|
|
return M.reduce(
|
|
|
|
function(acc, next) return acc or next == x end,
|
|
|
|
false,
|
|
|
|
M.values(seq)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
M.collapse = function(seq, delim)
|
|
|
|
if #seq == 0 then
|
|
|
|
return ''
|
|
|
|
elseif #seq == 1 then
|
|
|
|
return seq[1]
|
|
|
|
else
|
|
|
|
local init, rest = M.head(seq)
|
|
|
|
return M.reduce(
|
|
|
|
function(acc, next) return __string_format('%s%s%s', acc, delim, next) end,
|
|
|
|
init,
|
|
|
|
rest
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- mappy functions
|
|
|
|
|
2023-09-28 23:25:11 -04:00
|
|
|
M.keys = function(seq)
|
|
|
|
local r = {}
|
|
|
|
local n = 1
|
|
|
|
for k, _ in pairs(seq) do
|
|
|
|
r[n] = k
|
|
|
|
n = n + 1
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.values = function(seq)
|
|
|
|
local r = {}
|
|
|
|
local n = 1
|
|
|
|
for _, v in pairs(seq) do
|
|
|
|
r[n] = v
|
|
|
|
n = n + 1
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
M.map = function(f, seq, ...)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, #seq do
|
|
|
|
r[i] = f(seq[i], ...)
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.map_n = function(f, n, ...)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, n do
|
|
|
|
r[i] = f(i, ...)
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.imap = function(f, seq)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, #seq do
|
|
|
|
r[i] = f(i, seq[i])
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-23 23:52:13 -04:00
|
|
|
M.kmap = function(f, seq)
|
|
|
|
local r = {}
|
|
|
|
for k, v in pairs(seq) do
|
|
|
|
r[k] = f(k, v)
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
M.map_keys = function(key, tbls)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, #tbls do
|
|
|
|
r[i] = tbls[i][key]
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.map_at = function(key, f, tbl)
|
|
|
|
local r = {}
|
|
|
|
for k, v in pairs(tbl) do
|
|
|
|
if k == key then
|
|
|
|
r[k] = f(v)
|
|
|
|
else
|
|
|
|
r[k] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- generations
|
|
|
|
|
|
|
|
M.seq = function(n, start)
|
|
|
|
start = start or 1
|
|
|
|
local r = {}
|
|
|
|
for i = 1, n do
|
|
|
|
r[i] = i + start - 1
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.rep = function(n, x)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, n do
|
|
|
|
r[i] = x
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-23 21:50:31 -04:00
|
|
|
M.array_to_map = function(arr)
|
|
|
|
local r = {}
|
2022-07-23 23:52:13 -04:00
|
|
|
for _, v in pairs(arr) do
|
|
|
|
r[v[1]] = v[2]
|
2022-07-23 21:50:31 -04:00
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- random list things
|
|
|
|
|
2023-09-28 17:33:58 -04:00
|
|
|
M.length = function(tbl)
|
|
|
|
local n = 0
|
|
|
|
for _, _ in pairs(tbl) do
|
|
|
|
n = n + 1
|
|
|
|
end
|
|
|
|
return n
|
|
|
|
end
|
|
|
|
|
2023-09-28 23:25:11 -04:00
|
|
|
M.head = function(seq)
|
|
|
|
local r = {}
|
|
|
|
for i = 2, #seq do
|
|
|
|
r[i - 1] = seq[i]
|
|
|
|
end
|
|
|
|
return seq[1], r
|
|
|
|
end
|
|
|
|
|
2022-07-24 00:47:45 -04:00
|
|
|
-- a stupid but composable function
|
|
|
|
M.get = function(key, tbl)
|
|
|
|
return tbl[key]
|
|
|
|
end
|
|
|
|
|
|
|
|
M.getter = function(key)
|
|
|
|
return M.partial(M.get, key)
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
M.set = function(tbl, key, value)
|
|
|
|
local r = {}
|
|
|
|
for k, v in pairs(tbl) do
|
|
|
|
if k == key then
|
|
|
|
r[k] = value
|
|
|
|
else
|
|
|
|
r[k] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.reverse = function(xs)
|
|
|
|
local j = 1
|
|
|
|
local r = {}
|
|
|
|
for i = #xs, 1, -1 do
|
|
|
|
r[j] = xs[i]
|
|
|
|
j = j + 1
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.filter = function(f, seq)
|
|
|
|
local r = {}
|
|
|
|
local j = 1
|
|
|
|
for i = 1, #seq do
|
|
|
|
if f(seq[i]) == true then
|
|
|
|
r[j] = seq[i]
|
|
|
|
j = j + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.flatten = function(xs)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, #xs do
|
|
|
|
for j = 1, #xs[i] do
|
|
|
|
table.insert(r, xs[i][j])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2023-09-26 00:55:39 -04:00
|
|
|
M.group_with = function(keyfun, valfun, seq)
|
|
|
|
local f = function(acc, next)
|
|
|
|
local k = keyfun(next)
|
|
|
|
local v = valfun(next)
|
|
|
|
if acc[k] == nil then
|
|
|
|
acc[k] = {v}
|
|
|
|
else
|
|
|
|
acc[k][#acc[k] + 1] = v
|
|
|
|
end
|
|
|
|
return acc
|
|
|
|
end
|
|
|
|
return M.reduce(f, {}, seq)
|
|
|
|
end
|
|
|
|
|
|
|
|
M.group_by = function(k, seq)
|
|
|
|
local f = function(acc, next)
|
|
|
|
if acc[k] == nil then
|
|
|
|
acc[k] = {next}
|
|
|
|
else
|
|
|
|
acc[k][#acc[k]] = next
|
|
|
|
end
|
|
|
|
return acc
|
|
|
|
end
|
|
|
|
return M.reduce(f, {}, seq)
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
M.concat = function(...)
|
|
|
|
return M.flatten({...})
|
|
|
|
end
|
|
|
|
|
|
|
|
M.table_array = function(tbl)
|
|
|
|
local r = {}
|
|
|
|
for i = 1, #tbl do
|
|
|
|
r[i] = tbl[i]
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-23 21:50:31 -04:00
|
|
|
M.iter_to_table1 = function(iter)
|
|
|
|
local r = {}
|
|
|
|
for next in iter do
|
|
|
|
__table_insert(r, next)
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
|
|
|
M.iter_to_tableN = function(iter)
|
2022-07-23 19:54:49 -04:00
|
|
|
local r = {}
|
2022-07-23 21:50:31 -04:00
|
|
|
local next = {iter()}
|
|
|
|
while #next > 0 do
|
|
|
|
__table_insert(r, next)
|
|
|
|
next = {iter()}
|
2022-07-23 19:54:49 -04:00
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
|
2022-07-24 11:56:57 -04:00
|
|
|
M.curry_table = function(f)
|
|
|
|
return function(tbl) return f(table.unpack(tbl)) end
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- functional functions
|
|
|
|
|
|
|
|
local get_arity = function(f, args)
|
|
|
|
local i = #args
|
|
|
|
while args[i] == true do
|
|
|
|
i = i - 1
|
|
|
|
end
|
|
|
|
if i < #args then
|
|
|
|
return table.move(args, 1, #args - i, 1, {}), #args
|
|
|
|
else
|
|
|
|
local arity = debug.getinfo(f, "u")["nparams"]
|
|
|
|
err.assert_trace(arity > #args, 'too many arguments for partial')
|
|
|
|
return args, arity
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- poor man's Lisp macro :)
|
|
|
|
M.partial = function(f, ...)
|
|
|
|
local args, arity = get_arity(f, {...})
|
|
|
|
local format_args = function(fmt, n, start)
|
|
|
|
return table.concat(
|
|
|
|
M.map(function(i) return string.format(fmt, i) end, M.seq(n, start)),
|
|
|
|
','
|
|
|
|
)
|
|
|
|
end
|
|
|
|
local partial_args = format_args('args[%i]', #args, 1)
|
|
|
|
local rem_args = format_args('x%i', arity - #args, #args + 1)
|
|
|
|
local src = string.format(
|
|
|
|
'return function(%s) return f(%s,%s) end',
|
|
|
|
rem_args,
|
|
|
|
partial_args,
|
|
|
|
rem_args
|
|
|
|
)
|
|
|
|
return load(src, 'partial_apply', 't', {f = f, args = args})()
|
|
|
|
end
|
|
|
|
|
2022-07-23 22:55:30 -04:00
|
|
|
M.flip = function(f)
|
|
|
|
return function(x, y) return f(y, x) end
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
M.compose = function(f, ...)
|
|
|
|
if #{...} == 0 then
|
|
|
|
return f
|
|
|
|
else
|
|
|
|
local g = M.compose(...)
|
|
|
|
return function(x) return f(g(x)) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO is there a way to do this without nesting a zillion function calls?
|
|
|
|
M.sequence = function(...)
|
|
|
|
local fs = {...}
|
|
|
|
return function(x)
|
|
|
|
for i = 1, #fs do
|
|
|
|
fs[i](x)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
M.memoize = function(f)
|
|
|
|
local mem = {} -- memoizing table
|
|
|
|
setmetatable(mem, {__mode = "kv"}) -- make it weak
|
|
|
|
return function (x, ...)
|
|
|
|
local r = mem[x]
|
|
|
|
if not r then
|
|
|
|
r = f(x, ...)
|
|
|
|
mem[x] = r
|
|
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-24 00:47:45 -04:00
|
|
|
M.id = function(x) return x end
|
|
|
|
|
2022-07-23 22:55:30 -04:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- maybe
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
M.maybe = function(def, f, x)
|
|
|
|
if x == nil then
|
|
|
|
return def
|
|
|
|
else
|
|
|
|
return f(x)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-23 21:50:31 -04:00
|
|
|
M.fmap_maybe = function(f, x)
|
|
|
|
return M.maybe(nil, f, x)
|
|
|
|
end
|
|
|
|
|
2022-07-20 00:11:03 -04:00
|
|
|
-- round to whole numbers since I don't need more granularity and extra values
|
|
|
|
-- will lead to cache misses
|
|
|
|
M.round_percent = __math_floor
|
|
|
|
|
|
|
|
return M
|