(也发布在Lua邮件列表上)
所以我一直在编写深拷贝算法,我想测试它们是否按照我希望的方式工作。虽然我可以访问原始>复制映射,但我想要一个通用的深度比较算法,它必须能够比较表键(表作为键?)。
我的深拷贝算法在这里是可用的: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(下面的代码段副本)
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
序列化也不是一种选择,因为序列化的顺序是"随机"。
答案 0 :(得分:3)
正如其他人所说,这在很大程度上取决于你对等价的定义。 如果你想这是真的:
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}}) )
答案 1 :(得分:1)
您可以尝试序列化每个表并比较序列化结果,而不是直接比较。有几个序列化程序将表作为键处理,可以序列化深层和递归结构。例如,您可以尝试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)。其中一个警告是序列化结果取决于键序列化的顺序。默认情况下,表作为键是基于它们的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
防止这种情况的一种方法是在密钥比较中始终表示表格;例如,您可能希望序列化表及其值并根据该值对其进行排序(serpent允许custom handler for sortkeys
),而不是默认tostring
,这将使排序更加健壮并且序列化结果更稳定。