Le moyen le plus simple de fusionner des cartes / ensembles ES6?

171

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

Jameslk
la source
4
Ce billet de blog a un aperçu. 2ality.com/2015/01/es6-set-operations.html
lemieuxster
AFAIK pour la carte que vous devrez utiliser for..ofcar il keypeut s'agir de n'importe quel type
Paul S.

Réponses:

266

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 la valeur de la dernière carte fusionnée avec cette clé.

Oriol
la source
4
Documentation sur Map: «Constructor: new Map([iterable])», « iterableest un tableau ou un autre objet itérable dont les éléments sont des paires clé-valeur (tableaux à 2 éléments). Chaque paire clé-valeur est ajoutée à la nouvelle carte. » - juste comme référence.
user4642212
34
Pour les grands ensembles, soyez juste averti que cela itère le contenu des deux ensembles deux fois, une fois pour créer un tableau temporaire contenant l'union des deux ensembles, puis transmet ce tableau temporaire au constructeur de l'ensemble où il est itéré à nouveau pour créer le nouvel ensemble .
jfriend00
2
@ jfriend00: voir la réponse de jameslk ci-dessous pour une meilleure méthode
Bergi
2
@torazaburo: comme jfriend00 l'a dit, la solution Oriols crée des tableaux intermédiaires inutiles. Passer un itérateur au Mapconstructeur évite sa consommation de mémoire.
Bergi
2
Je suis déçu que ES6 Set / Map ne fournit pas de méthodes de fusion efficaces.
Andy
47

Voici ma solution 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' ]
Jameslk
la source
35
(IIGFE = Expression de la fonction de générateur immédiatement invoquée)
Oriol
3
bien aussi m2.forEach((k,v)=>m1.set(k,v))si vous voulez un support de navigateur facile
caub
9
@caub belle solution mais rappelez-vous que le premier paramètre de forEach est value donc votre fonction devrait être m2.forEach ((v, k) => m1.set (k, v));
David Noreña
44

Pour des raisons que je ne comprends pas, vous ne pouvez pas ajouter directement le contenu d'un ensemble à un autre avec une opération intégrée. Les opérations comme l'union, l'intersection, la fusion, etc. sont des opérations d'ensemble assez basiques, mais ne sont pas intégrées. Heureusement, vous pouvez tout construire vous-même assez facilement.

Ainsi, pour implémenter une opération de fusion (fusionner le contenu d'un ensemble dans un autre ou d'une carte dans une autre), vous pouvez le faire avec une seule .forEach()ligne:

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

Ou, dans la syntaxe ES6:

t.forEach((value, key) => s.set(key, value));

Pour info, si vous voulez une simple sous-classe de l' Setobjet intégré qui contient une .merge()méthode, vous pouvez utiliser ceci:

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

Ceci est censé être dans son propre fichier setex.js que vous pouvez ensuite require()utiliser dans node.js et utiliser à la place du Set intégré.

jfriend00
la source
3
Je ne pense pas new Set(s, t). travaux. Le tparamètre est ignoré. Aussi, ce n'est évidemment pas un comportement raisonnable d'avoir adddétecté le type de son paramètre et si un ensemble ajoute les éléments de l'ensemble, car alors il n'y aurait aucun moyen d'ajouter un ensemble lui-même à un ensemble.
@torazaburo - quant à la .add()méthode prenant un Set, je comprends votre point. Je trouve juste que cela est beaucoup moins utile que de pouvoir combiner des ensembles en utilisant .add()car je n'ai jamais eu besoin d'un ensemble ou d'ensembles, mais j'ai eu besoin de fusionner des ensembles plusieurs fois. Juste une question d'opinion sur l'utilité d'un comportement par rapport à l'autre.
jfriend00
Argh, je déteste que cela ne fonctionne pas pour les cartes: n.forEach(m.add, m)- cela inverse les paires clé / valeur!
Bergi
@Bergi - ouais, c'est étrange Map.prototype.forEach()et Map.prototype.set()avoir des arguments inversés. Cela ressemble à un oubli de quelqu'un. Cela force plus de code lorsque vous essayez de les utiliser ensemble.
jfriend00
@ jfriend00: OTOH, c'est un peu logique. setL'ordre des paramètres est naturel pour les paires clé / valeur, forEachest aligné sur la méthode Arrays forEach(et des choses comme $.eachou _.eachqui énumèrent également des objets).
Bergi
21

Modifier :

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

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

  • la solution de @ bfred.it, qui ajoute les valeurs une par une (14 955 op / s)
  • la solution de @ jameslk, qui utilise un générateur auto-appelant (5089 op / s)
  • le mien, qui utilise réduire et étaler (3,434 op / sec)

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

Performance + Immuabilité

Dans cet esprit, voici une version légèrement modifiée qui ne mute pas l'ensemble d'origine et exclut un nombre variable d'itérables à 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;
}

Usage:

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 proposer une autre approche, en utilisant reduceet l' spreadopérateur:

la mise en oeuvre

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

Usage:

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' restopérateur 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 tableau d'ensembles, nous pouvons passer un nombre arbitraire d' arguments d'ensembles:

union(a, b, c) // {1, 2, 3, 4, 5, 6}
Asaf Katz
la source
C'est horriblement inefficace.
Bergi
1
Salut @Bergi, tu as raison. Merci d'avoir augmenté ma prise de conscience (: J'ai testé mes solutions par rapport à d'autres suggérées ici et je l'ai prouvé moi-même. De plus, j'ai modifié ma réponse pour refléter cela. Veuillez envisager de supprimer votre vote négatif.
Asaf Katz
1
Super, merci pour la comparaison des performances. C'est drôle comme la solution "non élégante" est la plus rapide;) Je suis venu ici pour chercher une amélioration par rapport à forofet add, qui semble tout simplement très inefficace. Je souhaite vraiment une addAll(iterable)méthode sur les sets
Bruno Schäpper
1
Version 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 }
dactylographiée
4
Le lien jsperf en haut de votre réponse semble interrompu. Aussi, vous faites référence à la solution de bfred que je ne vois nulle part ici.
jfriend00
12

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

Si vous souhaitez faire muter un objet existant à la place, 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]
frégante
la source
5

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 est un peu mystérieux pour moi pourquoi ce qui suit, qui devrait être équivalent, échoue au moins à Babel:

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

la source
Array.fromprend des paramètres supplémentaires, le second étant une fonction de mappage. Array.prototype.mappasse trois arguments à son callback:, (value, index, array)donc il appelle effectivement Sets.map((set, index, array) => Array.from(set, index, array). De toute évidence, il indexs'agit d'un nombre et non d'une fonction de mappage, donc cela échoue.
nickf
1

Basé sur la réponse d'Asaf Katz, voici une version dactylographiée:

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
}
ndp
la source
1

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

J'utilise ceci dans une reducefonction, et c'est tout simplement idiot. Même si vous disposez de l' ...arrayopérateur de diffusion, vous ne devez pas l'utiliser dans ce cas, car il gaspille des ressources de processeur, de mémoire et de temps.

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

Steven Spungin
la source
1

Transformez les ensembles en tableaux, aplatissez-les et finalement le constructeur s'unifiera.

const union = (...sets) => new Set(sets.map(s => [...s]).flat());
NicoAdrian
la source
Veuillez ne pas publier uniquement le code comme réponse, mais également expliquer ce que fait votre code et comment il résout le problème de la question. Les réponses avec une explication sont généralement de meilleure qualité et sont plus susceptibles d'attirer des votes positifs.
Mark Rotteveel le
0

Non, il n'y a pas d'opérations intégrées 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;
};
Bergi
la source
2
Laissez-le faire ce qu'il veut.
pishpish
1
Si tout le monde fait ce qu'il veut, nous finirons avec une autre débâcle
smoosh
0

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']
dimpiax
la source
0

Vous pouvez utiliser la syntaxe de diffusion pour les fusionner:

const map1 = {a: 1, b: 2}
const map2 = {b: 1, c: 2, a: 5}

const mergedMap = {...a, ...b}

=> {a: 5, b: 1, c: 2}
Nadiar AS
la source
-1

fusionner avec la carte

 let merge = {...map1,...map2};
Danilo Santos
la source