Lua:高效复制表(深度复制)
我试图高效地复制一个lua表。我编写了下面的函数copyTable(),该函数运行良好(见下文)。但我认为我可以使用函数的“传递值”机制来实现更高效的功能。我做了一些测试来探索这种机制:Lua:高效复制表(深度复制),lua,arguments,parameter-passing,deep-copy,pass-by-reference-value,Lua,Arguments,Parameter Passing,Deep Copy,Pass By Reference Value,我试图高效地复制一个lua表。我编写了下面的函数copyTable(),该函数运行良好(见下文)。但我认为我可以使用函数的“传递值”机制来实现更高效的功能。我做了一些测试来探索这种机制: function nop(x) return x end function noop(x) x={} return x end function nooop(x) x[#x+1]=4 return x end function copyTable(datatable) local
function nop(x)
return x
end
function noop(x)
x={}
return x
end
function nooop(x)
x[#x+1]=4
return x
end
function copyTable(datatable)
local tblRes={}
if type(datatable)=="table" then
for k,v in pairs(datatable) do tblRes[k]=copyTable(v) end
else
tblRes=datatable
end
return tblRes
end
tab={1,2,3}
print(tab) -->table: 0x1d387e0 tab={1,2,3}
print(nop(tab)) -->table: 0x1d387e0 tab={1,2,3}
print(noop(tab)) -->table: 0x1e76f90 tab={1,2,3}
print(nooop(tab)) -->table: 0x1d387e0 tab={1,2,3,4}
print(tab) -->table: 0x1d387e0 tab={1,2,3,4}
print(copyTable(tab)) -->table: 0x1d388d0
我们可以看到,除了noop()中我尝试对现有表进行根本性修改之外,对表的引用是通过函数(当我只是读取它或添加内容时)不变地传输的
我读了一篇文章,然后回答了问题。关于将或表作为参数传递,他们强调了“由ref传递的参数”和“由值传递的参数和表是引用”之间的区别,并举例说明了这种区别
但这究竟意味着什么?我们是否有引用的副本,但这与传递引用有什么区别,因为指向并因此操纵的数据仍然是相同的,而不是复制的?当我们试图影响表的nil时,noop()中的机制是特定的,是特定于避免删除表,还是在什么情况下触发它(我们可以看到noop()在修改表时并不总是这样)
我的问题是:传递表格的机制到底是如何工作的?有没有一种方法可以更有效地复制表中的数据,而不必承担我的copyTable的负担?传入Lua的参数规则类似于C:所有内容都是按值传递的,但表和用户数据是作为指针传递的。传递引用的副本在用法上并没有太大区别,但它与通过引用传递完全不同 例如,您专门提出了这一部分
function noop(x)
x={}
return x
end
print(noop(tab)) -->table: 0x1e76f90 tab={1, 2, 3}
您正在将新表[1]的值赋给变量x
(x
现在保存一个新指针值)。您没有修改原始表,选项卡
变量仍然保存指向原始表的指针值。从noop
返回时,将传回新表的值,该值为空。变量保存值,指针是值,而不是引用
编辑:
错过了你的另一个问题。不,如果要深度复制表,唯一的方法是使用类似于您编写的函数。当表变大时,深度复制非常慢。为了避免性能问题,您可以使用一种类似于“倒带表”的机制,它跟踪对它们所做的更改,以便在以后的时间点可以撤消这些更改(在具有回溯上下文的递归中非常有用)。或者,如果您只需要防止用户破坏表的内部结构,请编写一个“可自由使用”特性
[1] 假设
{}
语法是一个函数,它构造一个新表并返回指向新表的指针。如果您确定这3个假设(a)对“tab”(正在复制的表)有效:
t1 = {}
tab = {}
tab[t1] = value
t1 = {}
tab = {}
tab.a = t1
tab.b = t1
-- or
-- tab.a.b...x = t1
tab = {}
tab.a = tab
-- or
-- tab.a.b...x = tab
function copyTable(datatable)
local tblRes={}
if type(datatable)=="table" then
for k,v in pairs(datatable) do
tblRes[copyTable(k)] = copyTable(v)
end
else
tblRes=datatable
end
return tblRes
end
function copyTable(datatable, cache)
cache = cache or {}
local tblRes={}
if type(datatable)=="table" then
if cache[datatable] then return cache[datatable]
for k,v in pairs(datatable) do
tblRes[copyTable(k, cache)] = copyTable(v, cache)
end
cache[datatable] = tblRes
else
tblRes=datatable
end
return tblRes
end
如果A2不成立(即,您有重复的表值),那么您可以将代码更改为:
function copyTable(datatable)
local tblRes={}
if type(datatable)=="table" then
for k,v in pairs(datatable) do
tblRes[copyTable(k)] = copyTable(v)
end
else
tblRes=datatable
end
return tblRes
end
function copyTable(datatable, cache)
cache = cache or {}
local tblRes={}
if type(datatable)=="table" then
if cache[datatable] then return cache[datatable]
for k,v in pairs(datatable) do
tblRes[copyTable(k, cache)] = copyTable(v, cache)
end
cache[datatable] = tblRes
else
tblRes=datatable
end
return tblRes
end
不过,这种方法只有在有大量重复的大型表的情况下才有回报。因此,这是一个评估哪个版本对于您的实际生产场景更快的问题
如果A3不起作用(即,您有递归表),那么您的代码(以及上面的两个调整)将进入无限递归循环,并最终引发堆栈溢出
最简单的处理方法是保持回溯并在发生表递归时抛出错误:
function copyTable(datatable, cache, parents)
cache = cache or {}
parents = parents or {}
local tblRes={}
if type(datatable)=="table" then
if cache[datatable] then return cache[datatable]
assert(not parents[datatable])
parents[datatable] = true
for k,v in pairs(datatable) do
tblRes[copyTable(k, cache, parents)]
= copyTable(v, cache, parents)
end
parents[datatable] = false
cache[datatable] = tblRes
else
tblRes=datatable
end
return tblRes
end
我对处理递归表、保留原始结构的deepcopy函数的解决方案可以在这里找到:然后我理解了“通过ref传递的参数”和“通过值传递的参数以及表是引用传递的参数”之间的区别:只要我使用指针,例如通过读取x.foo或通过影响x.foo=“foo”,一切都非常类似于通过ref传递。ref的不同之处在于我可以通过x={}重新影响x,这是我不能用引用来做的。对吗?完全正确。引用是变量的别名,而不是具有相同值的变量。例如,在C++中,由于这个事实,不能将新对象指派给引用。但是如果使用指针,则可以更改指针,因为指针是值。这是一个微妙的区别,但却是有用的。Lua使用指针语义、递归和尾调用。这是你能得到的最有效的了。@warspyking:我能在速度和空间上获得更多吗?当我需要组装子表时,我应该如何“限定”尾部调用?有趣的是,有一个“开箱即用”的函数在各种上下文中执行deepcopy。你能解释一下函数deepCopy的变量(值、缓存、承诺、副本)或/和一个工作示例吗?