Il modo più semplice per unire mappe / set ES6?


Risposte:


266

Per set:

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

Per le mappe:

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

Se più mappe hanno la stessa chiave, il valore della mappa unita sarà il valore dell'ultima mappa unita con quella chiave.


4
Documentazione su Map: “Costruttore: new Map([iterable])”, “ iterableè una matrice o altro oggetto iterabile i cui elementi sono coppie chiave-valore (matrici a 2 elementi). Ogni coppia chiave-valore viene aggiunta alla nuova mappa. " - solo come riferimento.
user4642212

34
Per insiemi di grandi dimensioni, basta essere avvisati che ciò ripeterà due volte il contenuto di entrambi gli insiemi, una volta per creare un array temporaneo contenente l'unione dei due insiemi, quindi passa quell'array temporaneo al costruttore Set dove viene ripetuto per creare il nuovo set .
jfriend00

2
@ jfriend00: vedi la risposta di jameslk qui sotto per un metodo migliore
Bergi,

2
@torazaburo: come ha detto jfriend00, la soluzione Oriols crea array intermedi non necessari. Passare un iteratore al Mapcostruttore evita il loro consumo di memoria.
Bergi,

2
Mi dispiace che ES6 Set / Map non fornisca metodi di unione efficienti.
Andy,

47

Ecco la mia soluzione usando i generatori:

Per le mappe:

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

Per set:

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 = Espressione della funzione del generatore immediatamente invocato)
Oriol

3
bello anche m2.forEach((k,v)=>m1.set(k,v))se vuoi un facile supporto per il browser
caub

9
@caub bella soluzione ma ricorda che il primo parametro di forEach è value quindi la tua funzione dovrebbe essere m2.forEach ((v, k) => m1.set (k, v));
David Noreña,

44

Per motivi che non capisco, non è possibile aggiungere direttamente il contenuto di un set a un altro con un'operazione integrata. Operazioni come unione, intersezione, unione, ecc ... sono operazioni di set piuttosto basilari, ma non sono integrate. Fortunatamente, puoi costruirli abbastanza da solo.

Quindi, per implementare un'operazione di unione (unendo il contenuto di un Set in un altro o una Mappa in un altro), puoi farlo con una sola .forEach()riga:

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

E, per a Map, potresti farlo:

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

Oppure, nella sintassi ES6:

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

Cordiali saluti, se si desidera una sottoclasse semplice Setdell'oggetto incorporato che contiene un .merge()metodo, è possibile utilizzare questo:

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

Questo è pensato per essere nel suo file setex.js che puoi quindi require()in node.js e usare al posto del Set integrato.


3
Non credo new Set(s, t). lavori. Il tparametro viene ignorato. Inoltre, non è ovviamente ragionevole avere il addrilevamento del tipo del suo parametro e se un set aggiunge gli elementi del set, perché non ci sarebbe modo di aggiungere un set stesso a un set.

@torazaburo - per quanto riguarda il .add()metodo di presa di un set, capisco il tuo punto. Trovo che sia molto meno utile che riuscire a combinare set usando .add()come non ho mai avuto bisogno di un Set o Set, ma ho avuto bisogno di unire più set. È solo una questione di opinione sull'utilità di un comportamento rispetto all'altro.
jfriend00

Argh, odio che questo non funzioni per le mappe: n.forEach(m.add, m)- inverte coppie chiave / valore!
Bergi,

@Bergi - sì, è strano Map.prototype.forEach()e Map.prototype.set()hanno argomenti invertiti. Sembra una svista da parte di qualcuno. Forza più codice quando si cerca di usarli insieme.
jfriend00,

@ jfriend00: OTOH, ha un po 'senso. setl'ordine dei parametri è naturale per le coppie chiave / valore, forEachè allineato con Arrayil forEachmetodo s (e cose simili $.eacho _.eachche elencano anche gli oggetti).
Bergi,

21

modificare :

Ho confrontato la mia soluzione originale con altre soluzioni suggerite qui e ho scoperto che è molto inefficiente.

Il benchmark stesso è molto interessante ( link ) Confronta 3 soluzioni (più alto è meglio):

  • La soluzione di @ bfred.it, che aggiunge i valori uno per uno (14.955 op / sec)
  • La soluzione di @ jameslk, che utilizza un generatore auto invocante (5.089 op / sec)
  • il mio, che utilizza riduci e diffondi (3.434 op / sec)

Come puoi vedere, la soluzione di @ bfred.it è sicuramente il vincitore.

Prestazioni + immutabilità

Con questo in mente, ecco una versione leggermente modificata che non muta il set originale ed esclude un numero variabile di iterabili da combinare come argomenti:

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

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

  return set;
}

Uso:

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}

Risposta originale

Vorrei suggerire un altro approccio, usando reducee ilspread operatore:

Implementazione

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

Uso:

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}

Mancia:

Possiamo anche utilizzare l' restoperatore per rendere l'interfaccia un po 'più gradevole:

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

Ora, invece di passare una matrice di insiemi, possiamo passare un numero arbitrario di argomenti di insiemi:

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

Questo è orribilmente inefficiente.
Bergi,

1
Ciao @Bergi, hai ragione. Grazie per aver aumentato la mia consapevolezza (: ho testato le mie soluzioni contro altri suggerite qui e l'ho dimostrato per me stesso. Inoltre, ho modificato la mia risposta per riflettere ciò. Ti preghiamo di considerare di rimuovere il tuo voto negativo.
Asaf Katz,

1
Ottimo, grazie per il confronto delle prestazioni. Divertente come la soluzione "non elegante" sia più veloce;) Sono venuto qui per cercare un miglioramento forofe add, il che sembra molto inefficiente. Vorrei davvero un addAll(iterable)metodo sui set
Bruno Schäpper il

1
Versione dattiloscritta: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

4
Il collegamento jsperf nella parte superiore della risposta sembra interrotto. Inoltre, ti riferisci alla soluzione di Bfred che non vedo da nessuna parte qui.
jfriend00,

12

La risposta approvata è ottima, ma crea sempre un nuovo set.

Se vuoi mutare un oggetto esistente, usa una funzione di aiuto.

Impostato

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

Uso:

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

Carta geografica

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

Uso:

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

Per unire i set nei set di array, puoi farlo

var Sets = [set1, set2, set3];

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

È leggermente misterioso per me il motivo per cui quanto segue, che dovrebbe essere equivalente, fallisce almeno in Babele:

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

Array.fromaccetta parametri aggiuntivi, il secondo è una funzione di mappatura. Array.prototype.mappassa tre argomenti al suo callback:, (value, index, array)quindi sta effettivamente chiamando Sets.map((set, index, array) => Array.from(set, index, array). Ovviamente, indexè un numero e non una funzione di mappatura, quindi fallisce.
Nickf

1

Sulla base della risposta di Asaf Katz, ecco una versione dattiloscritta:

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

Non ha senso chiamare new Set(...anArrayOrSet)quando si aggiungono più elementi (da un array o da un altro set) a un set esistente .

Lo uso in una reducefunzione ed è semplicemente stupido. Anche se hai l' ...arrayoperatore di diffusione disponibile, in questo caso non dovresti usarlo, poiché spreca risorse di processore, memoria e tempo.

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

Frammento dimostrativo

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

Trasforma gli insiemi in array, appiattiscili e infine il costruttore si unificherà.

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

Si prega di non pubblicare solo il codice come risposta, ma anche di fornire una spiegazione su cosa fa il codice e su come risolve il problema della domanda. Le risposte con una spiegazione sono generalmente di qualità superiore e hanno maggiori probabilità di attirare voti positivi.
Mark Rotteveel,

0

No, non ci sono operazioni integrate per queste, ma puoi facilmente crearle tue:

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
Lascialo fare quello che vuole.
Pishpish,

1
Se tutti fanno quello che vogliono, finiremo con un'altra debacle
smoosh

0

Esempio

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

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

    return dataMap
}

uso

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

0

È possibile utilizzare la sintassi di diffusione per unirli insieme:

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

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

=> {a: 5, b: 1, c: 2}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.