如何按值复制Lua表?

时间:2009-03-12 21:52:05

标签: lua lua-table

最近我写了一些Lua代码:

local a = {}
for i = 1, n do
   local copy = a
   -- alter the values in the copy
end

显然,这不是我想要做的,因为变量持有对匿名表的引用而不是Lua中表本身的值。这显然在Programming in Lua中列出,但我忘了它。

所以问题是我应该写什么而不是copy = a来获取a中的值的副本?

15 个答案:

答案 0 :(得分:42)

表副本有许多潜在的定义。这取决于您是想要简单还是深度复制,是否要复制,共享或忽略元数据等。没有单一的实现可以满足每个人。

一种方法是简单地创建一个新表并复制所有键/值对:

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仅迭代表键的一个子集(即连续的正整数键,从一个开始按升序排列)

答案 1 :(得分:30)

为了说明这一点,我的个人table.copy也关注元词:

function table.copy(t)
  local u = { }
  for k, v in pairs(t) do u[k] = v end
  return setmetatable(u, getmetatable(t))
end

没有足够广泛认同的复制功能被称为“标准”。

答案 2 :(得分:19)

要玩一些可读码高尔夫,这是一个处理标准棘手案例的简短版本:

  • 表格作为键,
  • 保留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

this gist中简要介绍了Lua深层复制操作。

另一个有用的参考是this Lua-users wiki page,其中包含一个如何避免__pairs元方法的示例。

答案 3 :(得分:12)

完整版的深层复制,处理所有3种情况:

  1. 表格循环参考
  2. 也是表格的键
  3. 元表
  4. 一般版本:

    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/CopyTableAlan Yates'函数。

答案 4 :(得分:10)

一个可选的深度,图形通用的递归版本:

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副本也应该是可选的?

答案 5 :(得分:6)

这就是我实际做的事情:

for j,x in ipairs(a) do copy[j] = x end

作为Doub mentions,如果您的表格密钥没有严格单调增加,则应该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

它通过递归调用(which is its own reward)来处理表和元表。其中一个聪明的部分是你可以传递任何值(无论是否是表),它将被正确复制。但是,成本是它可能会溢出堆栈。因此,可能需要更强大(非递归)function

但是,对于想要将数组复制到另一个变量的非常简单的情况来说,这太过分了。

答案 6 :(得分:4)

(遗憾的是,轻微记录的)stdlib项目对标准Lua发行版附带的几个库提供了许多有价值的扩展。其中有几个关于表复制和合并主题的变体。

此库也包含在Lua for Windows发行版中,应该是任何严重的Lua用户工具箱的一部分。

确保在手动执行此类操作时,有一点需要正确处理元数据。对于简单的表结构应用程序,您可能没有任何元表,使用pairs()的简单循环是可接受的答案。但是如果表被用作树,或包含循环引用,或者具有元表,那么事情会变得更加复杂。

答案 7 :(得分:4)

不要忘记函数也是引用,所以如果你想完全“复制”你需要的所有值来获得单独的函数;但是,我知道复制函数的唯一方法是使用loadstring(string.dump(func)),根据Lua参考手册,它不适用于具有upvalues的函数。

do
    local function table_copy (tbl)
        local new_tbl = {}
        for key,value in pairs(tbl) do
            local value_type = type(value)
            local new_value
            if value_type == "function" then
                new_value = loadstring(string.dump(value))
                -- Problems may occur if the function has upvalues.
            elseif value_type == "table" then
                new_value = table_copy(value)
            else
                new_value = value
            end
            new_tbl[key] = new_value
        end
        return new_tbl
    end
    table.copy = table_copy
end

答案 8 :(得分:2)

警告:标记的解决方案是 INCORRECT

当表包含表时,仍将使用对这些表的引用。我一直在寻找两个小时的错误,因为使用了上面的代码。

因此您需要检查值是否为表。如果是,你应该递归调用table.copy!

这是正确的table.copy函数:

function table.copy(t)
  local t2 = {};
  for k,v in pairs(t) do
    if type(v) == "table" then
        t2[k] = table.copy(v);
    else
        t2[k] = v;
    end
  end
  return t2;
end

注意:当表包含函数或其他特殊类型时,这也可能不完整,但这可能是我们大多数人不需要的。上述代码很容易适应需要它的人。

答案 9 :(得分:1)

这与基本表格一样好。如果需要复制带有元表的表,请使用类似deepcopy的东西。

答案 10 :(得分:1)

我认为Lua在其标准库中没有'table.copy()'的原因是因为任务定义不准确。如此处已经显示的那样,可以制作“一级深度”(你做过)的复制品,带有或不带有可能重复引用的深度复制。然后是metatables。

就个人而言,我仍然希望他们提供内置功能。只有当人们对其语义不满意时,他们才需要自己去做。但是,并非经常有一个实际上具有按值复制的需求。

答案 11 :(得分:1)

在大多数情况下,当我需要复制一个表时,我想要一个不与原始文件共享任何内容的副本,这样原始表的任何修改都不会对副本产生任何影响(反之亦然) )。

到目前为止显示的所有片段都无法为可能具有共享键或表的键的表创建副本,因为这些表将指向原始表。如果您尝试复制创建为a = {}; a[a] = a的表格,则很容易看到。 Jon引用的deepcopy函数负责处理,因此如果您需要创建真实/完整副本,则应使用deepcopy

答案 12 :(得分:1)

在这里使用penlight库: https://stevedonovan.github.io/Penlight/api/libraries/pl.tablex.html#deepcopy

local pl = require 'pl.import_into'()
local newTable = pl.tablex.deepcopy(oldTable)

答案 13 :(得分:0)

这可能是最简单的方法:

local data = {DIN1 = "Input(z)", DIN2 = "Input(y)", AINA1 = "Input(x)"}

function table.copy(mytable)  --mytable = the table you need to copy

    newtable = {}

    for k,v in pairs(mytable) do
        newtable[k] = v
    end
    return newtable
end

new_table = table.copy(data)  --copys the table "data"

答案 14 :(得分:0)

在我的情况下,当表中的信息只是数据和其他表(不包括函数,......)时,以下代码行是获胜的解决方案:

local copyOfTable = json.decode( json.encode( sourceTable ) )

我正在为Fibaro Home Center 2上的一些家庭自动化编写Lua代码.Lua的实现非常有限,没有您可以参考的中央函数库。每个函数都需要在代码中声明,以保持代码的可用性,因此像这样的一行解决方案是有利的。