最近我写了一些Lua代码,如:
local a = {} for i = 1, n do local copy = a -- alter the values in the copy end
显然,这不是我想要做的,因为变量持有对匿名表的引用而不是Lua中表本身的值.Lua中的编程清楚地阐述了这一点,但我忘了它.
所以问题是我应该写什么而不是copy = a
获取值的副本a
?
表副本有许多潜在的定义.这取决于您是想要简单还是深度复制,是否要复制,共享或忽略元数据等.没有单一的实现可以满足每个人.
一种方法是简单地创建一个新表并复制所有键/值对:
function table.shallow_copy(t) local t2 = {} for k,v in pairs(t) do t2[k] = v end return t2 end copy = table.shallow_copy(a)
请注意,您应该使用pairs
而不是ipairs
,因为ipairs
只迭代表键的一个子集(即,以递增的顺序从一开始的连续正整数键).
为了说明这一点,我的个人table.copy
也关注metatables:
function table.copy(t) local u = { } for k, v in pairs(t) do u[k] = v end return setmetatable(u, getmetatable(t)) end
没有足够广泛认同的复制功能被称为"标准".
要玩一点可读代码高尔夫,这是一个处理标准棘手案例的简短版本:
表作为键,
保留metatables,和
递归表.
我们可以用7行来做到这一点:
function copy(obj, seen) if type(obj) ~= 'table' then return obj end if seen and seen[obj] then return seen[obj] end local s = seen or {} local res = setmetatable({}, getmetatable(obj)) s[obj] = res for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end return res end
在这个要点中简要介绍了Lua深层复制操作.
另一个有用的参考是这个Lua-users wiki页面,其中包含一个如何避免__pairs
metamethod的示例.
完整版的深层复制,处理所有3种情况:
表循环引用
键也是表
元表
一般版本:
local function deepcopy(o, seen) seen = seen or {} if o == nil then return nil end if seen[o] then return seen[o] end local no if type(o) == 'table' then no = {} seen[o] = no for k, v in next, o, nil do no[deepcopy(k, seen)] = deepcopy(v, seen) end setmetatable(no, deepcopy(getmetatable(o), seen)) else -- number, string, boolean, etc no = o end return no end
或表格版本:
function table.deepcopy(o, seen) seen = seen or {} if o == nil then return nil end if seen[o] then return seen[o] end local no = {} seen[o] = no setmetatable(no, deepcopy(getmetatable(o), seen)) for k, v in next, o, nil do k = (type(k) == 'table') and k:deepcopy(seen) or k v = (type(v) == 'table') and v:deepcopy(seen) or v no[k] = v end return no end
基于lua-users.org/wiki/CopyTable和Alan Yates的功能.
一个可选的深度,图形通用的递归版本:
function table.copy(t, deep, seen) seen = seen or {} if t == nil then return nil end if seen[t] then return seen[t] end local nt = {} for k, v in pairs(t) do if deep and type(v) == 'table' then nt[k] = table.copy(v, deep, seen) else nt[k] = v end end setmetatable(nt, table.copy(getmetatable(t), deep, seen)) seen[t] = nt return nt end
也许metatable副本也应该是可选的?
这是我实际做的:
for j,x in ipairs(a) do copy[j] = x end
作为DOUB提到,如果表项不严格单调递增的,它应该是pairs
没有ipairs
.
我还发现了一个deepcopy
更强大的功能:
function deepcopy(orig) local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[deepcopy(orig_key)] = deepcopy(orig_value) end setmetatable(copy, deepcopy(getmetatable(orig))) else -- number, string, boolean, etc copy = orig end return copy end
它通过递归调用自身来处理表和元表(这是它自己的奖励).其中一个聪明的部分是你可以传递任何值(无论是否是表),它将被正确复制.但是,成本是它可能会溢出堆栈.因此,可能需要更强大(非递归)的功能.
但是对于想要将数组复制到另一个变量的非常简单的情况来说,这太过分了.