batteries/sequence.lua

137 lines
2.6 KiB
Lua

--[[
sequence - functional + oo wrapper for ordered tables
mainly beneficial when used for method chaining
to save on typing and data plumbing
]]
local path = (...):gsub("sequence", "")
local table = require(path .. "tablex") --shadow global table module
local functional = require(path .. "functional")
local stable_sort = require(path .. "sort").stable_sort
--(not a class, because we want to be able to upgrade tables that are passed in without a copy)
local sequence = {}
sequence.__index = sequence
setmetatable(sequence, {
__index = table,
__call = function(self, ...)
return sequence:new(...)
end,
})
--iterators as method calls
--(no pairs, sequences are ordered)
--todo: pico8 like `all`
sequence.ipairs = ipairs
sequence.iterate = ipairs
--upgrade a table into a sequence, or create a new sequence
function sequence:new(t)
return setmetatable(t or {}, sequence)
end
--sorting default to stable
sequence.sort = stable_sort
--patch various interfaces in a type-preserving way, for method chaining
--import copying tablex
--(common case where something returns another sequence for chaining)
for _, v in ipairs({
"keys",
"values",
"dedupe",
"collapse",
"append",
"shallow_overlay",
"deep_overlay",
"copy",
"shallow_copy",
"deep_copy",
}) do
local table_f = table[v]
sequence[v] = function(self, ...)
return sequence(table_f(self, ...))
end
end
--aliases
for _, v in ipairs({
{"flatten", "collapse"},
}) do
sequence[v[1]] = sequence[v[2]]
end
--import functional interface in method form
--(common case where something returns another sequence for chaining)
for _, v in ipairs({
"map",
"map_field",
"map_call",
"filter",
"remove_if",
"zip",
"combine",
"stitch",
"map_stitch",
"cycle",
"map_cycle",
"chain",
"map_chain",
}) do
local functional_f = functional[v]
sequence[v] = function(self, ...)
return sequence(functional_f(self, ...))
end
end
--(cases where we don't want to construct a new sequence)
for _, v in ipairs({
"map_inplace",
"filter_inplace",
"foreach",
"reduce",
"any",
"none",
"all",
"count",
"contains",
"sum",
"mean",
"minmax",
"max",
"min",
"find_min",
"find_max",
"find_nearest",
"find_match",
}) do
sequence[v] = functional[v]
end
--aliases
for _, v in ipairs({
{"remap", "map_inplace"},
{"map_stitch", "stitch"},
{"map_cycle", "cycle"},
{"find_best", "find_max"},
}) do
sequence[v[1]] = sequence[v[2]]
end
--(anything that needs bespoke wrapping)
function sequence:partition(f)
local a, b = functional.partition(self, f)
return sequence(a), sequence(b)
end
function sequence:unzip(f)
local a, b = functional.unzip(self, f)
return sequence(a), sequence(b)
end
return sequence