【问题标题】:How can I deep-compare 2 Lua tables, which may or may not have tables as keys?如何深度比较 2 个 Lua 表,它们可能有也可能没有表作为键?
【发布时间】:2018-10-03 10:34:16
【问题描述】:

(也发布在 Lua 邮件列表中)

所以我一直在编写深拷贝算法,我想测试它们,看看它们是否按我希望的方式工作。虽然我确实可以访问 original->copy 映射,但我想要一个通用的深度比较算法,它必须能够比较表键(表作为键?)。

我的深拷贝算法在这里可用:https://gist.github.com/SoniEx2/fc5d3614614e4e3fe131(它不是很有条理,但有 3 个,一个使用递归调用,另一个使用 todo 表,另一个模拟调用堆栈(以一种非常丑陋但兼容 5.1 的方式))

递归版本:

local function deep(inp,copies)
  if type(inp) ~= "table" then
    return inp
  end
  local out = {}
  copies = (type(copies) == "table") and copies or {}
  copies[inp] = out -- use normal assignment so we use copies' metatable (if any)
  for key,value in next,inp do -- skip metatables by using next directly
    -- we want a copy of the key and the value
    -- if one is not available on the copies table, we have to make one
    -- we can't do normal assignment here because metatabled copies tables might set metatables

    -- out[copies[key] or deep(key,copies)]=copies[value] or deep(value,copies)
    rawset(out,copies[key] or deep(key,copies),copies[value] or deep(value,copies))
  end
  return out
end

编辑:我发现这样的东西并不能真正将表作为键处理:http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3(下面是 sn-p 的副本)

function deepcompare(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 deepcompare(v1,v2) then return false end
  end
  for k2,v2 in pairs(t2) do
    local v1 = t1[k2]
    if v1 == nil or not deepcompare(v1,v2) then return false end
  end
  return true
end

序列化也不是一种选择,因为序列化的顺序是“随机的”。

【问题讨论】:

  • 这里的问题到底是什么?这些有用吗?他们在某些情况下会失败吗?我也不确定我是否完全理解你想要什么。您希望能够将两个任意表相互比较吗?我认为,确保你用尽两边桌子上的钥匙是困难的。
  • @EtanReisner 我没有简单的方法来测试它们是否工作,除了漂亮地打印输入和输出然后手动比较它们。如果我有一种自动化的方法来测试它(也就是深度比较它),我就不必花时间手动检查它是否正常工作......它对于测试套件也很有用......
  • 您是要按值还是按内容将表作为键进行比较?函数(作为键或值)呢? C 函数(相同)呢?用户数据(相同)呢?
  • @EtanReisner 我的函数只复制表(到目前为止),所以你可以只使用 == 或 rawequals()。我的主要问题是{[{}] = {}}
  • 对。在解决问题之前,您需要定义该场景的“平等”意味着什么。所有空表都“相等”吗?这可能是合理的,除非当您需要从原始表中的项目在副本表中查找时它对您没有帮助。

标签: algorithm lua compare equality


【解决方案1】:

正如其他人所说,这在很大程度上取决于您对等价的定义。 如果您希望这是真的:

local t1 = {[{}] = {1}, [{}] = {2}}
local t2 = {[{}] = {1}, [{}] = {2}}
assert( table_eq(t1, t2) )

如果你这样做了,那么每次 t1 中的键是一个表时,你必须 检查它与 t2 中的每个表键的等价性并尝试它们 一。这是一种实现方式(为了便于阅读,省略了元数据)。

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

assert( table_eq({}, {}) )
assert( table_eq({1,2,3}, {1,2,3}) )
assert( table_eq({1,2,3, foo = "fighters"}, {["foo"] = "fighters", 1,2,3}) )
assert( table_eq({{{}}}, {{{}}}) )
assert( table_eq({[{}] = {1}, [{}] = {2}}, {[{}] = {1}, [{}] = {2}}) )
assert( table_eq({a = 1, [{}] = {}}, {[{}] = {}, a = 1}) )
assert( table_eq({a = 1, [{}] = {1}, [{}] = {2}}, {[{}] = {2}, a = 1, [{}] = {1}}) )

assert( not table_eq({1,2,3,4}, {1,2,3}) )
assert( not table_eq({1,2,3, foo = "fighters"}, {["foo"] = "bar", 1,2,3}) )
assert( not table_eq({{{}}}, {{{{}}}}) )
assert( not table_eq({[{}] = {1}, [{}] = {2}}, {[{}] = {1}, [{}] = {2}, [{}] = {3}}) )
assert( not table_eq({[{}] = {1}, [{}] = {2}}, {[{}] = {1}, [{}] = {3}}) )

【讨论】:

  • 不应该将recurse 声明为local function
  • 尽管这个函数很棒,但对我来说,它在这个断言上间歇性地失败:assert( table_eq({a = 1, [{}] = {1}, [{}] = {2}}, {[{}] = {2}, a = 1, [{}] = {1}}) ) 上面提供了。我最终使用 LuaUnit 中的代码进行比较
【解决方案2】:

您可以尝试序列化每个表并比较序列化结果,而不是直接比较。有几个序列化器将表作为键处理,并且可以序列化深度和递归结构。例如,您可以尝试Serpent(作为独立模块提供,也包含在 Mobdebug 中):

local dump = pcall(require, 'serpent') and require('serpent').dump
  or pcall(require, 'mobdebug') and require('mobdebug').dump
  or error("can't find serpent or mobdebug")
local a = dump({a = 1, [{}] = {}})
local b = dump({[{}] = {}, a = 1})
print(a==b)

这将为我返回true(Lua 5.1 和 Lua 5.2)。需要注意的一点是序列化结果取决于序列化键的顺序。默认情况下,作为 key 的表是根据它们的 tostring 值排序的,这意味着顺序取决于它们的分配地址,不难想出一个在 Lua 5.2 下失败的例子:

local dump = pcall(require, 'serpent') and require('serpent').dump
  or pcall(require, 'mobdebug') and require('mobdebug').dump
  or error("can't find serpent or mobdebug")
local a = dump({a = 1, [{}] = {1}, [{}] = {2}})
local b = dump({[{}] = {2}, a = 1, [{}] = {1}})
print(a==b) --<-- `false` under Lua 5.2

防止这种情况的一种方法是在键比较中一致地表示表;例如,代替默认的tostring,您可能想要序列化表及其值并基于它对键进行排序(serpent 允许custom handler for sortkeys),这将使排序更加健壮并且序列化结果更加稳定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-28
    • 1970-01-01
    相关资源
    最近更新 更多