Есть ли простой способ объединить карты ES6 вместе (как Object.assign)? И в то время как мы на этом, как насчет наборов ES6 (как Array.concat)?
for..ofпотому что это keyможет быть любой тип
Есть ли простой способ объединить карты ES6 вместе (как Object.assign)? И в то время как мы на этом, как насчет наборов ES6 (как Array.concat)?
for..ofпотому что это keyможет быть любой тип
Ответы:
Для наборов:
var merged = new Set([...set1, ...set2, ...set3])
Для карт:
var merged = new Map([...map1, ...map2, ...map3])
Обратите внимание, что если несколько карт имеют один и тот же ключ, значением объединенной карты будет значение последней карты слияния с этим ключом.
Map: «Конструктор: new Map([iterable])», « iterableэто массив или другой итерируемый объект, элементами которого являются пары ключ-значение (двухэлементные массивы). Каждая пара ключ-значение добавляется на новую карту ». - просто как ссылка.
Mapконструктору позволяет избежать потребления памяти.
Вот мое решение с использованием генераторов:
Для карт:
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' ]
m2.forEach((k,v)=>m1.set(k,v))если вы хотите легкую поддержку браузера
По причинам, которые я не понимаю, вы не можете напрямую добавлять содержимое одного набора в другой с помощью встроенной операции. Такие операции, как объединение, пересечение, объединение и т. Д., Являются довольно простыми операциями над множествами, но не являются встроенными. К счастью, вы можете довольно легко построить все это самостоятельно.
Таким образом, для реализации операции слияния (слияния содержимого одного набора в другой или одной карты в другую) вы можете сделать это одной .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 и использовать вместо встроенного Set.
new Set(s, t). работает. tПараметр игнорируется. Кроме того, очевидно, что нецелесообразно addобнаруживать тип его параметра и, если набор добавляет элементы набора, потому что тогда не было бы способа добавить сам набор в набор.
.add()метода получения набора, я понимаю вашу точку зрения. Я просто нахожу это гораздо менее полезным, чем возможность комбинировать наборы с использованием, .add()поскольку у меня никогда не было необходимости в наборе или наборах, но мне приходилось много раз объединять наборы. Просто вопрос о полезности одного поведения против другого.
n.forEach(m.add, m)- это инвертирует пары ключ / значение!
Map.prototype.forEach()и Map.prototype.set()изменили аргументы. Похоже на недосмотр кого-то. Это заставляет больше кода при попытке использовать их вместе.
setПорядок параметров является естественным для пар ключ / значение, forEachвыровнен по методу Arrays forEach(и тому подобное $.eachили _.eachтакже перечисляет объекты).
Редактировать :
Я сравнил свое первоначальное решение с другими предложениями и обнаружил, что оно очень неэффективно.
Сам бенчмарк очень интересный ( ссылка ) Он сравнивает 3 решения (чем выше, тем лучше):
- Решение @ bfred.it, которое добавляет значения одно за другим (14 955 оп / с)
- Решение @ jameslk, которое использует генератор самозапуска (5,089 оп / сек)
- мой собственный, который использует уменьшение и распространение (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}
forofс add, что кажется просто очень неэффективным. Я действительно хочу addAll(iterable)метод на множествах
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 }
Утвержденный ответ отличный, но каждый раз он создает новый набор.
Если вы хотите вместо этого изменить существующий объект, используйте вспомогательную функцию.
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]
Чтобы объединить наборы в массиве 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)));
Array.fromпринимает дополнительные параметры, второй является функцией отображения. Array.prototype.mapпередает три аргумента в свой обратный вызов:, (value, index, array)так что он эффективно вызывает Sets.map((set, index, array) => Array.from(set, index, array). Очевидно, indexчто это число, а не функция отображения, поэтому оно не работает.
Не имеет смысла вызывать 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()))
Преобразуйте наборы в массивы, сгладьте их и, наконец, конструктор будет унифицирован.
const union = (...sets) => new Set(sets.map(s => [...s]).flat());
Нет, для них нет встроенных операций, но вы можете легко создать их самостоятельно:
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;
};
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']
Вы можете использовать синтаксис распространения, чтобы объединить их вместе:
const map1 = {a: 1, b: 2}
const map2 = {b: 1, c: 2, a: 5}
const mergedMap = {...a, ...b}
=> {a: 5, b: 1, c: 2}
объединить с картой
let merge = {...map1,...map2};