如何检查两个 ES2015 Map 对象是否具有相同的一组(key, value)
对?
我们可以假设所有的键和值都是原始数据类型。
解决此问题的一种方法是获取map.entries()
,从中创建数组,然后按键对该数组进行排序。并对另一张地图执行相同的操作。然后遍历这两个数组以比较它们。所有这些接缝都很麻烦,而且由于排序(性能效率低下)和制作这些数组(内存效率低下)而效率非常低。
有人有更好的主意吗?
没有"标准"或"内置"的方法可以做到这一点。 从概念上讲,您只需要比较两个 Map 对象对每个键具有相同的键和值,并且没有额外的键。
为了尽可能高效地进行比较,您可以进行以下优化:
- 首先检查两张地图上的
.size
属性。 如果两个地图没有相同数量的键,那么你马上就知道,它们不可能相同。 - 此外,保证它们具有相同数量的键允许您只迭代其中一个映射并将其值与另一个映射进行比较。
- 使用
for (var [key, val] of map1)
迭代器语法来迭代键,这样就不必自己构建或排序键数组(应该更快、更节省内存)。 - 然后,最后,如果您确保在发现不匹配后立即返回比较,那么当它们不相同时,它将缩短执行时间。
然后,由于 undefined
是 Map 中的合法值,但它也是.get()
在找不到键时返回的值,因此我们必须通过执行额外的.has()
来注意这一点,如果我们比较的值是undefined
。
由于 Map 对象的键和值本身都可以是对象,因此如果您希望对对象进行深入的属性比较以确定相等性,而不仅仅是 Javascript 默认用于测试同一对象的更简单===
,这将变得更加棘手。 或者,如果您只对具有键和值基元的对象感兴趣,则可以避免这种复杂性。
对于仅测试严格值相等性的函数(检查对象以查看它们是否是相同的物理对象,而不是深层属性比较),您可以执行如下所示的操作。 这使用 ES6 语法对 map 对象进行高效迭代,并尝试在不匹配时通过短路和在发现不匹配后立即返回false
来提高性能。
"use strict";
function compareMaps(map1, map2) {
let testVal;
if (map1.size !== map2.size) {
return false;
}
for (let [key, val] of map1) {
testVal = map2.get(key);
// in cases of an undefined value, make sure the key
// actually exists on the object so there are no false positives
if (testVal !== val || (testVal === undefined && !map2.has(key))) {
return false;
}
}
return true;
}
// construct two maps that are initially identical
const o = {"k" : 2}
const m1 = new Map();
m1.set("obj", o);
m1.set("str0", undefined);
m1.set("str1", 1);
m1.set("str2", 2);
m1.set("str3", 3);
const m2 = new Map();
m2.set("str0", undefined);
m2.set("obj", o);
m2.set("str1", 1);
m2.set("str2", 2);
m2.set("str3", 3);
log(compareMaps(m1, m2));
// add an undefined key to m1 and a corresponding other key to m2
// this will pass the .size test and even pass the equality test, but not pass the
// special test for undefined values
m1.set("str-undefined", undefined);
m2.set("str4", 4);
log(compareMaps(m1, m2));
// remove one key from m1 so m2 has an extra key
m1.delete("str-undefined");
log(compareMaps(m1, m2));
// add that same extra key to m1, but give it a different value
m1.set("str4", 5);
log(compareMaps(m1, m2));
function log(args) {
let str = "";
for (let i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "object") {
str += JSON.stringify(arguments[i]);
} else {
str += arguments[i];
}
}
const div = document.createElement("div");
div.innerHTML = str;
const target = log.id ? document.getElementById(log.id) : document.body;
target.appendChild(div);
}
<小时 />
如果你想做深度的对象比较,而不仅仅是比较它们在物理上是否是同一个对象,其中值可以是对象或数组,那么生活就会变得复杂得多。
为此,您需要一种考虑到以下所有因素的深度对象比较方法:
- 嵌套对象的递归比较
- 防止循环引用(这可能导致无限循环)
- 了解如何比较某些类型的内置对象,例如
Date
。
由于其他地方已经写了很多关于如何进行深度对象比较的文章(包括StackOverflow上一些高投票的答案),我将假设这不是您问题的主要部分。
下面是一个用于检查地图相等性的单行函数:
const mapsAreEqual = (m1, m2) => m1.size === m2.size && Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key));
如果您的Map
只有字符串键,则可以使用此方法比较它们:
const mapToObj = (map) => {
let obj = Object.create(null)
for (let [k,v] of map) {
// We don’t escape the key '__proto__'
// which can cause problems on older engines
obj[k] = v
}
return obj
}
assert.deepEqual(mapToObj(myMap), myExpectedObj)
注意:deepEqual
是许多测试套件的一部分,如果没有,您可以使用 lodash/underscore 等效项。任何进行深度比较的函数都可以。
mapToObj
功能由 http://exploringjs.com/es6/ch_maps-sets.html 提供
上述内容不适用于Map<string, object>
因为以下行将无法正确评估两个对象:
if (testVal !== val || (testVal === undefined && !map2.has(key))) {
以下版本通过使用 JSON.stringify() 进行比较来扩展Map<string, object>
函数
function compareMaps(map1, map2) {
var testVal;
if (map1.size !== map2.size) {
return false;
}
for (var [key, val] of map1) {
testVal = map2.get(key);
// in cases of an undefined value, make sure the key
// actually exists on the object so there are no false positives
if (JSON.stringify(testVal) !== JSON.stringify(val) || (testVal === undefined && !map2.has(key))) {
return false;
}
}
return true;
}
这是我的示例,能够提供可选的比较函数
/**
* The utility function that returns an intersection of two Sets
*
* @returns an array of items in common
*/
function intersection<T>(a: Set<T>, b: Set<T>): T[] {
return Array.from(a).filter(x => b.has(x));
}
/**
* Compares two Maps
*
* @param compare is an optional function for values comparison
* @returns `true` if they are equal, and `false` otherwise
*/
function compareMaps<T>(a: Map<string, T>, b: Map<string, T>, compare?: (aValue: T, bValue: T) => boolean): boolean {
const common = intersection(new Set(a.keys()), new Set(b.keys()));
return a.size === b.size &&
common.length === a.size &&
common.every(key =>
compare?.(a.get(key) as T, b.get(key) as T) ?? a.get(key) === b.get(key));
}
请注意,@jfriend00提出的解决方案在打字稿 ES2016 中不起作用。请参阅此正确答案:对打字稿映射的迭代失败