Есть ли простой способ объединить карты ES6 вместе (например, Object.assign
)? И пока мы это делаем, как насчет ES6 Sets (например, Array.concat
)?
Самый простой способ слияния карт/наборов ES6?
Ответ 1
Для множеств:
var merged = new Set([...set1, ...set2, ...set3])
Для карт:
var merged = new Map([...map1, ...map2, ...map3])
Обратите внимание, что если несколько карт имеют один и тот же ключ, значение объединенной карты будет значением последней слияющей карты с этим ключом.
Ответ 2
Здесь мое решение с использованием генераторов:
Для карт:
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' ]
Ответ 3
По причинам, которые я не понимаю, вы не можете напрямую добавить содержимое одного набора в другой с помощью встроенной операции. Такие операции, как объединение, пересечение, слияние и т.д., Являются довольно простыми операциями над множествами, но не являются встроенными. К счастью, вы можете довольно легко построить все это самостоятельно.
Таким образом, для реализации операции слияния (слияния содержимого одного набора в другой или одной карты в другую) вы можете сделать это с помощью одной строки .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));
К вашему сведению, если вам нужен простой подкласс встроенного объекта Set
, который содержит метод .merge()
, вы можете использовать это:
// 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 и использовать вместо встроенного набора.
Ответ 4
Редактировать:
Я сравнил свое первоначальное решение с другими предложениями и обнаружил, что оно очень неэффективно.
Сам бенчмарк очень интересный (ссылка) Он сравнивает 3 решения (чем выше, тем лучше):
- Решение @bfred.it, которое добавляет значения одно за другим (14 955 оп/с)
- Решение @jameslk, в котором используется самозапускающийся генератор (5089 операций в секунду)
- мой собственный, который использует уменьшение и распространение (3434 оп/сек)
Как видите, решение @bfred.it определенно является победителем.
Производительность + неизменность
Имея это в виду, здесь слегка измененная версия, которая не изменяет исходный набор и исключает переменное число итераций для объединения в качестве аргументов:
function union(...iterables) { const set = new Set(); for (let iterable of iterables) { for (let item of iterable) { set.add(item); } } return 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}
Оригинальный ответ
Я хотел бы предложить другой подход, использующий оператор 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}
Ответ 5
Утвержденный ответ велик, но каждый раз создается новый набор.
Если вы хотите вместо мутировать существующий объект использовать вспомогательную функцию.
Set
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]
Ответ 6
Чтобы объединить множества в массиве Sets, вы можете сделать
var Sets = [set1, set2, set3];
var merged = new Set([].concat(...Sets.map(set => Array.from(set))));
Для меня немного загадочно, почему следующее, которое должно быть эквивалентным, терпит неудачу, по крайней мере, в Вавилоне:
var merged = new Set([].concat(...Sets.map(Array.from)));
Ответ 7
Основываясь на ответе Асафа Каца, вот машинописная версия:
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
}
Ответ 8
Нет, для них нет встроенных операций, но вы можете легко создать их самостоятельно:
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;
};
Ответ 9
Пример
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']
Ответ 10
It не имеет никакого смысла вызывать 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()))
Ответ 11
Object.assign
можно использовать для объединения карт:
merged = Object.assign({}, first, second)