Lua:有效地复制表格(深层复制)

时间:2017-02-11 17:14:06

标签: 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 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 Bossink中阅读了Michael Andersonthis Q/A的回答。关于传递或表作为参数,他们强调了"参数传递的参数之间的区别。和"由值和表传递的参数是引用"以及出现这种差异的例子。

但那究竟是什么意思呢?我们是否有一个引用的副本,但是由于数据指向并因此被操作仍然是相同的,而不是复制,因此与传递引用有什么不同?当我们尝试影响表的nil时,noop()中的机制是否具体,具体是为了避免删除表或在哪种情况下触发(我们可以看到nooop(),当它不是总是如此表被修改了吗?

我的问题:传递表的机制是如何工作的?有没有办法在没有copyTable负担的情况下更有效地复制表的数据?

2 个答案:

答案 0 :(得分:1)

Lua中的参数传递规则与C类似: 所有 都是按值传递的,但表和userdata作为指针传递。传递引用的副本在使用上看起来并没有那么不同,但它与通过引用传递完全不同。

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

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

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

编辑:

错过了你的另一个问题。不,如果你想深度复制一个表,一个类似你所写的函数是唯一的方法。当表变大时,深层副本非常慢。为了避免性能问题,您可以使用"倒带表" 这样的机制,跟踪对它们所做的更改,以便在以后的某个时间点撤消它们(非常有用)用回溯上下文递归)。或者,如果您只是想让用户不要使用表格内部,请写一个" freezable" 特征。

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

答案 1 :(得分:0)

如果您确定这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
    
  4. 然后,您提供的代码是最小的,几乎尽可能高效。

    如果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
    

    我可以在此处找到处理递归表的深层复制函数的解决方案,保留原始结构:https://gist.github.com/cpeosphoros/0aa286c6b39c1e452d9aa15d7537ac95