mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-10 02:31:48 +00:00
Merge remote-tracking branch 'origin/feature/functional-refactor' into master
This commit is contained in:
commit
5335b6fbdd
190
functional.lua
190
functional.lua
@ -26,61 +26,83 @@ end
|
||||
--simple sequential iteration, f is called for all elements of t
|
||||
--f can return non-nil to break the loop (and return the value)
|
||||
function functional.foreach(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
local r = f(v, i)
|
||||
if r ~= nil then
|
||||
return r
|
||||
for i = 1, #t do
|
||||
local result = f(t[i], i)
|
||||
if result ~= nil then
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--performs a left to right reduction of t using f, with o as the initial value
|
||||
-- reduce({1, 2, 3}, f, 0) -> f(f(f(0, 1), 2), 3)
|
||||
--performs a left to right reduction of t using f, with seed as the initial value
|
||||
-- reduce({1, 2, 3}, 0, f) -> f(f(f(0, 1), 2), 3)
|
||||
-- (but performed iteratively, so no stack smashing)
|
||||
function functional.reduce(t, f, o)
|
||||
for i,v in ipairs(t) do
|
||||
o = f(o, v)
|
||||
function functional.reduce(t, seed, f)
|
||||
for i = 1, #t do
|
||||
seed = f(seed, t[i], i)
|
||||
end
|
||||
return o
|
||||
return seed
|
||||
end
|
||||
|
||||
--maps a sequence {a, b, c} -> {f(a), f(b), f(c)}
|
||||
-- (automatically drops any nils due to table.insert, which can be used to simultaneously map and filter)
|
||||
-- (automatically drops any nils to keep a sequence, so can be used to simultaneously map and filter)
|
||||
function functional.map(t, f)
|
||||
local r = {}
|
||||
for i,v in ipairs(t) do
|
||||
local mapped = f(v, i)
|
||||
if mapped ~= nil then
|
||||
table.insert(r, mapped)
|
||||
local result = {}
|
||||
for i = 1, #t do
|
||||
local v = f(t[i], i)
|
||||
if v ~= nil then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
return result
|
||||
end
|
||||
|
||||
--maps a sequence inplace, modifying it {a, b, c} -> {f(a), f(b), f(c)}
|
||||
-- (automatically drops any nils, which can be used to simultaneously map and filter,
|
||||
-- but this results in a linear table.remove so "careful" for big working sets)
|
||||
function functional.remap(t, f)
|
||||
local i = 1
|
||||
while i <= #t do
|
||||
local mapped = f(t[i])
|
||||
if mapped ~= nil then
|
||||
t[i] = mapped
|
||||
i = i + 1
|
||||
else
|
||||
table.remove(t, i)
|
||||
-- (automatically drops any nils, which can be used to simultaneously map and filter)
|
||||
function functional.map_inplace(t, f)
|
||||
local write_i = 0
|
||||
local n = #t --cache, so splitting the sequence doesn't stop iteration
|
||||
for i = 1, n do
|
||||
local v = f(t[i], i)
|
||||
if v ~= nil then
|
||||
write_i = write_i + 1
|
||||
t[write_i] = v
|
||||
end
|
||||
if i ~= write_i then
|
||||
t[i] = nil
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
--alias
|
||||
functional.remap = functional.map_inplace
|
||||
|
||||
--filters a sequence
|
||||
-- returns a table containing items where f(v) returns truthy
|
||||
-- returns a table containing items where f(v, i) returns truthy
|
||||
function functional.filter(t, f)
|
||||
local r = {}
|
||||
for i,v in ipairs(t) do
|
||||
local result = {}
|
||||
for i = 1, #t do
|
||||
local v = t[i]
|
||||
if f(v, i) then
|
||||
table.insert(r, v)
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--filters a sequence in place, modifying it
|
||||
function functional.filter_inplace(t, f)
|
||||
local write_i = 1
|
||||
local n = #t --cache, so splitting the sequence doesn't stop iteration
|
||||
for i = 1, n do
|
||||
local v = t[i]
|
||||
if f(v, i) then
|
||||
t[write_i] = v
|
||||
write_i = write_i + 1
|
||||
end
|
||||
if i ~= write_i then
|
||||
t[i] = nil
|
||||
end
|
||||
end
|
||||
return r
|
||||
@ -90,13 +112,14 @@ end
|
||||
-- returns a table containing items where f(v) returns falsey
|
||||
-- nil results are included so that this is an exact complement of filter; consider using partition if you need both!
|
||||
function functional.remove_if(t, f)
|
||||
local r = {}
|
||||
for i, v in ipairs(t) do
|
||||
local result = {}
|
||||
for i = 1, #t do
|
||||
local v = t[i]
|
||||
if not f(v, i) then
|
||||
table.insert(r, v)
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
return result
|
||||
end
|
||||
|
||||
--partitions a sequence into two, based on filter criteria
|
||||
@ -104,7 +127,8 @@ end
|
||||
function functional.partition(t, f)
|
||||
local a = {}
|
||||
local b = {}
|
||||
for i,v in ipairs(t) do
|
||||
for i = 1, #t do
|
||||
local v = t[i]
|
||||
if f(v, i) then
|
||||
table.insert(a, v)
|
||||
else
|
||||
@ -115,11 +139,13 @@ function functional.partition(t, f)
|
||||
end
|
||||
|
||||
-- returns a table where the elements in t are grouped into sequential tables by the result of f on each element.
|
||||
-- more general than partition, but requires you to know your groups ahead of time (or use numeric grouping) if you want to avoid pairs!
|
||||
-- more general than partition, but requires you to know your groups ahead of time
|
||||
-- (or use numeric grouping and pre-seed) if you want to avoid pairs!
|
||||
function functional.group_by(t, f)
|
||||
local result = {}
|
||||
for i, v in ipairs(t) do
|
||||
local group = f(v)
|
||||
for i = 1, #t do
|
||||
local v = t[i]
|
||||
local group = f(v, i)
|
||||
if result[group] == nil then
|
||||
result[group] = {}
|
||||
end
|
||||
@ -154,26 +180,26 @@ end
|
||||
--basically a map on numeric values from 1 to count
|
||||
--nil values are omitted in the result, as for map
|
||||
function functional.generate(count, f)
|
||||
local r = {}
|
||||
local result = {}
|
||||
for i = 1, count do
|
||||
local v = f(i)
|
||||
if v ~= nil then
|
||||
table.insert(r, v)
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return r
|
||||
return result
|
||||
end
|
||||
|
||||
--2d version of the above
|
||||
--note: ends up with a 1d table;
|
||||
-- if you need a 2d table, you should nest 1d generate calls
|
||||
function functional.generate_2d(width, height, f)
|
||||
local r = {}
|
||||
local result = {}
|
||||
for y = 1, height do
|
||||
for x = 1, width do
|
||||
local v = f(x, y)
|
||||
if v ~= nil then
|
||||
table.insert(r, v)
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -186,8 +212,8 @@ end
|
||||
|
||||
--true if any element of the table matches f
|
||||
function functional.any(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
for i = 1, #t do
|
||||
if f(t[i], i) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@ -196,8 +222,8 @@ end
|
||||
|
||||
--true if no element of the table matches f
|
||||
function functional.none(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
for i = 1, #t do
|
||||
if f(t[i], i) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -206,8 +232,8 @@ end
|
||||
|
||||
--true if all elements of the table match f
|
||||
function functional.all(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if not f(v) then
|
||||
for i = 1, #t do
|
||||
if not f(t[i], i) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -217,8 +243,8 @@ end
|
||||
--counts the elements of t that match f
|
||||
function functional.count(t, f)
|
||||
local c = 0
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
for i = 1, #t do
|
||||
if f(t[i], i) then
|
||||
c = c + 1
|
||||
end
|
||||
end
|
||||
@ -227,8 +253,8 @@ end
|
||||
|
||||
--true if the table contains element e
|
||||
function functional.contains(t, e)
|
||||
for i, v in ipairs(t) do
|
||||
if v == e then
|
||||
for i = 1, #t do
|
||||
if t[i] == e then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@ -237,9 +263,11 @@ end
|
||||
|
||||
--return the numeric sum of all elements of t
|
||||
function functional.sum(t)
|
||||
return functional.reduce(t, function(a, b)
|
||||
return a + b
|
||||
end, 0)
|
||||
local c = 0
|
||||
for i = 1, #t do
|
||||
c = c + t[i]
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
--return the numeric mean of all elements of t
|
||||
@ -256,14 +284,16 @@ end
|
||||
-- (would perhaps more correctly be math.huge, -math.huge
|
||||
-- but that tends to be surprising/annoying in practice)
|
||||
function functional.minmax(t)
|
||||
local max, min
|
||||
for i,v in ipairs(t) do
|
||||
min = not min and v or math.min(min, v)
|
||||
max = not max and v or math.max(max, v)
|
||||
local n = #t
|
||||
if n == 0 then
|
||||
return 0, 0
|
||||
end
|
||||
if min == nil then
|
||||
min = 0
|
||||
max = 0
|
||||
local max = t[1]
|
||||
local min = t[1]
|
||||
for i = 2, n do
|
||||
local v = t[i]
|
||||
min = math.min(min, v)
|
||||
max = math.max(max, v)
|
||||
end
|
||||
return min, max
|
||||
end
|
||||
@ -285,7 +315,8 @@ end
|
||||
function functional.find_min(t, f)
|
||||
local current = nil
|
||||
local current_min = math.huge
|
||||
for i, e in ipairs(t) do
|
||||
for i = 1, #t do
|
||||
local e = t[i]
|
||||
local v = f(e, i)
|
||||
if v and v < current_min then
|
||||
current_min = v
|
||||
@ -300,7 +331,8 @@ end
|
||||
function functional.find_max(t, f)
|
||||
local current = nil
|
||||
local current_max = -math.huge
|
||||
for i, e in ipairs(t) do
|
||||
for i = 1, #t do
|
||||
local e = t[i]
|
||||
local v = f(e, i)
|
||||
if v and v > current_max then
|
||||
current_max = v
|
||||
@ -314,16 +346,28 @@ end
|
||||
functional.find_best = functional.find_max
|
||||
|
||||
--return the element of the table that results in the value nearest to the passed value
|
||||
--todo: optimise as this generates a closure each time
|
||||
function functional.find_nearest(t, f, v)
|
||||
return functional.find_min(t, function(e)
|
||||
return math.abs(f(e) - v)
|
||||
end)
|
||||
--todo: optimise, inline as this generates a closure each time
|
||||
function functional.find_nearest(t, f, target)
|
||||
local current = nil
|
||||
local current_min = math.huge
|
||||
for i = 1, #t do
|
||||
local e = t[i]
|
||||
local v = math.abs(f(e, i) - target)
|
||||
if v and v < current_min then
|
||||
current_min = v
|
||||
current = e
|
||||
if v == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return current
|
||||
end
|
||||
|
||||
--return the first element of the table that results in a true filter
|
||||
function functional.find_match(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
for i = 1, #t do
|
||||
local v = t[i]
|
||||
if f(v) then
|
||||
return v
|
||||
end
|
||||
|
14
sequence.lua
14
sequence.lua
@ -58,22 +58,28 @@ function sequence:foreach(f)
|
||||
return functional.foreach(self, f)
|
||||
end
|
||||
|
||||
function sequence:reduce(f, o)
|
||||
return functional.reduce(self, f, o)
|
||||
function sequence:reduce(seed, f)
|
||||
return functional.reduce(self, seed, f)
|
||||
end
|
||||
|
||||
function sequence:map(f)
|
||||
return sequence(functional.map(self, f))
|
||||
end
|
||||
|
||||
function sequence:remap(f)
|
||||
return functional.remap(self, f)
|
||||
function sequence:map_inplace(f)
|
||||
return sequence(functional.map_inplace(self, f))
|
||||
end
|
||||
|
||||
sequence.remap = sequence.map_inplace
|
||||
|
||||
function sequence:filter(f)
|
||||
return sequence(functional.filter(self, f))
|
||||
end
|
||||
|
||||
function sequence:filter_inplace(f)
|
||||
return sequence(functional.filter_inplace(self, f))
|
||||
end
|
||||
|
||||
function sequence:remove_if(f)
|
||||
return sequence(functional.remove_if(self, f))
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user