Найпростіший спосіб об’єднання карт / наборів ES6?


170

Чи є простий спосіб об’єднати карти ES6 разом (як Object.assign)? І поки ми це робимо, що з наборами ES6 (як Array.concat)?


4
Ця публікація в блозі має деяке розуміння. 2ality.com/2015/01/es6-set-operations.html
lemieuxster

AFAIK для карти, яку вам потрібно буде використовувати, for..ofоскільки keyможе бути будь-якого типу
Пол С.

Відповіді:


265

Для наборів:

var merged = new Set([...set1, ...set2, ...set3])

Для карт:

var merged = new Map([...map1, ...map2, ...map3])

Зауважте, що якщо кілька карт мають один і той же ключ, значення об'єднаної карти буде значенням останньої карти злиття з цим ключем.


4
Документація на тему Map: "Конструктор: new Map([iterable])", " iterable- це масив чи інший ітерабельний об'єкт, елементами якого є пари ключових значень (2-елементні масиви). Кожна пара ключ-значення додається до нової карти ». - просто як еталон.
user4642212

34
Для великих наборів просто слід попередити, що це повторює вміст обох наборів двічі, один раз для створення тимчасового масиву, що містить об'єднання двох наборів, а потім передає цей тимчасовий масив конструктору Set, де він повторно повторюється для створення нового набору .
jfriend00

2
@ jfriend00: див. відповідь jameslk нижче для кращого методу
Бергі

2
@torazaburo: як сказав jfriend00, рішення Oriols створює непотрібні проміжні масиви. Передача ітератора Mapконструктору дозволяє уникнути їх споживання пам'яті.
Бергі

2
Мені здається, що ES6 Set / Map не забезпечує ефективних методів злиття.
Енді

47

Ось моє рішення з використанням генераторів:

Для Карт:

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' ]

35
(IIGFE = Вираз функцій генератора негайно)
Oriol

3
приємно також, m2.forEach((k,v)=>m1.set(k,v))якщо ви хочете просту підтримку браузера
кауб

9
@caub приємне рішення, але пам’ятайте, що першим параметром forEach є значення, тому ваша функція повинна бути m2.forEach ((v, k) => m1.set (k, v));
Девід Норенья

44

З незрозумілих причин ви не можете безпосередньо додавати вміст одного набору до іншого за допомогою вбудованої операції. Такі операції, як об'єднання, пересікання, злиття тощо ... є досить базовими операціями набору, але не є вбудованими. На щастя, ви можете створити все це досить легко.

Отже, щоб здійснити операцію злиття (об'єднання вмісту одного Набору в інший або одну Карту в іншу), ви можете зробити це одним .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));

FYI, якщо ви хочете простий підклас вбудованого 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 і використовувати замість вбудованого набору.


3
Я не думаю new Set(s, t). працює. tПараметр ігнорується. Крім того, очевидно не розумною поведінкою addвиявляти тип його параметра і якщо набір додає елементи набору, тому що тоді не було б можливості додати сам набір до набору.

@torazaburo - що стосується .add()методу прийому набору, я розумію вашу думку. Я просто вважаю, що набагато менше використання, ніж можливість комбінувати набори, використовуючи, .add()як ніколи у мене не було потреби в наборі або наборах, але мені довелося багато разів об'єднувати набори. Просто питання щодо корисності однієї поведінки проти іншої.
jfriend00

Argh, я ненавиджу, що це не працює для карт: n.forEach(m.add, m)- це перетворює пари ключ / значення!
Бергі

@Bergi - так, як це не дивно, Map.prototype.forEach()і Map.prototype.set()аргументи відмінено. Здається, хтось недогляд. Це примушує більше коду при спробі використовувати їх разом.
jfriend00

@ jfriend00: OTOH, це має сенс. setпараметр порядку є природним для пар ключ / значення, forEachпоєднаний з Arrayами forEachметодом (тощо , $.eachабо , _.eachщо також перерахувати об'єкти).
Бергі

20

Редагувати :

Я порівняв своє оригінальне рішення проти інших рішень, що наводить тут, і виявив, що це дуже неефективно.

Сам бенчмарк дуже цікавий ( посилання ) Він порівнює 3 рішення (чим вище, тим краще):

  • рішення bbred.it, яке додає значення по черзі (14955 оп / сек)
  • рішення jameslk, яке використовує генератор самовикликання (5,089 оп / сек)
  • моє власне, яке використовує зменшення та розповсюдження (3,434 оп / сек)

Як бачите, рішення @ 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}

Це жахливо неефективно.
Бергі

1
Привіт @Bergi, ти маєш рацію. Дякую за те, що ви підвищили рівень поінформованості (: Я протестував свої рішення проти інших, запропонованих тут, і довів це на собі. Також я відредагував свою відповідь, щоб це відобразити. Будь ласка, подумайте про те, щоб зняти свій голос.
Асаф Кац

1
Чудово, дякую за порівняння продуктивності. Смішно, як "неелегантне" рішення найшвидше;) Сюди прийшли шукати покращення forofі add, що здається дуже неефективним. Я дуже бажаю addAll(iterable)методу
набору

1
Версія 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 }
машинопису

4
Посилання jsperf у верхній частині вашої відповіді здається розбитою. Крім того, ви посилаєтесь на рішення bfred, якого я ніде тут не бачу.
jfriend00

12

Затверджена відповідь чудова, але щоразу створює новий набір.

Якщо ви бажаєте замість цього вимкнути існуючий об'єкт, скористайтеся функцією помічника.

Встановити

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]

5

Щоб об'єднати набори в масиві 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це число, а не функція відображення, тому воно не працює.
нікф

1

Виходячи з відповіді Асафа Каца, ось версія машинопису:

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
}

1

Немає сенсу дзвонити, 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()))


1

Перетворіть набори в масиви, вирівняйте їх і нарешті конструктор уніфікує.

const union = (...sets) => new Set(sets.map(s => [...s]).flat());

Будь ласка, не публікуйте лише код як відповідь, але також надайте пояснення, що робить ваш код та як він вирішує проблему. Відповіді з поясненням, як правило, вищої якості і, швидше за все, залучають репутацію.
Марк Ротвевель

0

Ні, для них немає вбудованих операцій, але ви можете легко створити їх своїми:

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;
};


2
Нехай він робить те, що хоче.
пішпіш

1
Якщо кожен робить те , що вони хочуть , ми в кінцевому підсумку з іншим smoosh фіаско
dwelle

0

Приклад

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']


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.