如何在Lua中检查两个表(对象)是否具有相同的值

25

我想在Lua中检查两个表格是否具有相同的值,但没有找到方法。

我使用运算符==,它似乎只检查相同的对象,而不是表格中的元素。

如果我有两个表格,

a={}
b={}

a==b的值为false

但是如果

a={}
b=a

a==b的值为true

我想知道在Lua中是否有一种方法可以检查两个表是否具有相同的元素。是否有像table.equals()这样的内置函数进行检查?


请记住这两种情况实际上是不同的。在第二种情况下,如果您执行a.test=true,那么b.test将保持不变。 - Ivo
1
理解它的关键在于Lua通过引用而非内容比较表格。你需要自己比较内容。 - Yu Hao
好的,我明白了。我在想Python中可能有不同的运算符,比如'=='和'is'。我只是想比较两个简单表格之间的差异。所以我先将它们转换为JSON格式,然后再比较字符串。谢谢。 - LetsOMG
1
请注意,通常比较JSON字符串是一个糟糕的想法,因为字典是无序的,而且空格不是强制性的。简而言之:不要比较JSON字符串。 - ratijas
6个回答

12

我为Rutrus的解决方案提供一些改进。

---@param o1 any|table First object to compare
---@param o2 any|table Second object to compare
---@param ignore_mt boolean True to ignore metatables (a recursive function to tests tables inside tables)
function equals(o1, o2, ignore_mt)
    if o1 == o2 then return true end
    local o1Type = type(o1)
    local o2Type = type(o2)
    if o1Type ~= o2Type then return false end
    if o1Type ~= 'table' then return false end

    if not ignore_mt then
        local mt1 = getmetatable(o1)
        if mt1 and mt1.__eq then
            --compare using built in method
            return o1 == o2
        end
    end

    local keySet = {}

    for key1, value1 in pairs(o1) do
        local value2 = o2[key1]
        if value2 == nil or equals(value1, value2, ignore_mt) == false then
            return false
        end
        keySet[key1] = true
    end

    for key2, _ in pairs(o2) do
        if not keySet[key2] then return false end
    end
    return true
end

请注意,这个解决方案没有考虑自引用的情况。你可以使用pequals(下面的方法)。当你的代码中有一些技巧时,它非常有用。 但是不要将这种方法用于常规检查!它会更慢。而且如果你的对象存在自引用,你应该重新分析你的结构。自引用可能是糟糕架构的一个迹象。
local function internalProtectedEquals(o1, o2, ignore_mt, callList)
    if o1 == o2 then return true end
    local o1Type = type(o1)
    local o2Type = type(o2)
    if o1Type ~= o2Type then return false end
    if o1Type ~= 'table' then return false end

    -- add only when objects are tables, cache results
    local oComparisons = callList[o1]
    if not oComparisons then
        oComparisons = {}
        callList[o1] = oComparisons
    end
    -- false means that comparison is in progress
    oComparisons[o2] = false

    if not ignore_mt then
        local mt1 = getmetatable(o1)
        if mt1 and mt1.__eq then
            --compare using built in method
            return o1 == o2
        end
    end

    local keySet = {}
    for key1, value1 in pairs(o1) do
        local value2 = o2[key1]
        if value2 == nil then return false end

        local vComparisons = callList[value1]
        if not vComparisons or vComparisons[value2] == nil then
            if not internalProtectedEquals(value1, value2, ignore_mt, callList) then
                return false
            end
        end

        keySet[key1] = true
    end

    for key2, _ in pairs(o2) do
        if not keySet[key2] then
            return false
        end
    end

    -- comparison finished - objects are equal do not compare again
    oComparisons[o2] = true
    return true
end

function pequals(o1, o2, ignore_mt)
    return internalProtectedEquals(o1, o2, ignore_mt, {})
end

另外,您可以分析lua wiki上的CompareTables


什么是 ignore_mt? - Derzu
这应该是更好的解决方案! - Zachary

11

在SO上,我们更喜欢将答案复制到回答中。 - TamaMcGlinn

11
如果您想测试简单的表格,请尝试以下内容...
function do_tables_match( a, b )
    return table.concat(a) == table.concat(b)
end

另外,有一个与您的具体示例相似的内容,涉及到IT技术。
function is_table_empty( table_to_test )
    -- Doesn't work
    return table_to_test == {}
    -- Works only if the table is numeric keyed with no gaps
    return #table_to_test = 0 
    -- Works!
    return next( table_to_test ) ~= nil 
end

1
很遗憾,这对于具有键/值的表格不起作用,仅适用于索引值。 - rboy

3

顺便提一下,我检查了@lhf的链接发现它已经失效了,我找到了这个有用的例子:

function is_table_equal(t1,t2,ignore_mt)
   local ty1 = type(t1)
   local ty2 = type(t2)
   if ty1 ~= ty2 then return false end
   -- non-table types can be directly compared
   if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end
   -- as well as tables which have the metamethod __eq
   local mt = getmetatable(t1)
   if not ignore_mt and mt and mt.__eq then return t1 == t2 end
   for k1,v1 in pairs(t1) do
      local v2 = t2[k1]
      if v2 == nil or not is_table_equal(v1,v2) then return false end
   end
   for k2,v2 in pairs(t2) do
      local v1 = t1[k2]
      if v1 == nil or not is_table_equal(v1,v2) then return false end
   end
   return true
end

什么是 ignore_mt? - Derzu
2
这是一个布尔值,如果您想忽略megatables,则可以使用它(一个递归函数来测试表格内的表格)。 - Rutrus
只需添加引用链接:https://web.archive.org/web/20131225070434/http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3 - Zachary

1

我目前使用这个

local tableCompare
do
    local compare
    compare = function(src, tmp, _reverse)
        if (type(src) ~= "table" or type(tmp) ~= "table") then
            return src == tmp
        end

        for k, v in next, src do
            if type(v) == "table" then
                if type(tmp[k]) ~= "table" or not compare(v, tmp[k]) then
                    return false
                end
            else
                if tmp[k] ~= v then
                    return false
                end
            end
        end
        return _reverse and true or compare(tmp, src, true)
    end
    tableCompare = function(src, tmp, checkMeta)
        return compare(src, tmp) and (not checkMeta or compare(getmetatable(src), getmetatable(tmp)))
    end
end

print(tableCompare({ 1 , b = 30 }, { b = 30, 1 }, false))

0

如果你只想比较两个小表格,你可以(滥用)inspect

local ins = require 'inspect'
local assert_equal = require 'luassert' .equal

assert_equal(ins({ 1 , b = 30 }), ins({ b = 30, 1 }))

这种方法利用了inspect在序列化对象时对表元素进行排序的优势,而cjson则不会,这使得它在这种情况下无法使用。


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