如何合并两个表格并覆盖其中重复的元素?

90

我需要合并两个表格,如果某个项目在两个表格中都存在,第二个表格的内容将覆盖第一个表格的内容。我寻找了一下,但标准库好像没有提供这样的功能。请问在哪里可以获取此类函数?


1
没有一种单一的方法来做这件事。你是在比较表中的键还是值?键/值可能是其他表吗?... - Karl Voigtland
我不需要任何比较,但我需要子表格... - RCIX
10个回答

128
for k,v in pairs(second_table) do first_table[k] = v end

等等,那会捕获字符串键之类的吗? - RCIX
1
这应该可以通过一些调整来处理子表。谢谢! - RCIX
32
如果索引相同,这是否会覆盖表中已有的内容,就像在数组中一样?如果你有两个带有数字键[1],[2],[3]等的表格,其中一个已经有数据,而你仅迭代第二个表格的键,也是[1],[2],[3]等,并将具有相同键的数据添加到第一个表格中相同位置,那么你将覆盖原来的任何内容。你可以使用first_table[#first_table + k] = v来解决这个问题。就我个人而言,我会使用tables.insert()来处理,不过我不确定它在2009年是否可用! - Astridax
我猜这就是最大紧凑性的代价。我对此没有问题,但阅读这需要我在脑海中解析、编译和执行这个循环!在其他语言中,你只需要记住一种内置的方法,比如 Python 的 dict.update() - Tomasz Gandor
2
在99%的情况下,这将是正确的解决方案,但请记住,在引用其他表的表格应用程序中(这在面向对象模式中很常见),您可能需要进行额外的簿记。例如,如果有某些原因导致给定的表格直接引用自身,则需要检查v==second_table,如果是,则分配first_table。表格也可以间接地引用自身,但这可能需要解决一本书的问题。我只是想把这个放在雷达上,所以说得够了。 - Aiken Drum
3
@Astridax,这就是提问者所要求的。 - Moberg

23

根据Doug Currie的回答,这是我得出的结果:

function tableMerge(t1, t2)
    for k,v in pairs(t2) do
        if type(v) == "table" then
            if type(t1[k] or false) == "table" then
                tableMerge(t1[k] or {}, t2[k] or {})
            else
                t1[k] = v
            end
        else
            t1[k] = v
        end
    end
    return t1
end

6
请注意,通常不建议更改 Lua 的标准“命名空间”(如 table.*)。最好创建自己的命名空间。 - Alexander Gladysh
4
“if not t1[k] then t1[k] = {} end”中包含一个微妙的错误(找出来!),更好的写法是“t1[k] = t1[k] or {}”。如果t2[k]是一个表,但t1[k]存在但不是一个表,会发生什么?最后,“table1[k] = v”应该改为“t1[k] = v”。 - lhf
1
tableMerge(t1[k] or {}, t2[k] or {}) 会将t2合并到一个新表中,然后丢弃该表。也许应该改为 t1[k] = tableMerge(t1[k] or {}, t2[k] or {}) - idbrii

14

这个代码不应该正常工作吗?


function merge(t1, t2)
    for k, v in pairs(t2) do
        if (type(v) == "table") and (type(t1[k] or false) == "table") then
            merge(t1[k], t2[k])
        else
            t1[k] = v
        end
    end
    return t1
end

3
是的,但是如果你看一下RCIX的原始帖子,里面有一些不同的逻辑,后来简化为两个相同的“else”语句。它应该进一步简化为你在这里看到的内容。 - BMitch
4
有人能解释一下为什么这里需要t1[k] or false吗? - what the

8

对于数字索引表的合并:

for k,v in pairs(secondTable) do table.insert(firstTable, v) end

3
这个解决方案不是错的吗(因为“pairs”不能保证按数字顺序返回元素)?此外,“table.insert”效率低下,请参见https://dev59.com/LXM_5IYBdhLWcg3wWR2V#15278426(还可以查看其他答案)。 - user202729

5
Doug Currie的回答对于大多数情况来说是最简单的。如果需要更强大的表格合并功能,请考虑使用Penlight库中的merge()方法。
require 'pl'
pretty.dump(tablex.merge({a=1,b=2}, {c=3,d=4}, true))

-- {
--   a = 1,
--   d = 4,
--   c = 3,
--   b = 2
-- }

顺便问一下,第三个参数是什么? - Tomasz Gandor
1
第三个参数决定了表格如何合并:true表示联合,false表示交集。https://github.com/stevedonovan/Penlight/blob/master/lua/pl/tablex.lua#L554 - Blackcoat

4
这是深度合并的迭代版本,因为我不喜欢递归可能导致堆栈溢出的情况。
local merge_task = {}
function merge_to_left_o(orig, new)
   merge_task[orig] = new

   local left = orig
   while left ~= nil do
      local right = merge_task[left]
      for new_key, new_val in pairs(right) do
         local old_val = left[new_key]
         if old_val == nil then
            left[new_key] = new_val
         else
            local old_type = type(old_val)
            local new_type = type(new_val)
            if (old_type == "table" and new_type == "table") then
               merge_task[old_val] = new_val
            else
               left[new_key] = new_val
            end
         end
      end
      merge_task[left] = nil
      left = next(merge_task)
   end
end

2
我的评论是讽刺意味的:如果你因为担心/预期堆栈溢出而逃避递归;然后不检查你的任务容器是否溢出内存,那么逃避递归就没有什么意义了。 - Irfy
2
@Irfy,“通用”的内存用于Lua中的表或其他语言中的类似结构通常比调用堆栈空间更为充裕。当然,应该考虑哪种解决方案适用于特定环境,而不是盲目地复制/粘贴其中一个。 - Oleg V. Volkov

3

我喜欢James版本的简洁性,并在我的utils.lua中使用它- 我添加了一个检查表类型以进行错误处理。

function merge(a, b)
    if type(a) == 'table' and type(b) == 'table' then
        for k,v in pairs(b) do if type(v)=='table' and type(a[k] or false)=='table' then merge(a[k],v) else a[k]=v end end
    end
    return a
end

感谢您提供的这种好的功能,它应该成为表格类的一部分,这样您就可以调用 a:merge(b) ,但是使用 table.merge = function(a, b) ... 对我来说不起作用。甚至可以压缩为一行代码给真正的极客 :)

2

像Doug Currie所说,你可以使用他的函数,但他的方法存在问题。如果first_table在其k索引中有内容,则该函数将覆盖它。

我假设您正在尝试合并这些表格,而不是覆盖索引和值。因此,这将是我的方法,它非常类似,但用于合并表格。

for _, v in pairs(second_table) do table.insert(first_table, v) end

这个解决方案唯一的问题是索引设置为数字,而不是字符串。这将适用于索引为数字的表,对于索引为字符串的表,请使用Doug Currie的方法。

Doug Currie的方法:

for k,v in pairs(second_table) do first_table[k] = v end

你的方法是插入一个新的 v 并自动递增编号,对吗? - VimNing
早些时候在 Jimmy Liu 的回答中,评论区有人发表了如下言论:“这个解决方案是错误的(因为pairs不能保证按照数字顺序返回元素)。而且,table.insert 是低效的,请参见 https://dev59.com/LXM_5IYBdhLWcg3wWR2V#15278426(还可以查看其他答案)。”(来自 user202729 的评论) - Lazerbeak12345

1

在扩展这个很棒的答案https://dev59.com/A3M_5IYBdhLWcg3wp1Cl#1283399的基础上,我想采用一个(纯)函数式方法,就像下面这个例子:

-- example values
local t1 = { a = 0, b = 2 }
local t2 = { a = 1, c = 3 }

-- merge function that takes functional approach

local merge = function(a, b)
    local c = {}
    for k,v in pairs(a) do c[k] = v end
    for k,v in pairs(b) do c[k] = v end
    return c
end

-- t1 and t2 value still same after merge
print(merge(t1, t2)) -- { a = 1, b = 2, c = 3 }
print(t2) -- { a = 1, c = 3 }
print(t1) -- { a = 0, b = 2 }


-1
for k,v in pairs(t2) do t1[k] = v end

字符串解决方案的关键


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接