如何按值复制Lua表?

如何按值复制Lua表?,lua,lua-table,Lua,Lua Table,最近我写了一些Lua代码,比如: local a = {} for i = 1, n do local copy = a -- alter the values in the copy end 显然,这不是我想要做的,因为变量包含对匿名表的引用,而不是Lua中表本身的值。这是清楚地列在,但我忘记了 所以问题是我应该写什么而不是copy=a来获得a中值的副本?以下是我实际做的: for j,x in ipairs(a) do copy[j] = x end 同样,如果表键不是严格单

最近我写了一些Lua代码,比如:

local a = {}
for i = 1, n do
   local copy = a
   -- alter the values in the copy
end
显然,这不是我想要做的,因为变量包含对匿名表的引用,而不是Lua中表本身的值。这是清楚地列在,但我忘记了


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

以下是我实际做的:

for j,x in ipairs(a) do copy[j] = x end
同样,如果表键不是严格单调递增的,那么它应该是
而不是
ipairs

我还发现了一个更健壮的函数:

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
它通过递归调用自身()来处理表和元表。聪明的一点是,您可以向它传递任何值(无论是否为表),它都将被正确复制。然而,代价是它可能会溢出堆栈。因此,可能需要更健壮的(非递归的)


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

这与基本表一样好。如果需要使用元表复制表,请使用类似deepcopy的方法。

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

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

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)
请注意,您应该使用
而不是
ipairs
,因为
ipairs
仅迭代表键的子集(即,以递增顺序从一开始的连续正整数键)。

该项目对标准Lua发行版附带的几个库进行了许多有价值的扩展。其中有几个关于表复制和合并主题的变体

这个库也包含在发行版中,并且可能是任何严肃的Lua用户工具箱的一部分


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

我认为Lua在其标准库中没有“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

没有被广泛认可为“标准”的复制函数。

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

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

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

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


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

完整版本的deepcopy,处理所有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.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
    
    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
    

    基于“和”函数。

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

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

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

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

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

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

    • 表作为键
    • 保存元表,以及
    • 递归表
    我们可以在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深度复制操作的简短总结


    另一个有用的参考是,其中包括一个关于如何避免
    \u对
    元方法的示例。

    这可能是最简单的方法:

    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"
    
    在此处使用penlight库:


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

    local copyOfTable = json.decode( json.encode( sourceTable ) )
    
    我正在为Fibaro home Center 2上的一些家庭自动化编写Lua代码。Lua的实现非常有限