tablex: Add deep & shallow for copy, overlay

Replace copy with shallow_copy, deep_copy.
Replace overlay with shallow_overlay, deep_overlay.

Significant rewrite of these functions and their semantics to update to
the note above them.

Add corresponding tests.
This commit is contained in:
David Briscoe 2022-03-02 08:11:57 -08:00
parent 5b4d1c16c5
commit 5fa80ca97c
4 changed files with 147 additions and 52 deletions

View File

@ -64,16 +64,16 @@ function _batteries:export()
end end
--overlay tablex and functional and sort routines onto table --overlay tablex and functional and sort routines onto table
self.tablex.overlay(table, self.tablex) self.tablex.shallow_overlay(table, self.tablex)
--now we can use it through table directly --now we can use it through table directly
table.overlay(table, self.functional) table.shallow_overlay(table, self.functional)
self.sort:export() self.sort:export()
--overlay onto global math table --overlay onto global math table
table.overlay(math, self.mathx) table.shallow_overlay(math, self.mathx)
--overlay onto string --overlay onto string
table.overlay(string, self.stringx) table.shallow_overlay(string, self.stringx)
--overwrite assert wholesale (it's compatible) --overwrite assert wholesale (it's compatible)
assert = self.assert assert = self.assert

View File

@ -44,8 +44,10 @@ for _, v in ipairs({
"dedupe", "dedupe",
"collapse", "collapse",
"append", "append",
"overlay", "shallow_overlay",
"copy", "deep_overlay",
"shallow_copy",
"deep_copy",
}) do }) do
local table_f = table[v] local table_f = table[v]
sequence[v] = function(self, ...) sequence[v] = function(self, ...)

View File

@ -328,61 +328,81 @@ if not tablex.clear then
end end
end end
--note: -- Copy a table
-- copies and overlays are currently not satisfactory -- See shallow_overlay to shallow copy into another table to avoid garbage.
-- function tablex.shallow_copy(t)
-- i feel that copy especially tries to do too much and
-- probably they should be split into separate functions
-- to be both more explicit and performant, ie
--
-- shallow_copy, deep_copy, shallow_overlay, deep_overlay
--
-- input is welcome on this :)
--copy a table
-- deep_or_into is either:
-- a boolean value, used as deep flag directly
-- or a table to copy into, which implies a deep copy
-- if deep specified:
-- calls copy method of member directly if it exists
-- and recurses into all "normal" table children
-- if into specified, copies into that table
-- but doesn't clear anything out
-- (useful for deep overlays and avoiding garbage)
function tablex.copy(t, deep_or_into)
assert:type(t, "table", "tablex.copy - t", 1) assert:type(t, "table", "tablex.copy - t", 1)
local is_bool = type(deep_or_into) == "boolean" local into = {}
local is_table = type(deep_or_into) == "table"
local deep = is_bool and deep_or_into or is_table
local into = is_table and deep_or_into or {}
for k, v in pairs(t) do for k, v in pairs(t) do
if deep and type(v) == "table" then
if type(v.copy) == "function" then
v = v:copy()
else
v = tablex.copy(v, deep)
end
end
into[k] = v into[k] = v
end end
return into return into
end end
--overlay tables directly onto one another, shallow only local function deep_copy(t, copied)
--takes as many tables as required, -- TODO: consider supporting deep_copy(3) so you can always use deep_copy without type checking
--overlays them in passed order onto the first, local into = {}
--and returns the first table with the overlay(s) applied for k, v in pairs(t) do
function tablex.overlay(a, b, ...) local clone = v
assert:type(a, "table", "tablex.overlay - a", 1) if type(v) == "table" then
assert:type(b, "table", "tablex.overlay - b", 1) if copied[v] then
for k,v in pairs(b) do clone = copied[v]
a[k] = v elseif type(v.copy) == "function" then
clone = v:copy()
assert:type(clone, "table", "copy() didn't return a copy")
else
clone = deep_copy(v, copied)
setmetatable(clone, getmetatable(v))
end end
if ... then copied[v] = clone
return tablex.overlay(a, ...)
end end
return a into[k] = clone
end
return into
end
-- Recursively copy values of a table.
-- Retains the same keys as original table -- they're not cloned.
function tablex.deep_copy(t)
assert:type(t, "table", "tablex.deep_copy - t", 1)
return deep_copy(t, {})
end
-- Overlay tables directly onto one another, merging them together.
-- Doesn't merge tables within.
-- Takes as many tables as required,
-- overlays them in passed order onto the first,
-- and returns the first table.
function tablex.shallow_overlay(dest, ...)
assert:type(dest, "table", "tablex.shallow_overlay - dest", 1)
for i = 1, select("#", ...) do
local t = select(i, ...)
assert:type(t, "table", "tablex.shallow_overlay - ...", 1)
for k,v in pairs(t) do
dest[k] = v
end
end
return dest
end
-- Overlay tables directly onto one another, merging them together into something like a union.
-- Also overlays nested tables, but doesn't clone them (so a nested table may be added to dest).
-- Takes as many tables as required,
-- overlays them in passed order onto the first,
-- and returns the first table.
function tablex.deep_overlay(dest, ...)
assert:type(dest, "table", "tablex.deep_overlay - dest", 1)
for i = 1, select("#", ...) do
local t = select(i, ...)
assert:type(t, "table", "tablex.deep_overlay - ...", 1)
for k,v in pairs(t) do
if type(v) == "table" and type(dest[k]) == "table" then
tablex.deep_overlay(dest[k], v)
else
dest[k] = v
end
end
end
return dest
end end
--collapse the first level of a table into a new table of reduced dimensionality --collapse the first level of a table into a new table of reduced dimensionality

View File

@ -9,6 +9,79 @@ local tablex = require("batteries.tablex")
-- tablex {{{ -- tablex {{{
local function test_shallow_copy()
local x,r
x = { a = 1, b = 2, c = 3 }
r = tablex.shallow_copy(x)
assert:equal(r.a, 1)
assert:equal(r.b, 2)
assert:equal(r.c, 3)
x = { a = { b = { 2 }, c = { 3 }, } }
r = tablex.shallow_copy(x)
assert:equal(r.a, x.a)
end
local function test_deep_copy()
local x,r
x = { a = 1, b = 2, c = 3 }
r = tablex.deep_copy(x)
assert:equal(r.a, 1)
assert:equal(r.b, 2)
assert:equal(r.c, 3)
x = { a = { b = { 2 }, c = { 3 }, } }
r = tablex.deep_copy(x)
assert(r.a ~= x.a)
assert:equal(r.a.b[1], 2)
assert:equal(r.a.c[1], 3)
end
local function test_shallow_overlay()
local x,y,r
x = { a = 1, b = 2, c = 3 }
y = { c = 8, d = 9 }
r = tablex.shallow_overlay(x, y)
assert(
tablex.deep_equal(
r,
{ a = 1, b = 2, c = 8, d = 9 }
)
)
x = { b = { 2 }, c = { 3 }, }
y = { c = { 8 }, d = { 9 }, }
r = tablex.shallow_overlay(x, y)
assert(r.b == x.b)
assert(r.c == y.c)
assert(r.d == y.d)
assert(
tablex.deep_equal(
r,
{ b = { 2 }, c = { 8 }, d = { 9 }, }))
end
local function test_deep_overlay()
local x,y,r
x = { a = 1, b = 2, c = 3 }
y = { c = 8, d = 9 }
r = tablex.deep_overlay(x, y)
assert(
tablex.deep_equal(
r,
{ a = 1, b = 2, c = 8, d = 9 }))
x = { a = { b = { 2 }, c = { 3 }, } }
y = { a = { c = { 8 }, d = { 9 }, } }
r = tablex.deep_overlay(x, y)
assert(
tablex.deep_equal(
r,
{ a = { b = { 2 }, c = { 8 }, d = { 9 }, } }))
end
local function test_shallow_equal() local function test_shallow_equal()
local x,y local x,y
x = { a = { b = { 2 }, } } x = { a = { b = { 2 }, } }