【问题标题】:Lua : copying a table efficiently (deep copy)Lua:高效复制表(深拷贝)
【发布时间】:2017-06-29 22:42:05
【问题描述】:

我尝试高效地制作 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 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() 中我尝试对现有的进行彻底修改。

我阅读了Bas BossinkMichael Andersonthis Q/A 中的答案。关于传递或表作为参数,他们强调了“通过ref传递的参数”和“通过值和表传递的参数是引用”之间的区别,并举例说明了这种区别。

但这究竟是什么意思?我们是否有引用的副本,但是由于指向并因此被操纵的数据仍然是相同的,而不是复制的,这与通过 ref 有什么区别?当我们尝试将 nil 影响到 table 时,noop() 中的机制是否特定于避免删除 table 或在哪些情况下触发(我们可以通过 nooop() 看到,当表被修改)?

我的问题:传递表格的机制如何真正起作用?有没有一种方法可以更有效地复制表的数据而无需我的 copyTable 负担?

【问题讨论】:

  • 递归和尾调用。这将与您获得的一样高效。
  • @warspyking :在太空中,我会在速度上获得很多吗?当我需要组装子表时,我应该如何“有资格”进行尾调用?

标签: lua arguments parameter-passing deep-copy pass-by-reference-value


【解决方案1】:

Lua 中的参数传递规则与 C 类似:一切都是按值传递,但表和用户数据作为指针传递。传递引用的副本在用法上看起来并没有太大的不同,但它与通过引用传递完全不同。

例如,您专门提出了这部分。

function noop(x)
  x={}
  return x
end
print(noop(tab))      -->table: 0x1e76f90 tab={1, 2, 3}

您正在将新表[1] 的值分配给变量xx 现在拥有一个新的指针值)。您没有改变原始表,tab 变量仍然保存指向原始表的指针值。当您从noop 返回时,您将传回新表的值,该值是空的。 变量保存值,指针是值,而不是引用。

编辑:

错过了您的其他问题。不,如果你想深拷贝一个表,一个类似于你写的函数是唯一的方法。当表变大时,深拷贝非常很慢。为避免性能问题,您可以使用类似 “rewind tables” 的机制,它会跟踪对它们所做的更改,以便在以后的时间点撤消它们(在递归回溯上下文中非常有用) .或者,如果您只需要防止用户搞砸表格内部结构,请编写一个 "freezable" 特征。

[1] 想象一下{} 语法是一个构造新表并返回指向新表的指针的函数。

【讨论】:

  • 然后我明白了“ref 传递的参数”和“值和表传递的参数是引用”之间的区别:只要我使用指针,通过读取 x.foo 或通过影响 x。例如 foo="foo" ,一切都与通过 ref 传递非常相似。与 ref 的不同之处在于我可以重新影响 x,例如通过 x={},这是我无法用引用做的事情。正确吗?
  • 完全正确。引用是 variable 的别名,而不是具有相同值的变量。例如,在 C++ 中,由于这个事实,您不能将新对象分配给引用。但是,如果您改用指针,则可以更改指针,因为指针是值。这是一个微妙的区别,但很有用。 Lua 使用指针语义。
【解决方案2】:

如果您确定这 3 个假设 (A) 对“tab”(正在复制的表)有效:

  1. 没有表键

    t1 = {}
    tab = {}
    tab[t1] = value
    
  2. 没有重复的表值

    t1 = {}
    tab = {}
    tab.a = t1
    tab.b = t1
    -- or
    -- tab.a.b...x = t1
    
  3. 没有递归表:

    tab = {}
    tab.a = tab
    -- or
    -- tab.a.b...x = tab
    

那么你提供的代码是最小的并且几乎是尽可能高效的。

如果 A1 不成立(即您有表键),那么您必须将代码更改为:

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

如果 A2 不成立(即您有重复的表格值),那么您可以将代码更改为:

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 函数的解决方案,保留原始结构可以在这里找到:https://gist.github.com/cpeosphoros/0aa286c6b39c1e452d9aa15d7537ac95

【讨论】:

  • 有趣的是有一个“开箱即用”的功能来在各种上下文中执行深度复制。您能否解释一下函数 deepCopy(value, cache, promises, copies) 或/和一个工作示例的变量是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多