63

最近我写了一些 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

4

15 回答 15

49

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

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

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仅迭代表键的子集(即连续的正整数键以递增顺序从一个开始)。

于 2009-03-13T09:51:30.167 回答
32

为了说明这一点,我个人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

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

于 2009-03-20T00:56:28.750 回答
28

为了玩一点可读的代码高尔夫,这里有一个处理标准棘手情况的简短版本:

  • 表作为键,
  • 保留元表,和
  • 递归表。

我们可以在 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

在这个 gist中有一个关于 Lua 深拷贝操作的简短描述。

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

于 2014-10-14T17:49:10.460 回答
19

完整版的深拷贝,处理所有 3 种情况:

  1. 表循环引用
  2. 也是表的键
  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/CopyTableAlan Yates的功能。

于 2013-04-18T08:07:57.263 回答
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

也许元表副本也应该是可选的?

于 2011-04-19T01:17:52.790 回答
7

这是我实际所做的:

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

它通过递归调用自身来处理表和元表(这是它自己的奖励)。一个聪明的地方是您可以将任何值传递给它(无论是否是表),它将被正确复制。但是,代价是它可能会溢出堆栈。因此,可能需要更健壮(非递归)的函数。

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

于 2009-03-12T21:52:19.613 回答
4

(不幸的是,文档很少)stdlib项目对标准 Lua 发行版附带的几个库进行了许多有价值的扩展。其中有几个关于表格复制和合并主题的变体。

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

手动实现这样的事情时要确保的一件事是正确处理元表。对于简单的表结构应用程序,您可能没有任何元表,简单的循环 usingpairs()是一个可以接受的答案。但是如果表被用作树,或者包含循环引用,或者有元表,那么事情就会变得更加复杂。

于 2009-03-13T21:19:37.547 回答
4

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

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
于 2013-06-26T15:25:05.533 回答
3

警告:标记的解决方案不正确

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

因此,您需要检查该值是否为表格。如果是,您应该递归调用 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

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

于 2013-09-24T07:55:10.070 回答
2

我认为 Lua 在其标准库中没有 'table.copy()' 的原因是因为任务定义不精确。正如这里已经显示的那样,您可以制作“一层深”的副本(您已经这样做了),一个带有或不关心可能重复引用的深拷贝。然后是元表。

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

于 2009-03-15T23:05:28.347 回答
1

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

于 2009-03-13T00:48:21.143 回答
1

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

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

于 2012-06-10T22:20:40.393 回答
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)
于 2016-01-31T18:29:53.863 回答
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"
于 2015-06-22T14:24:09.993 回答
-2

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

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

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

于 2016-10-02T23:31:10.740 回答