最近我写了一些 Lua 代码,如下所示:

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

显然,这不是我想要做的,因为变量保存对匿名表的引用,而不是 Lua 中表本身的值。这明确规定在 Lua 编程, ,但我已经忘记了。

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

有帮助吗?

解决方案

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

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

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

有一篇关于 Lua 深复制操作的简短文章 这个要点.

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

其他提示

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

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

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 开始按递增顺序的连续正整数键)。

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

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

深拷贝的完整版本,处理所有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/CopyTable'沙 艾伦·耶茨' 功能。

可选的深度、通用图形、递归版本:

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

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

这是我实际做的:

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

作为 杜布提到, ,如果你的表键不是严格单调递增的,那么应该是 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

它通过递归调用自身来处理表和元表(这是它自己的奖励)。聪明的一点是,您可以向它传递任何值(无论是否是表),并且它将被正确复制。然而,代价是它可能会溢出堆栈。因此甚至更加稳健(非递归) 功能 可能需要。

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

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

该库也包含在 Windows 下的 Lua 发行版,并且可能应该成为任何认真的 Lua 用户工具箱的一部分。

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

不要忘记函数也是引用,因此如果您想完全“复制”所有值,您也需要获取单独的函数;但是,我知道复制函数的唯一方法是使用 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

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

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

所以你需要检查该值是否是一个表。如果是,你应该递归调用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

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

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

我认为Lua的标准库中没有'table.copy()'的原因是因为任务定义不精确。如此处所示,人们可以制作“一层深”的副本(您所做的),即考虑或不考虑可能的重复引用的深层副本。然后是元表。

就我个人而言,我仍然希望他们提供内置功能。只有当人们对其语义不满意时,他们才需要自己去做。不过,真正有按值复制需求的情况并不常见。

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

到目前为止显示的所有片段都无法为可能具有共享密钥或与表共享密钥的表创建副本,因为这些密钥将继续指向原始表。如果您尝试复制创建的表,很容易看出: a = {}; a[a] = a. 深复制 Jon 引用的函数可以解决这个问题,所以如果您需要创建真实/完整的副本, deepcopy 应该使用。

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

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

这可能是最简单的方法:

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"

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top