Lua中的表连接

56

原始帖子

鉴于Lua中没有内置函数,我正在寻找一种允许我将表格连接在一起的函数。我已经搜索了很多并尝试了我遇到的所有解决方案,但似乎没有一个能正常工作。

情景如下:我正在使用嵌入应用程序的Lua。 应用程序的内部命令以表格形式返回值列表。

我尝试的是在循环中递归调用该命令,并将返回的值(再次以表格形式)附加到上一次迭代的表格中。


编辑

对于将来查看此帖子的人,请注意@gimf发布的内容。由于Lua中的表格与数组非常相似(即使在列表上下文中也是如此),因此没有真正正确的方法将一个表格附加到另一个表格中。最接近的概念是合并表格。请参见帖子“Lua - merge tables?”以获得相关帮助。


可能是重复问题:https://dev59.com/A3M_5IYBdhLWcg3wp1Cl。 您提到“循环递归”。您是否在寻找深度复制+合并? - gimpf
以下是我找到的提供解决方案的链接:http://ardoris.wordpress.com/2008/08/10/lua-merge-two-tables-awesome3-rc2-config/http://www.idevgames.com/forum/archive/index.php/t-10223.html尽管我理解每个方法的思路,但似乎都不可行。您有可行的解决方案吗? - John Mark Mitchell
gimpf,也许我没有表述清楚。合并表格和连接表格虽然相似但是非常不同。我感兴趣的是将一个表格附加到另一个表格上,因此使用了连接这个词。 - John Mark Mitchell
请查看我的编辑;提供三个Lua表格的示例(2个输入,1个输出)将非常有帮助。 - gimpf
15个回答

39

回答是否有点过于复杂了?

这是我的实现:

function TableConcat(t1,t2)
    for i=1,#t2 do
        t1[#t1+1] = t2[i]
    end
    return t1
end

1
ipairs迭代加table.insert会更好吗(更易读和/或更快)? - Nas Banov
1
ipairs比普通的for循环略微昂贵,不使用它的原因是它不能保证表中项目的顺序。不使用insert的原因是它比在表上设置标准索引要显着昂贵,因为insert将调用一个例程,将从调用它的索引处向后推送表中的值,而在[#t+1]之后没有值,该例程仍然被调用,导致性能问题,在编译语言上没有区别,但在使用解释性语言时,我们必须小心询问计算机为我们做什么。 - Weeve Ferrelaine
9
据我所知,ipairs保证迭代顺序为for i=1 ...直到第一个t[i]==nil,对于非退化情况来说,这与for i=1,#t相同。关于insert和索引集合的比较,你是正确的-我进行了测量,性能差异为5-6倍。 - Nas Banov
9
注意:ipairs并不等同于pairs。其中的“i”是有原因的。“pairs”是无序的,“ipairs”则不是。 - Nas Banov
5
第三行不应该是 t1[#t1+i] = t2[i] 吗? - Dave Yarwood
显示剩余8条评论

27
如果您想将现有的表连接到新表中,这是最简洁的方法:
local t = {3, 4, 5}
local concatenation = {1, 2, table.unpack(t)}

尽管我不确定这在性能方面有多好。

15
请注意,这仅在 table.unpack 是最后一个参数时起作用(不像 Python 的 *t 或 JavaScript 的 ...t),请参见 https://dev59.com/Spffa4cB1Zd3GeqP50tj - user202729

15

还有一种方式:

for _,v in ipairs(t2) do 
    table.insert(t1, v)
end

在我看来,这个最易读 - 它迭代第二个表,并将其值附加到第一个表中,结束了。好奇它在速度上与上面的显式索引[]相比如何表现


不适用于t1={a={1},2} t2={3,d={4}} - rboy

8
您想要的方法很简单:
local t1 = {1, 2, 3, 4, 5}
local t2 = {6, 7, 8, 9, 10}

local t3 = {unpack(t1)}
for I = 1,#t2 do
    t3[#t1+I] = t2[I]
end

2
为什么要使用{unpack(t1)}?!它只是复制了t1,但问题暗示需要原地更新? - Nas Banov
2
@NasBanov 他问如何连接字符串。当您连接字符串时,会得到一个新的字符串。我假设他想要类似于这样的东西。 - warspyking
1
嗯,你对标题中的“连接”一词是正确的。但问题讨论的是“追加”,它是一种改变器。尽管如此,'{ unpack(tbl) }'对于克隆一个表格来说是个巧妙的技巧 - 但有限制(大约1M元素之类的)。 - Nas Banov
@NasBanov 然而,这种术语上的矛盾是提问者的问题,所以我认为接受任何答案都是有效和正确的。 - warspyking

5
要将两个表相加,请执行以下操作:
    ii=0
for i=#firsttable, #secondtable+#firsttable do
    ii=ii+1
    firsttable[i]=secondtable[ii]
end

使用第一个表格作为您想要添加的变量,代码将第二个表格添加到第一个表格的末尾。
  • i 是表格或列表的起始编号。
  • #secondtable+#firsttable 是结束位置。
它从您想要添加的第一个表格的末尾开始,并在第二个表格的末尾以 for 循环的方式结束,因此它适用于任何大小的表格或列表。

这是错误的。你必须从i=(#firsttable+1)开始,否则你会覆盖第一个表中的最后一个元素。如果第一个表为空,你甚至会尝试访问firsttable[0],但在lua中数组是从1开始索引的。 - scravy

4

通常情况下,在Lua中连接任意表的概念是没有意义的,因为一个键只能有一个值。

但也有一些特殊情况下可以进行连接操作。其中之一是包含简单数组的表格,这可能是一个旨在返回结果列表的函数的自然结果。

在这种情况下,您可以编写以下代码:

-- return a new array containing the concatenation of all of its 
-- parameters. Scaler parameters are included in place, and array 
-- parameters have their values shallow-copied to the final array.
-- Note that userdata and function values are treated as scalar.
function array_concat(...) 
    local t = {}
    for n = 1,select("#",...) do
        local arg = select(n,...)
        if type(arg)=="table" then
            for _,v in ipairs(arg) do
                t[#t+1] = v
            end
        else
            t[#t+1] = arg
        end
    end
    return t
end

这是一种浅拷贝,它没有尝试找出userdata或函数值是否是某种容器或对象,可能需要不同的处理。
另一种实现方式可能会修改第一个参数而不是创建一个新表。这将节省复制的成本,并使array_concat与字符串上的..运算符不同。 编辑:正如Joseph Kingry在评论中指出的那样,我未能正确提取...中每个参数的实际值。我还未能从函数中返回合并后的表。这就是我在答案框中编程而不进行任何测试的结果。

对于“函数的自然结果是返回结果列表”的想法表示赞同。这是非常可能的。 - gimpf
我认为这个函数有错误,我认为你需要在for之后再加一个select来获取...的实际值。请参考http://lua-users.org/wiki/VarargTheSecondClassCitizen中的第8个问题。 - Joseph Kingry
1
是的。显然在发布之前我没有测试这段代码,否则这个缺陷会很明显。现在回头看来,最后一个end前缺少了return t - RBerteig

2
如果您想合并两个表,但需要一个结果表的深层副本(无论出于什么原因),请使用来自另一个关于合并表的SO问题的合并方法再加上lua-users中的一些深层复制代码即可。请注意保留HTML标签。 (编辑) 好吧,也许您可以编辑您的问题提供一个最小的例子... 如果您的意思是一个表格。
 { a = 1, b = 2 }

与另一个表拼接

{ a = 5, b = 10 }

应该导致
{ a = 1, b = 2, a = 5, b = 10 }

如果你想要的是键值对列表,例如 { { a, 1 }, { b, 2 }, { a, 5 }, { b, 10 } },那么你可能会碰到麻烦。因为键必须是唯一的。根据你的应用场景,你也可以使用最终结构如 { a = { 1, 5 }, b = { 2, 10 } }
但是,在 Lua 表中简单地“连接”表的概念是没有意义的。

gimf,你是对的。我误解了在表格中使用列表的方式,认为它们可以简单地连接起来。进一步的测试让我得出结论,我真正需要做的是合并。感谢你对Lua新手的帮助和耐心。 - John Mark Mitchell
1
@John,我们都曾经是新手...从复杂的语言转换过来,Lua 的简单性中隐藏着多少力量有时会让人惊讶。理解它可能需要一些时间。 - RBerteig

2
这里是一个我实现的类似于RBerteig上面的实现,但使用了隐藏参数arg,当函数接收可变数量的参数时该参数是可用的。个人而言,与选择语法相比,我认为这更易读。
function array_concat(...)
    local t = {}

    for i = 1, arg.n do
        local array = arg[i]
        if (type(array) == "table") then
            for j = 1, #array do
                t[#t+1] = array[j]
            end
        else
            t[#t+1] = array
        end
    end

    return t
end

1

这是我实现的一组纯整数索引表的连接方法,供您参考。

  1. define a function to concatenate two tables, concat_2tables
  2. another recursive function concatenateTables: split the table list by unpack, and call concat_2tables to concatenate table1 and restTableList

    t1 = {1, 2, 3}
    t2 = {4, 5}
    t3 = {6}
    
    concat_2tables = function(table1, table2)
        len = table.getn(table1)
        for key, val in pairs(table2)do
            table1[key+len] = val
        end
        return table1
    end
    
    concatenateTables = function( tableList )
        if tableList==nil then
            return  nil
        elseif table.getn(tableList) == 1 then
            return  tableList[1]
        else
            table1 = tableList[1]
            restTableList = {unpack(tableList, 2)}
            return concat_2tables(table1, concatenateTables(restTableList))
        end
    end
    
    tt = {t1, t2, t3}
    t = concatenateTables(tt)  
    

1

编辑


这里有一个更好的解决方案,另一个解决方案往往会覆盖数字键,使用方法仍然相同:

function merge(...)
  local temp = {}
  local index = 1
  local result = {}
  
  math.randomseed(os.time())

  for i, tbl in ipairs({ ... }) do
    for k, v in pairs(tbl) do
      if type(k) == 'number' then
        -- randomize numeric keys
        k = math.random() * i * k
      end
      
      temp[k] = v
    end
  end
  
  for k, v in pairs(temp) do
    if type(k) == "number" then
      -- Sort numeric keys into order
      if result[index] then
        index = index + 1
      end
      
      k = index
    end
    
    result[k] = v
  end

  return result
end

原始内容


有点晚了,但这对我来说似乎有效:
function concat(...)
  local result = {}
  
  for i, tbl in ipairs({...}) do
    for k, v in pairs(tbl) do
      if type(k) ~= "number" then
        result[k] = v
      else
        result[i] = v
      end
    end
  end
  
  return result
end

这可能有点复杂,但它可以接受无限数量的参数,并且适用于键值对和常规“数组”(数字作为键)。这里是一个示例


这是唯一适用于具有关键字/索引t1={a={a=1}, b=2, 5}t2={c={c=3}, d=4, 6}表格的解决方案。 - rboy
然而请注意,当合并表格时,它不保持元素的顺序。 - rboy

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