web-dev-qa-db-fra.com

Le moyen le plus simple de fusionner ES6 Maps/Sets?

Existe-t-il un moyen simple de fusionner des cartes ES6 (comme Object.assign)? Et tant que nous y sommes, qu'en est-il des ensembles ES6 (comme Array.concat)?

103
jameslk

Pour les ensembles:

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

Pour les cartes:

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

Notez que si plusieurs cartes ont la même clé, la valeur de la carte fusionnée sera celle de la dernière carte fusionnée avec cette clé.

170
Oriol

Voici ma solution en utilisant des générateurs:

Pour les cartes:

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

Pour les ensembles:

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

Pour des raisons que je ne comprends pas, vous ne pouvez pas ajouter directement le contenu d’un Set à un autre avec une opération intégrée. 

Il semblerait naturel que .add() ait détecté que vous dépassiez un autre objet Set, puis que vous récupériez tous les éléments de cet ensemble (c'est ainsi que mon own set object - avant qu'il y ait eu une spécification ES6 Set) fonctionne . Mais, ils ont choisi de ne pas l'appliquer de cette façon.

Au lieu de cela, vous pouvez le faire avec une seule ligne .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

Et, pour un Map, vous pouvez faire ceci:

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);
});
23
jfriend00

Modifier:

J'ai comparé ma solution initiale à d'autres solutions suggérées ici et constaté qu'elle était très inefficace.

Le benchmark lui-même est très intéressant ( link ) Il compare 3 solutions (le plus haut est le mieux):

  • La solution de @ bfred.it, qui ajoute des valeurs une par une (14 955 op/s)
  • La solution de @ jameslk, qui utilise un générateur à invocation automatique (5 089 op/s)
  • mon propre, qui utilise réduire et répandre (3,434 op/sec)

Comme vous pouvez le constater, la solution de @ bfred.it est définitivement gagnante.

Performance + Immutabilité

Dans cet esprit, voici une version légèrement modifiée qui ne le fait pas mute l'ensemble d'origine et excepte un nombre variable d'itérables vers combiner comme arguments:

function union(...iterables) {
  const set = new Set();

  for (let iterable of iterables) {
    for (let item of iterable) {
      set.add(item);
    }
  }

  return set;
}

Utilisation:

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}

Réponse originale

Je voudrais suggérer une autre approche, en utilisant reduce et l'opérateur spread:

La mise en oeuvre

function union (sets) {
  return sets.reduce((combined, list) => {
    return new Set([...combined, ...list]);
  }, new Set());
}

Utilisation:

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}

Pointe:

Nous pouvons également utiliser l'opérateur rest pour rendre l'interface un peu plus agréable:

function union (...sets) {
  return sets.reduce((combined, list) => {
    return new Set([...combined, ...list]);
  }, new Set());
}

Maintenant, au lieu de passer un array d'ensembles, nous pouvons passer un nombre arbitraire de arguments d'ensembles:

union(a, b, c) // {1, 2, 3, 4, 5, 6}
11
Asaf Katz

La réponse approuvée est excellente mais crée à chaque fois un nouvel ensemble.

Si vous souhaitez plutôt muter un objet existant, utilisez une fonction d'assistance.

Ensemble

function concatSets(set, ...iterables) {
    for (const iterable of iterables) {
        for (const item of iterable) {
            set.add(item);
        }
    }
}

Usage:

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

Carte

function concatMaps(map, ...iterables) {
    for (const iterable of iterables) {
        for (const item of iterable) {
            map.set(...item);
        }
    }
}

Usage:

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]
10
bfred.it

Pour fusionner les ensembles dans le tableau Ensembles, vous pouvez faire

var Sets = [set1, set2, set3];

var merged = new Set([].concat(...Sets.map(set => Array.from(set))));

Il m'est un peu mystérieux pourquoi les raisons suivantes, qui devraient être équivalentes, échouent au moins dans Babel:

var merged = new Set([].concat(...Sets.map(Array.from)));
4
user663031

Non, il n'y a pas d'opération intégrée pour ceux-ci, mais vous pouvez facilement les créer vous-même:

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

Sur la base de la réponse d'Asaf Katz, voici une version TypeScript:

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
}
0
ndp

Exemple

const mergedMaps = (...maps) => {
    const dataMap = new Map([])

    for (const map of maps) {
        for (const [key, value] of map) {
            dataMap.set(key, value)
        }
    }

    return dataMap
}

Usage

const map = mergedMaps(new Map([[1, false]]), new Map([['foo', 'bar']]), new Map([['lat', 1241.173512]]))
Array.from(map.keys()) // [1, 'foo', 'lat']
0
dimpiax

Il n'a aucun sens d'appeler new Set(...anArrayOrSet) lors de l'ajout de plusieurs éléments (d'un tableau ou d'un autre ensemble) à un ensemble existant.

J'utilise ceci dans une fonction reduce, et c'est tout simplement idiot. Même si vous disposez de l'opérateur ...array spread, vous ne devez pas l'utiliser dans ce cas, car cela gaspille du temps, de la mémoire et du processeur.

// 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))
  }
}

Extrait de démonstration

// 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()))
0
Steven Spungin