【问题标题】:How can I structurally deep-compare 2 lua tables which may contain cyclic references, where keys of a table may themselves be tables?如何在结构上深度比较 2 个可能包含循环引用的 lua 表,其中表的键本身可能是表?
【发布时间】:2018-10-03 10:41:41
【问题描述】:

这个问题类似于之前发布的问题How can I deep-compare 2 Lua tables, which may or may not have tables as keys?

问题是,那里的解决方案非常适合简单的深度比较。但是,它不能正确处理循环引用。更具体地说,如下:

function table_eq(table1, table2)
   local avoid_loops = {}
   local function recurse(t1, t2)
      -- compare value types
      if type(t1) ~= type(t2) then return false end
      -- Base case: compare simple values
      if type(t1) ~= "table" then return t1 == t2 end
      -- Now, on to tables.
      -- First, let's avoid looping forever.
      if avoid_loops[t1] then return avoid_loops[t1] == t2 end
      avoid_loops[t1] = t2
      -- Copy keys from t2
      local t2keys = {}
      local t2tablekeys = {}
      for k, _ in pairs(t2) do
         if type(k) == "table" then table.insert(t2tablekeys, k) end
         t2keys[k] = true
      end
      -- Let's iterate keys from t1
      for k1, v1 in pairs(t1) do
         local v2 = t2[k1]
         if type(k1) == "table" then
            -- if key is a table, we need to find an equivalent one.
            local ok = false
            for i, tk in ipairs(t2tablekeys) do
               if table_eq(k1, tk) and recurse(v1, t2[tk]) then
                  table.remove(t2tablekeys, i)
                  t2keys[tk] = nil
                  ok = true
                  break
               end
            end
            if not ok then return false end
         else
            -- t1 has a key which t2 doesn't have, fail.
            if v2 == nil then return false end
            t2keys[k1] = nil
            if not recurse(v1, v2) then return false end
         end
      end
      -- if t2 has a key which t1 doesn't have, fail.
      if next(t2keys) then return false end
      return true
   end
   return recurse(table1, table2)
end


local t1 = {}

t1[t1]=t1
t1.x = {[t1] = {1, 2, 3}}

local t2 = {}
local t3 = {}

t2[t3]=t2
t3[t2]=t3
t2.x = {[t3] = {1, 2, 3}}
t3.x = {[t2] = {1, 2, 3}}

print(table_eq(t1, t2))
--[[>
lua: deeptest.lua:15: stack overflow
stack traceback:
    deeptest.lua:15: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    ...
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:26: in function <deeptest.lua:3>
    (...tail calls...)
    deeptest.lua:62: in main chunk
    [C]: in ?
--]]

产生堆栈溢出。如果不是因为堆栈溢出,它可能会产生误报(不是我可以测试的)。

我该如何处理这种情况? (它甚至可以处理吗?当我想到它时,这听起来像是计算机科学中一个未解决的问题......但我对此知之甚少)


当我说“结构平等”时,我指的是下表:

local t = {}
t[{}] = 1
t["1"] = {}

在结构上与下表不同:

local t = {}
local t2 = {}
t[t2] = 1
t["1"] = t2

而在“内容平等”中,它们是平等的。


测试用例:

local t1 = {}

t1[t1]=t1
t1.x = {[t1] = {1, 2, 3}}

local t2 = {}
local t3 = {}

t2[t3]=t2
t3[t2]=t3
t2.x = {[t3] = {1, 2, 3}}
t3.x = {[t2] = {1, 2, 3}}

assert(table_eq(t1, t2) == false)
assert(table_eq(t2, t3) == true)

local t4 = {}
t4[{}] = 1
t4["1"] = {}

local t5 = {}
local t6 = {}
t5[t6] = 1
t5["1"] = t6

assert(table_eq(t4, t5) == false)

【问题讨论】:

  • 你能给出你对平等的定义吗?从您的示例中,我无法理解 table_eq(t2, t3) 的正确答案应该是什么。
  • 也许一种选择是将所有对象推入一个表中,然后断言它们都是浅相等的(以消除误报),然后进行深度比较?该解决方案可能仍然存在误报,但很容易使用当前算法。
  • @EgorSkriptunoff table_eq(t2, t3) 应该是...未定义。 t2 和 t3 指向同一对象的不同部分(即它们相互引用)。只有table_eq(t1, t2)(即具有完全隔离表的table_eq)才是重要的/应该是确定性的。
  • 仍在尝试理解预期的比较... 浅比较足以确定两个表具有相同的键值对。看来您想要某种结构比较;包括,也许所有的空表都是等价的?
  • @TomBlodget 否。只有在系统内的引用相同时,空表才等同于空表。也就是说,给定两个空表 t1 和 t2,table_eq(t1, t2) 应该为真,但table_eq({a=t1, b=t2}, {a=t2, b=t2}) 应该为假。

标签: algorithm lua compare structural-equality


【解决方案1】:

通过更改此示例,可以轻松修复此示例中的堆栈溢出:

-- if key is a table, we need to find an equivalent one.
        local ok = false
        for i, tk in ipairs(t2tablekeys) do
           if table_eq(k1, tk) and recurse(v1, t2[tk]) then

到:

-- if key is a table, we need to find an equivalent one.
        local ok = false
        for i, tk in ipairs(t2tablekeys) do
           if recurse(k1, tk) and recurse(v1, t2[tk]) then

前 2 个测试用例返回 true,但第 3 个测试用例失败,因为作为键的表是内容与内容的比较。如果您希望测试密钥是否与实际表相同,则需要将其更改为:

-- if key is a table, we need to find an equivalent one.
        local ok = false
        for i, tk in ipairs(t2tablekeys) do
           if k1 == tk and recurse(v1, t2[tk]) then

但是请记住,第二个测试用例将失败,因为您希望将表格作为键来比较内容与内容,而不是实际表格。

您的测试用例在您认为“相等”的问题上相互矛盾,因此没有真正的答案。

附言

此函数忽略元数据,这也可以与 __eq 元方法进行比较。所以无论如何它仍然不是一个完整的比较。

【讨论】:

  • 他们并不矛盾。只是您只查看“内容”与“实例”。但我要找的其实不是,而是“结构”。
  • Lua 表没有任何结构,实际上它应该是这样的。所以这是不可能的。您可以使用某些东西对表格进行“排序”并尝试以这种方式比较结构。
  • Lua 表没有结构是什么意思?它们是键值对集合。它们被称为数据结构是有原因的。
  • 如果你不想要帮助也没关系,不要做一个聪明人。您只能通过 2 种方式比较表格,内容或地址。如果您想比较用于创建表的变量名称,您应该阅读它们是什么。因为你给他们不同的名字,但他们代表完全相同的东西。
  • 你还可以比较结构,它是内容和地址的混合,以一种非常具体的方式。在同一个表中,您比较地址,但跨表比较内容。正如我所说,看这里:sprunge.us/QFeM 它显示了单元测试中一些表的结构
猜你喜欢
  • 1970-01-01
  • 2018-03-26
  • 1970-01-01
  • 2011-08-17
  • 1970-01-01
  • 2012-01-06
  • 2022-09-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多