合并 ES6 Maps/Sets 的最简单方法?
Simplest way to merge ES6 Maps/Sets?
有没有一种简单的方法可以将 ES6 映射合并在一起(比如 Object.assign
)?在我们讨论的同时,ES6 集合(比如 Array.concat
)呢?
对于集合:
var merged = new Set([...set1, ...set2, ...set3])
对于地图:
var merged = new Map([...map1, ...map2, ...map3])
请注意,如果多个映射具有相同的键,则合并后的映射的值将是最后一个具有该键的合并映射的值。
由于我不明白的原因,您不能使用内置方法直接将一个 Set 的内容添加到另一个。并集、交集、合并等操作是非常基本的集合操作,但不是内置的。幸运的是,您可以相当轻松地自己构建这些。
[2021 年添加] - 现在有一个 proposal 为这些类型的操作添加新的 Set/Map 方法,但实施时间目前还不清楚。它们似乎处于规范流程的第 2 阶段。
要实现合并操作(将一个 Set 的内容合并到另一个 Set 或将一个 Map 合并到另一个 Map),您可以使用单个 .forEach()
行:
var s = new Set([1,2,3]);
var t = new Set([4,5,6]);
t.forEach(s.add, s);
console.log(s); // 1,2,3,4,5,6
并且,对于 Map
,您可以这样做:
var s = new Map([["key1", 1], ["key2", 2]]);
var t = new Map([["key3", 3], ["key4", 4]]);
t.forEach(function(value, key) {
s.set(key, value);
});
或者,在 ES6 语法中:
t.forEach((value, key) => s.set(key, value));
[2021 年新增]
由于现在有一个关于新 Set 方法的官方提议,您可以将此 polyfill 用于提议的 .union()
方法,该方法将在 ES6+ 版本的 ECMAScript 中工作。请注意,根据规范,这个 returns 一个新的集合,它是另外两个集合的并集。它不会将一个集合的内容合并到另一个集合中,这会实现 proposal.
中指定的类型检查
if (!Set.prototype.union) {
Set.prototype.union = function(iterable) {
if (typeof this !== "object") {
throw new TypeError("Must be of object type");
}
const Species = this.constructor[Symbol.species];
const newSet = new Species(this);
if (typeof newSet.add !== "function") {
throw new TypeError("add method on new set species is not callable");
}
for (item of iterable) {
newSet.add(item);
}
return newSet;
}
}
或者,这里有一个更完整的版本,它对 ECMAScript 过程进行建模,以更完整地获取物种构造函数,并且已经适应 运行 在甚至可能没有 Javascript 的旧版本上19=] 或设置 Symbol.species
:
if (!Set.prototype.union) {
Set.prototype.union = function(iterable) {
if (typeof this !== "object") {
throw new TypeError("Must be of object type");
}
const Species = getSpeciesConstructor(this, Set);
const newSet = new Species(this);
if (typeof newSet.add !== "function") {
throw new TypeError("add method on new set species is not callable");
}
for (item of iterable) {
newSet.add(item);
}
return newSet;
}
}
function isConstructor(C) {
return typeof C === "function" && typeof C.prototype === "object";
}
function getSpeciesConstructor(obj, defaultConstructor) {
const C = obj.constructor;
if (!C) return defaultConstructor;
if (typeof C !== "function") {
throw new TypeError("constructor is not a function");
}
// use try/catch here to handle backward compatibility when Symbol does not exist
let S;
try {
S = C[Symbol.species];
if (!S) {
// no S, so use C
S = C;
}
} catch (e) {
// No Symbol so use C
S = C;
}
if (!isConstructor(S)) {
throw new TypeError("constructor function is not a constructor");
}
return S;
}
仅供参考,如果你想要一个包含 .merge()
方法的内置 Set
对象的简单子类,你可以使用这个:
// subclass of Set that adds new methods
// Except where otherwise noted, arguments to methods
// can be a Set, anything derived from it or an Array
// Any method that returns a new Set returns whatever class the this object is
// allowing SetEx to be subclassed and these methods will return that subclass
// For this to work properly, subclasses must not change behavior of SetEx methods
//
// Note that if the contructor for SetEx is passed one or more iterables,
// it will iterate them and add the individual elements of those iterables to the Set
// If you want a Set itself added to the Set, then use the .add() method
// which remains unchanged from the original Set object. This way you have
// a choice about how you want to add things and can do it either way.
class SetEx extends Set {
// create a new SetEx populated with the contents of one or more iterables
constructor(...iterables) {
super();
this.merge(...iterables);
}
// merge the items from one or more iterables into this set
merge(...iterables) {
for (let iterable of iterables) {
for (let item of iterable) {
this.add(item);
}
}
return this;
}
// return new SetEx object that is union of all sets passed in with the current set
union(...sets) {
let newSet = new this.constructor(...sets);
newSet.merge(this);
return newSet;
}
// return a new SetEx that contains the items that are in both sets
intersect(target) {
let newSet = new this.constructor();
for (let item of this) {
if (target.has(item)) {
newSet.add(item);
}
}
return newSet;
}
// return a new SetEx that contains the items that are in this set, but not in target
// target must be a Set (or something that supports .has(item) such as a Map)
diff(target) {
let newSet = new this.constructor();
for (let item of this) {
if (!target.has(item)) {
newSet.add(item);
}
}
return newSet;
}
// target can be either a Set or an Array
// return boolean which indicates if target set contains exactly same elements as this
// target elements are iterated and checked for this.has(item)
sameItems(target) {
let tsize;
if ("size" in target) {
tsize = target.size;
} else if ("length" in target) {
tsize = target.length;
} else {
throw new TypeError("target must be an iterable like a Set with .size or .length");
}
if (tsize !== this.size) {
return false;
}
for (let item of target) {
if (!this.has(item)) {
return false;
}
}
return true;
}
}
module.exports = SetEx;
这意味着在它自己的文件 setex.js 中,然后您可以 require()
进入 node.js 并代替内置集使用。
要合并数组 Sets 中的集合,您可以这样做
var Sets = [set1, set2, set3];
var merged = new Set([].concat(...Sets.map(set => Array.from(set))));
对我来说有点神秘的是,为什么下面的应该是等价的,但至少在 Babel 中失败了:
var merged = new Set([].concat(...Sets.map(Array.from)));
这是我使用生成器的解决方案:
对于地图:
let map1 = new Map(), map2 = new Map();
map1.set('a', 'foo');
map1.set('b', 'bar');
map2.set('b', 'baz');
map2.set('c', 'bazz');
let map3 = new Map(function*() { yield* map1; yield* map2; }());
console.log(Array.from(map3)); // Result: [ [ 'a', 'foo' ], [ 'b', 'baz' ], [ 'c', 'bazz' ] ]
对于集合:
let set1 = new Set(['foo', 'bar']), set2 = new Set(['bar', 'baz']);
let set3 = new Set(function*() { yield* set1; yield* set2; }());
console.log(Array.from(set3)); // Result: [ 'foo', 'bar', 'baz' ]
不,这些没有内置操作,但您可以轻松地创建自己的操作:
Map.prototype.assign = function(...maps) {
for (const m of maps)
for (const kv of m)
this.add(...kv);
return this;
};
Set.prototype.concat = function(...sets) {
const c = this.constructor;
let res = new (c[Symbol.species] || c)();
for (const set of [this, ...sets])
for (const v of set)
res.add(v);
return res;
};
批准的答案很好,但每次都会创建一个新的集合。
如果您想 改变 现有对象,请使用辅助函数。
设置
function concatSets(set, ...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
set.add(item);
}
}
}
用法:
const setA = new Set([1, 2, 3]);
const setB = new Set([4, 5, 6]);
const setC = new Set([7, 8, 9]);
concatSets(setA, setB, setC);
// setA will have items 1, 2, 3, 4, 5, 6, 7, 8, 9
地图
function concatMaps(map, ...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
map.set(...item);
}
}
}
用法:
const mapA = new Map().set('S', 1).set('P', 2);
const mapB = new Map().set('Q', 3).set('R', 4);
concatMaps(mapA, mapB);
// mapA will have items ['S', 1], ['P', 2], ['Q', 3], ['R', 4]
例子
const mergedMaps = (...maps) => {
const dataMap = new Map([])
for (const map of maps) {
for (const [key, value] of map) {
dataMap.set(key, value)
}
}
return dataMap
}
用法
const map = mergedMaps(new Map([[1, false]]), new Map([['foo', 'bar']]), new Map([['lat', 1241.173512]]))
Array.from(map.keys()) // [1, 'foo', 'lat']
Edit:
I benchmarked my original solution against other solutions suggests here and found that it is very inefficient.
The benchmark itself is very interesting (link) It compares 3 solutions (higher is better):
- @fregante (formerly called @bfred.it) solution, which adds values one by one (14,955 op/sec)
- @jameslk's solution, which uses a self invoking generator (5,089 op/sec)
- my own, which uses reduce & spread (3,434 op/sec)
As you can see, @fregante's solution is definitely the winner.
Performance + Immutability
With that in mind, here's a slightly modified version which doesn't
mutates the original set and excepts a variable number of iterables to
combine as arguments:
function union(...iterables) {
const set = new Set();
for (const iterable of iterables) {
for (const item of iterable) {
set.add(item);
}
}
return set;
}
Usage:
const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const c = new Set([4, 5, 6]);
union(a,b,c) // {1, 2, 3, 4, 5, 6}
原答案
我想建议另一种方法,使用 reduce
和 spread
运算符:
实施
function union (sets) {
return sets.reduce((combined, list) => {
return new Set([...combined, ...list]);
}, new Set());
}
用法:
const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const c = new Set([4, 5, 6]);
union([a, b, c]) // {1, 2, 3, 4, 5, 6}
提示:
我们还可以利用rest
运算符使界面更漂亮:
function union (...sets) {
return sets.reduce((combined, list) => {
return new Set([...combined, ...list]);
}, new Set());
}
现在,我们可以传递任意数量的参数集合,而不是传递数组集合:
union(a, b, c) // {1, 2, 3, 4, 5, 6}
根据 Asaf Katz 的回答,这里是打字稿版本:
export function union<T> (...iterables: Array<Set<T>>): Set<T> {
const set = new Set<T>()
iterables.forEach(iterable => {
iterable.forEach(item => set.add(item))
})
return set
}
在向现有集合添加多个元素(来自数组或另一个集合)时调用 new Set(...anArrayOrSet)
没有任何意义 .
我在 reduce
函数中使用了它,它非常愚蠢。即使您有可用的 ...array
展开运算符,在这种情况下也不应使用它,因为它会浪费处理器、内存和时间资源。
// Add any Map or Set to another
function addAll(target, source) {
if (target instanceof Map) {
Array.from(source.entries()).forEach(it => target.set(it[0], it[1]))
} else if (target instanceof Set) {
source.forEach(it => target.add(it))
}
}
演示片段
// Add any Map or Set to another
function addAll(target, source) {
if (target instanceof Map) {
Array.from(source.entries()).forEach(it => target.set(it[0], it[1]))
} else if (target instanceof Set) {
source.forEach(it => target.add(it))
}
}
const items1 = ['a', 'b', 'c']
const items2 = ['a', 'b', 'c', 'd']
const items3 = ['d', 'e']
let set
set = new Set(items1)
addAll(set, items2)
addAll(set, items3)
console.log('adding array to set', Array.from(set))
set = new Set(items1)
addAll(set, new Set(items2))
addAll(set, new Set(items3))
console.log('adding set to set', Array.from(set))
const map1 = [
['a', 1],
['b', 2],
['c', 3]
]
const map2 = [
['a', 1],
['b', 2],
['c', 3],
['d', 4]
]
const map3 = [
['d', 4],
['e', 5]
]
const map = new Map(map1)
addAll(map, new Map(map2))
addAll(map, new Map(map3))
console.log('adding map to map',
'keys', Array.from(map.keys()),
'values', Array.from(map.values()))
您可以使用 spread syntax 将它们合并在一起:
const map1 = {a: 1, b: 2}
const map2 = {b: 1, c: 2, a: 5}
const mergedMap = {...a, ...b}
=> {a: 5, b: 1, c: 2}
将集合转换为数组,将它们展平,最后构造函数将统一化。
const union = (...sets) => new Set(sets.map(s => [...s]).flat());
我创建了一个小片段来使用 ES6 中的函数合并任意数量的集合。您可以将“设置”更改为“地图”以使其与地图一起使用。
const mergeSets = (...args) => {
return new Set(args.reduce((acc, current) => {
return [...acc, ...current];
}, []));
};
const foo = new Set([1, 2, 3]);
const bar = new Set([1, 3, 4, 5]);
mergeSets(foo, bar); // Set(5) {1, 2, 3, 4, 5}
mergeSets(foo, bar, new Set([6])); // Set(6) {1, 2, 3, 4, 5, 6}
无论您是否有两个或更多地图要合并,一个好的解决方案是将它们分组为一个数组并使用以下内容:
Array.prototype.merge = function () {
return this.reduce((p, c) => Object.assign(c, p), {});
};
有几种方法可以做到这一点。您可以使用 Map.merge 函数:
let mergedMap = map1.merge(map2);
注意:如果任意一个Map的key相同,则使用最后一个要合并的Map中重复key的值。
我创建了一个辅助方法来合并映射并以所需的任何 pair-wise 方式处理重复键的值:
const mergeMaps = (map1, map2, combineValuesOfDuplicateKeys) => {
const mapCopy1 = new Map(map1);
const mapCopy2 = new Map(map2);
mapCopy1.forEach((value, key) => {
if (!mapCopy2.has(key)) {
mapCopy2.set(key, value);
} else {
const newValue = combineValuesOfDuplicateKeys
? combineValuesOfDuplicateKeys(value, mapCopy2.get(key))
: mapCopy2.get(key);
mapCopy2.set(key, newValue);
mapCopy1.delete(key);
}
});
return new Map([...mapCopy1, ...mapCopy2]);
};
const mergeMaps = (map1, map2, combineValuesOfDuplicateKeys) => {
const mapCopy1 = new Map(map1);
const mapCopy2 = new Map(map2);
mapCopy1.forEach((value, key) => {
if (!mapCopy2.has(key)) {
mapCopy2.set(key, value);
} else {
const newValue = combineValuesOfDuplicateKeys
? combineValuesOfDuplicateKeys(value, mapCopy2.get(key))
: mapCopy2.get(key);
mapCopy2.set(key, newValue);
mapCopy1.delete(key);
}
});
return new Map([...mapCopy1, ...mapCopy2]);
};
const map1 = new Map([
["key1", 1],
["key2", 2]
]);
const map2 = new Map([
["key2", 3],
["key4", 4]
]);
const show = (object) => {
return JSON.stringify(Array.from(object), null, 2)
}
document.getElementById("app").innerHTML = `
<h1>Maps are awesome!</h1>
<div>map1 = ${show(map1)}</div>
<div>map2 = ${show(map2)}</div><br>
<div>Set value of last duplicate key:<br>merged map = ${show(mergeMaps(map1, map2))}</div><br>
<div>Set value of pair-wise summated duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 + value2))}</div><br>
<div>Set value of pair-wise difference of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 - value2))}</div><br>
<div>Set value of pair-wise multiplication of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 * value2))}</div><br>
<div>Set value of pair-wise quotient of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 / value2))}</div><br>
<div>Set value of pair-wise power of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => Math.pow(value1, value2)))}</div><br>
`;
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app"></div>
<script src="src/index.js">
</script>
</body>
</html>
有没有一种简单的方法可以将 ES6 映射合并在一起(比如 Object.assign
)?在我们讨论的同时,ES6 集合(比如 Array.concat
)呢?
对于集合:
var merged = new Set([...set1, ...set2, ...set3])
对于地图:
var merged = new Map([...map1, ...map2, ...map3])
请注意,如果多个映射具有相同的键,则合并后的映射的值将是最后一个具有该键的合并映射的值。
由于我不明白的原因,您不能使用内置方法直接将一个 Set 的内容添加到另一个。并集、交集、合并等操作是非常基本的集合操作,但不是内置的。幸运的是,您可以相当轻松地自己构建这些。
[2021 年添加] - 现在有一个 proposal 为这些类型的操作添加新的 Set/Map 方法,但实施时间目前还不清楚。它们似乎处于规范流程的第 2 阶段。
要实现合并操作(将一个 Set 的内容合并到另一个 Set 或将一个 Map 合并到另一个 Map),您可以使用单个 .forEach()
行:
var s = new Set([1,2,3]);
var t = new Set([4,5,6]);
t.forEach(s.add, s);
console.log(s); // 1,2,3,4,5,6
并且,对于 Map
,您可以这样做:
var s = new Map([["key1", 1], ["key2", 2]]);
var t = new Map([["key3", 3], ["key4", 4]]);
t.forEach(function(value, key) {
s.set(key, value);
});
或者,在 ES6 语法中:
t.forEach((value, key) => s.set(key, value));
[2021 年新增]
由于现在有一个关于新 Set 方法的官方提议,您可以将此 polyfill 用于提议的 .union()
方法,该方法将在 ES6+ 版本的 ECMAScript 中工作。请注意,根据规范,这个 returns 一个新的集合,它是另外两个集合的并集。它不会将一个集合的内容合并到另一个集合中,这会实现 proposal.
if (!Set.prototype.union) {
Set.prototype.union = function(iterable) {
if (typeof this !== "object") {
throw new TypeError("Must be of object type");
}
const Species = this.constructor[Symbol.species];
const newSet = new Species(this);
if (typeof newSet.add !== "function") {
throw new TypeError("add method on new set species is not callable");
}
for (item of iterable) {
newSet.add(item);
}
return newSet;
}
}
或者,这里有一个更完整的版本,它对 ECMAScript 过程进行建模,以更完整地获取物种构造函数,并且已经适应 运行 在甚至可能没有 Javascript 的旧版本上19=] 或设置 Symbol.species
:
if (!Set.prototype.union) {
Set.prototype.union = function(iterable) {
if (typeof this !== "object") {
throw new TypeError("Must be of object type");
}
const Species = getSpeciesConstructor(this, Set);
const newSet = new Species(this);
if (typeof newSet.add !== "function") {
throw new TypeError("add method on new set species is not callable");
}
for (item of iterable) {
newSet.add(item);
}
return newSet;
}
}
function isConstructor(C) {
return typeof C === "function" && typeof C.prototype === "object";
}
function getSpeciesConstructor(obj, defaultConstructor) {
const C = obj.constructor;
if (!C) return defaultConstructor;
if (typeof C !== "function") {
throw new TypeError("constructor is not a function");
}
// use try/catch here to handle backward compatibility when Symbol does not exist
let S;
try {
S = C[Symbol.species];
if (!S) {
// no S, so use C
S = C;
}
} catch (e) {
// No Symbol so use C
S = C;
}
if (!isConstructor(S)) {
throw new TypeError("constructor function is not a constructor");
}
return S;
}
仅供参考,如果你想要一个包含 .merge()
方法的内置 Set
对象的简单子类,你可以使用这个:
// subclass of Set that adds new methods
// Except where otherwise noted, arguments to methods
// can be a Set, anything derived from it or an Array
// Any method that returns a new Set returns whatever class the this object is
// allowing SetEx to be subclassed and these methods will return that subclass
// For this to work properly, subclasses must not change behavior of SetEx methods
//
// Note that if the contructor for SetEx is passed one or more iterables,
// it will iterate them and add the individual elements of those iterables to the Set
// If you want a Set itself added to the Set, then use the .add() method
// which remains unchanged from the original Set object. This way you have
// a choice about how you want to add things and can do it either way.
class SetEx extends Set {
// create a new SetEx populated with the contents of one or more iterables
constructor(...iterables) {
super();
this.merge(...iterables);
}
// merge the items from one or more iterables into this set
merge(...iterables) {
for (let iterable of iterables) {
for (let item of iterable) {
this.add(item);
}
}
return this;
}
// return new SetEx object that is union of all sets passed in with the current set
union(...sets) {
let newSet = new this.constructor(...sets);
newSet.merge(this);
return newSet;
}
// return a new SetEx that contains the items that are in both sets
intersect(target) {
let newSet = new this.constructor();
for (let item of this) {
if (target.has(item)) {
newSet.add(item);
}
}
return newSet;
}
// return a new SetEx that contains the items that are in this set, but not in target
// target must be a Set (or something that supports .has(item) such as a Map)
diff(target) {
let newSet = new this.constructor();
for (let item of this) {
if (!target.has(item)) {
newSet.add(item);
}
}
return newSet;
}
// target can be either a Set or an Array
// return boolean which indicates if target set contains exactly same elements as this
// target elements are iterated and checked for this.has(item)
sameItems(target) {
let tsize;
if ("size" in target) {
tsize = target.size;
} else if ("length" in target) {
tsize = target.length;
} else {
throw new TypeError("target must be an iterable like a Set with .size or .length");
}
if (tsize !== this.size) {
return false;
}
for (let item of target) {
if (!this.has(item)) {
return false;
}
}
return true;
}
}
module.exports = SetEx;
这意味着在它自己的文件 setex.js 中,然后您可以 require()
进入 node.js 并代替内置集使用。
要合并数组 Sets 中的集合,您可以这样做
var Sets = [set1, set2, set3];
var merged = new Set([].concat(...Sets.map(set => Array.from(set))));
对我来说有点神秘的是,为什么下面的应该是等价的,但至少在 Babel 中失败了:
var merged = new Set([].concat(...Sets.map(Array.from)));
这是我使用生成器的解决方案:
对于地图:
let map1 = new Map(), map2 = new Map();
map1.set('a', 'foo');
map1.set('b', 'bar');
map2.set('b', 'baz');
map2.set('c', 'bazz');
let map3 = new Map(function*() { yield* map1; yield* map2; }());
console.log(Array.from(map3)); // Result: [ [ 'a', 'foo' ], [ 'b', 'baz' ], [ 'c', 'bazz' ] ]
对于集合:
let set1 = new Set(['foo', 'bar']), set2 = new Set(['bar', 'baz']);
let set3 = new Set(function*() { yield* set1; yield* set2; }());
console.log(Array.from(set3)); // Result: [ 'foo', 'bar', 'baz' ]
不,这些没有内置操作,但您可以轻松地创建自己的操作:
Map.prototype.assign = function(...maps) {
for (const m of maps)
for (const kv of m)
this.add(...kv);
return this;
};
Set.prototype.concat = function(...sets) {
const c = this.constructor;
let res = new (c[Symbol.species] || c)();
for (const set of [this, ...sets])
for (const v of set)
res.add(v);
return res;
};
批准的答案很好,但每次都会创建一个新的集合。
如果您想 改变 现有对象,请使用辅助函数。
设置
function concatSets(set, ...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
set.add(item);
}
}
}
用法:
const setA = new Set([1, 2, 3]);
const setB = new Set([4, 5, 6]);
const setC = new Set([7, 8, 9]);
concatSets(setA, setB, setC);
// setA will have items 1, 2, 3, 4, 5, 6, 7, 8, 9
地图
function concatMaps(map, ...iterables) {
for (const iterable of iterables) {
for (const item of iterable) {
map.set(...item);
}
}
}
用法:
const mapA = new Map().set('S', 1).set('P', 2);
const mapB = new Map().set('Q', 3).set('R', 4);
concatMaps(mapA, mapB);
// mapA will have items ['S', 1], ['P', 2], ['Q', 3], ['R', 4]
例子
const mergedMaps = (...maps) => {
const dataMap = new Map([])
for (const map of maps) {
for (const [key, value] of map) {
dataMap.set(key, value)
}
}
return dataMap
}
用法
const map = mergedMaps(new Map([[1, false]]), new Map([['foo', 'bar']]), new Map([['lat', 1241.173512]]))
Array.from(map.keys()) // [1, 'foo', 'lat']
Edit:
I benchmarked my original solution against other solutions suggests here and found that it is very inefficient.
The benchmark itself is very interesting (link) It compares 3 solutions (higher is better):
- @fregante (formerly called @bfred.it) solution, which adds values one by one (14,955 op/sec)
- @jameslk's solution, which uses a self invoking generator (5,089 op/sec)
- my own, which uses reduce & spread (3,434 op/sec)
As you can see, @fregante's solution is definitely the winner.
Performance + Immutability
With that in mind, here's a slightly modified version which doesn't mutates the original set and excepts a variable number of iterables to combine as arguments:
function union(...iterables) { const set = new Set(); for (const iterable of iterables) { for (const item of iterable) { set.add(item); } } return set; }
Usage:
const a = new Set([1, 2, 3]); const b = new Set([1, 3, 5]); const c = new Set([4, 5, 6]); union(a,b,c) // {1, 2, 3, 4, 5, 6}
原答案
我想建议另一种方法,使用 reduce
和 spread
运算符:
实施
function union (sets) {
return sets.reduce((combined, list) => {
return new Set([...combined, ...list]);
}, new Set());
}
用法:
const a = new Set([1, 2, 3]);
const b = new Set([1, 3, 5]);
const c = new Set([4, 5, 6]);
union([a, b, c]) // {1, 2, 3, 4, 5, 6}
提示:
我们还可以利用rest
运算符使界面更漂亮:
function union (...sets) {
return sets.reduce((combined, list) => {
return new Set([...combined, ...list]);
}, new Set());
}
现在,我们可以传递任意数量的参数集合,而不是传递数组集合:
union(a, b, c) // {1, 2, 3, 4, 5, 6}
根据 Asaf Katz 的回答,这里是打字稿版本:
export function union<T> (...iterables: Array<Set<T>>): Set<T> {
const set = new Set<T>()
iterables.forEach(iterable => {
iterable.forEach(item => set.add(item))
})
return set
}
在向现有集合添加多个元素(来自数组或另一个集合)时调用 new Set(...anArrayOrSet)
没有任何意义 .
我在 reduce
函数中使用了它,它非常愚蠢。即使您有可用的 ...array
展开运算符,在这种情况下也不应使用它,因为它会浪费处理器、内存和时间资源。
// Add any Map or Set to another
function addAll(target, source) {
if (target instanceof Map) {
Array.from(source.entries()).forEach(it => target.set(it[0], it[1]))
} else if (target instanceof Set) {
source.forEach(it => target.add(it))
}
}
演示片段
// Add any Map or Set to another
function addAll(target, source) {
if (target instanceof Map) {
Array.from(source.entries()).forEach(it => target.set(it[0], it[1]))
} else if (target instanceof Set) {
source.forEach(it => target.add(it))
}
}
const items1 = ['a', 'b', 'c']
const items2 = ['a', 'b', 'c', 'd']
const items3 = ['d', 'e']
let set
set = new Set(items1)
addAll(set, items2)
addAll(set, items3)
console.log('adding array to set', Array.from(set))
set = new Set(items1)
addAll(set, new Set(items2))
addAll(set, new Set(items3))
console.log('adding set to set', Array.from(set))
const map1 = [
['a', 1],
['b', 2],
['c', 3]
]
const map2 = [
['a', 1],
['b', 2],
['c', 3],
['d', 4]
]
const map3 = [
['d', 4],
['e', 5]
]
const map = new Map(map1)
addAll(map, new Map(map2))
addAll(map, new Map(map3))
console.log('adding map to map',
'keys', Array.from(map.keys()),
'values', Array.from(map.values()))
您可以使用 spread syntax 将它们合并在一起:
const map1 = {a: 1, b: 2}
const map2 = {b: 1, c: 2, a: 5}
const mergedMap = {...a, ...b}
=> {a: 5, b: 1, c: 2}
将集合转换为数组,将它们展平,最后构造函数将统一化。
const union = (...sets) => new Set(sets.map(s => [...s]).flat());
我创建了一个小片段来使用 ES6 中的函数合并任意数量的集合。您可以将“设置”更改为“地图”以使其与地图一起使用。
const mergeSets = (...args) => {
return new Set(args.reduce((acc, current) => {
return [...acc, ...current];
}, []));
};
const foo = new Set([1, 2, 3]);
const bar = new Set([1, 3, 4, 5]);
mergeSets(foo, bar); // Set(5) {1, 2, 3, 4, 5}
mergeSets(foo, bar, new Set([6])); // Set(6) {1, 2, 3, 4, 5, 6}
无论您是否有两个或更多地图要合并,一个好的解决方案是将它们分组为一个数组并使用以下内容:
Array.prototype.merge = function () {
return this.reduce((p, c) => Object.assign(c, p), {});
};
有几种方法可以做到这一点。您可以使用 Map.merge 函数:
let mergedMap = map1.merge(map2);
注意:如果任意一个Map的key相同,则使用最后一个要合并的Map中重复key的值。
我创建了一个辅助方法来合并映射并以所需的任何 pair-wise 方式处理重复键的值:
const mergeMaps = (map1, map2, combineValuesOfDuplicateKeys) => {
const mapCopy1 = new Map(map1);
const mapCopy2 = new Map(map2);
mapCopy1.forEach((value, key) => {
if (!mapCopy2.has(key)) {
mapCopy2.set(key, value);
} else {
const newValue = combineValuesOfDuplicateKeys
? combineValuesOfDuplicateKeys(value, mapCopy2.get(key))
: mapCopy2.get(key);
mapCopy2.set(key, newValue);
mapCopy1.delete(key);
}
});
return new Map([...mapCopy1, ...mapCopy2]);
};
const mergeMaps = (map1, map2, combineValuesOfDuplicateKeys) => {
const mapCopy1 = new Map(map1);
const mapCopy2 = new Map(map2);
mapCopy1.forEach((value, key) => {
if (!mapCopy2.has(key)) {
mapCopy2.set(key, value);
} else {
const newValue = combineValuesOfDuplicateKeys
? combineValuesOfDuplicateKeys(value, mapCopy2.get(key))
: mapCopy2.get(key);
mapCopy2.set(key, newValue);
mapCopy1.delete(key);
}
});
return new Map([...mapCopy1, ...mapCopy2]);
};
const map1 = new Map([
["key1", 1],
["key2", 2]
]);
const map2 = new Map([
["key2", 3],
["key4", 4]
]);
const show = (object) => {
return JSON.stringify(Array.from(object), null, 2)
}
document.getElementById("app").innerHTML = `
<h1>Maps are awesome!</h1>
<div>map1 = ${show(map1)}</div>
<div>map2 = ${show(map2)}</div><br>
<div>Set value of last duplicate key:<br>merged map = ${show(mergeMaps(map1, map2))}</div><br>
<div>Set value of pair-wise summated duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 + value2))}</div><br>
<div>Set value of pair-wise difference of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 - value2))}</div><br>
<div>Set value of pair-wise multiplication of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 * value2))}</div><br>
<div>Set value of pair-wise quotient of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => value1 / value2))}</div><br>
<div>Set value of pair-wise power of duplicate keys:<br>merged map = ${show(mergeMaps(map1, map2, (value1, value2) => Math.pow(value1, value2)))}</div><br>
`;
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app"></div>
<script src="src/index.js">
</script>
</body>
</html>