Есть ли простой способ объединить карты 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
выровнен по методу Array
s 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};