Smallest Lua table copy

235

Substantially based on the guts of SoniEx2's 239 answer.

local o,k,F=type,next,{}for n=0,2 do
F[n]=function(a,r,t,G)if n<1 or o{}~=o(a)then return a end
t={}r=r or{}r[a]=n<2 and t G=F[n%2]for x,y in k,a do
t[r[x]or G(x,r)]=r[y]or G(y,r)end
return t end end
table.copy={shallow=F[2],deep=F[1]}

The advantage comes from using the same function prototype for both functions. Instantiating the same function prototype with different upvalues allows it to fill both roles.

Ungolfed:

-- ungolfed
-- note that type and next must have local copies to meet the spec
local o, k, F = type, next, {}
for n = 0, 2 do
  -- F[0] will be the identity function
  -- F[1] will be table.copy.deep
  -- F[2] will be table.copy.shallow
  F[n] = function(a, r, t, G)
    -- a is the table input
    -- r is the optional "custom recursion table" that is required
    -- t and G are just locals
    -- the spec implies (but does not state) that the global environment shouldn't be polluted
    -- r will only be used by recursive calls

    -- if n < 1, this is F[0], so act is the identity
    -- o is type, o{} is "table"
    -- if a is not a table, just return it
    if n < 1 or o{} ~= o(a) then
      return a
    end

    -- t will be the copy
    t = {}

    -- r will be the map that remembers which tables in the original map to which tables in the copy
    -- or, if it is passed in, it is a table that controls the behavior of the copy
    r = r or {}

    -- F[0] doesn't each here
    -- F[1] must add t to the map
    -- F[2] must not add t to the map
    -- (adding false will not hurt F[2] -- only true values will be picked up below)
    -- (behavior may not be exactly as desired for shallow copy, but spec doesn't require this feature)
    r[a] = n < 2 and t

    -- this is the function we will call to copy members
    -- for F[1] table.copy.deep, this is F[1] itself
    -- for F[2] table.copy.shallow, this is F[0] the identity
    -- (for F[0], we never get this far)
    -- the byte count seems equivalent making this a local vs putting it
    -- in both places it is used, but this is probably more efficient
    G=F[n%2]

    -- loop over and copy members
    -- first try r (which will only have non-1 entries for tables in F[1])
    -- then try G
    -- note that instead of calling "pairs" as usual, we can observe that pairs(a)
    -- is defined to return next, a, nil -- we use these (with implicit nil) directly
    for x, y in k, a do
      t[r[x] or G(x,r)] = r[y] or G(y,r)
    end

    return t
  end
end

-- export the functions as required
table.copy = {
  shallow = F[2],
  deep = F[1]
}

Hopefully I understood the spec correctly. This passes the provided test cases and there is enough similarity in the construction that I'm reasonably sure it does the same thing.

Note -- Initially I didn't understand the part about the "custom recursion table", but I have edited my answer to support it.

Note 2 -- Improved answer. I've also decided that the provided reference solution in 239 bytes does not exactly match my understanding of how the custom recursion table ought to work, since it cannot be used to add false into the copy. However, I believe my solution works as well as the provided one.