使用高阶函数的详尽地图比较
我将采用与在类似答案中处理数组比较相同的方式:How to compare arrays in JavaScript?
我会一点一点地看代码,但最后我会有一个完整的可运行示例
浅比较
首先,我们将从通用地图比较函数开始。这样我们就可以对 Map 对象进行各种比较,而不仅仅是测试相等性
这个mapCompare 函数符合我们关于应该如何比较地图的直觉——我们将m1 中的每个键与m2 中的每个键进行比较。请注意,这个特定的比较器正在进行浅层比较。稍后我们将处理深度比较
const mapCompare = f => (m1, m2) => {
const aux = (it, m2) => {
let {value, done} = it.next()
if (done) return true
let [k, v] = value
return f (v, m2.get(k), $=> aux(it, m2))
}
return aux(m1.entries(), m2) && aux(m2.entries(), m1)
}
唯一可能无法立即清楚的是$=> aux(it, m2) thunk。地图有一个内置的生成器.entries(),一旦发现不匹配的键/值对,我们就可以通过返回一个早期的false 答案来利用惰性求值。这意味着我们必须以稍微特殊的方式编写比较器。
const shortCircuitEqualComparator = (a, b, k) =>
a === b ? true && k() : false
a 和b 分别是m1.get(somekey) 和m2.get(somekey) 的值。 iff 两个值严格相等 (===),然后我们才想继续比较 - 在这种情况下,我们返回 true && k() 其中k() 是键/值的其余部分配对比较。另一方面,如果a 和b 不匹配,我们可以返回一个早期的false 并跳过比较其余的值——也就是说,我们已经知道m1 和m2 不匹配如果 任何 a/b 对不匹配。
最后,我们可以定义mapEqual - 它也很简单。这只是mapCompare 使用我们的特殊shortCircuitEqualComparator
const mapEqual = mapCompare (shortCircuitEqualComparator)
让我们快速看看它是如何工作的
// define two maps that are equal but have keys in different order
const a = new Map([['b', 2], ['a', 1]])
const b = new Map([['a', 1], ['b', 2]])
// define a third map that is not equal
const c = new Map([['a', 3], ['b', 2]])
// verify results
// a === b should be true
// b === a should be true
console.log('true', mapEqual(a, b)) // true true
console.log('true', mapEqual(b, a)) // true true
// a === c should be false
// c === a should be false too
console.log('false', mapEqual(a, c)) // false false
console.log('false', mapEqual(c, a)) // false false
哎呀,是的。事情看起来不错...
与瑞克和莫蒂的深度比较
现在我们有了一个可笑的甜蜜mapCompare 通用,深入比较是轻而易举的事。请注意,我们实际上是使用mapCompare 本身来实现mapDeepCompare。
我们使用自定义比较器来简单地检查 a 和 b 是否都是 Map 对象——如果是,我们在嵌套 Maps 上使用 mapDeepCompare 进行递归;还要注意调用... && k() 以确保比较剩余的键/值对。如果 a 或 b 是非 Map 对象,则使用 f 进行正常比较,我们直接传递延续 k
const mapDeepCompare = f => mapCompare ((a, b, k) => {
console.log(a, b)
if (a instanceof Map && b instanceof Map)
return mapDeepCompare (f) (a,b) ? true && k() : false
else
return f(a,b,k)
})
现在有了mapDeepCompare,我们可以在嵌套地图上执行任何类型的深度比较。请记住,平等只是我们可以检查的事情之一。
事不宜迟,mapDeepEqual。重要的是,我们可以重用我们之前定义的shortCircuitEqualComparator。这非常清楚地表明我们的比较器可以(重新)用于浅或深度地图比较。
const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)
让我们看看它的工作原理
// define two nested maps that are equal but have different key order
const e = new Map([
['a', 1],
['b', new Map([['c', 2]])]
])
const f = new Map([
['b', new Map([['c', 2]])],
['a', 1]
])
// define a third nested map that is not equal
const g = new Map([
['b', new Map([
['c', 3]
])],
['a', 1]
])
// e === f should be true
// f === e should also be true
console.log('true', mapDeepEqual(e, f)) // true
console.log('true', mapDeepEqual(f, e)) // true
// e === g should be false
// g === e should also be false
console.log('false', mapDeepEqual(e, g)) // false
console.log('false', mapDeepEqual(g, e)) // false
好的,只是为了确保。如果我们在嵌套的地图e 和f 上调用mapEqual 会发生什么?由于mapEqual 进行浅 比较,我们预计结果应该是false
console.log('false', mapEqual(e, f)) // false
console.log('false', mapEqual(f, e)) // false
你有它。 ES6 Map 对象的浅层和深层比较。可以编写一组几乎相同的函数来支持 ES6 Set。我将把它作为练习留给读者。
可运行代码演示
这是编译成单个可运行演示的所有上述代码。每个console.log 调用都会输出<expected>, <actual>。所以true, true 或false, false 将是通过测试 - 而true, false 将是失败测试。
// mapCompare :: ((a, a, (* -> Bool)) -> Bool) -> (Map(k:a), Map(k:a)) -> Bool
const mapCompare = f => (m1, m2) => {
const aux = (it, m2) => {
let {value, done} = it.next()
if (done) return true
let [k, v] = value
return f (v, m2.get(k), $=> aux(it, m2))
}
return aux(m1.entries(), m2) && aux(m2.entries(), m1)
}
// mapDeepCompare :: ((a, a, (* -> Bool)) -> Bool) -> (Map(k:a), Map(k:a)) -> Bool
const mapDeepCompare = f => mapCompare ((a, b, k) => {
if (a instanceof Map && b instanceof Map)
return mapDeepCompare (f) (a,b) ? true && k() : false
else
return f(a,b,k)
})
// shortCircuitEqualComparator :: (a, a, (* -> Bool)) -> Bool
const shortCircuitEqualComparator = (a, b, k) =>
a === b ? true && k() : false
// mapEqual :: (Map(k:a), Map(k:a)) -> Bool
const mapEqual = mapCompare (shortCircuitEqualComparator)
// mapDeepEqual :: (Map(k:a), Map(k:a)) -> Bool
const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)
// fixtures
const a = new Map([['b', 2], ['a', 1]])
const b = new Map([['a', 1], ['b', 2]])
const c = new Map([['a', 3], ['b', 2]])
const d = new Map([['a', 1], ['c', 2]])
const e = new Map([['a', 1], ['b', new Map([['c', 2]])]])
const f = new Map([['b', new Map([['c', 2]])], ['a', 1]])
const g = new Map([['b', new Map([['c', 3]])], ['a', 1]])
// shallow comparison of two equal maps
console.log('true', mapEqual(a, b))
console.log('true', mapEqual(b, a))
// shallow comparison of two non-equal maps (differing values)
console.log('false', mapEqual(a, c))
console.log('false', mapEqual(c, a))
// shallow comparison of two other non-equal maps (differing keys)
console.log('false', mapEqual(a, d))
console.log('false', mapEqual(d, a))
// deep comparison of two equal nested maps
console.log('true', mapDeepEqual(e, f))
console.log('true', mapDeepEqual(f, e))
// deep comparison of two non-equal nested maps
console.log('false', mapDeepEqual(e, g))
console.log('false', mapDeepEqual(g, e))
// shallow comparison of two equal nested maps
console.log('false', mapEqual(e, f))
console.log('false', mapEqual(f, e))