Einfachste Möglichkeit zum Zusammenführen von ES6-Karten / -Sätzen?

170

Gibt es eine einfache Möglichkeit, ES6-Karten zusammenzuführen (wie Object.assign)? Und wenn wir schon dabei sind, was ist mit ES6-Sets (wie Array.concat)?

jameslk
quelle
4
Dieser Blog-Beitrag hat einige Einblicke. 2ality.com/2015/01/es6-set-operations.html
Lemieuxster
AFAIK für die Karte, die Sie verwenden müssen, for..ofweil keyjeder Typ sein kann
Paul S.

Antworten:

264

Für Sets:

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

Für Karten:

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

Beachten Sie, dass der Wert der zusammengeführten Karte der Wert der zuletzt zusammengeführten Karte mit diesem Schlüssel ist, wenn mehrere Karten denselben Schlüssel haben.

Oriol
quelle
4
Dokumentation zu Map: „Konstruktor: new Map([iterable])“, „ iterableist ein Array oder ein anderes iterierbares Objekt, dessen Elemente Schlüssel-Wert-Paare sind (2-Element-Arrays). Jedes Schlüssel-Wert-Paar wird der neuen Karte hinzugefügt. “ - Nur als Referenz.
user4642212
34
Seien Sie bei großen Sets nur gewarnt, dass dies den Inhalt beider Sets zweimal wiederholt, einmal, um ein temporäres Array zu erstellen, das die Vereinigung der beiden Sets enthält, und übergeben Sie dieses temporäre Array dann an den Set-Konstruktor, wo es erneut iteriert wird, um das neue Set zu erstellen .
jfriend00
2
@ jfriend00: siehe jameslk Antwort unten für eine bessere Methode
Bergi
2
@torazaburo: Wie jfriend00 sagte, erzeugt die Oriols-Lösung unnötige Zwischen-Arrays. Durch die Übergabe eines Iterators an den MapKonstruktor wird dessen Speicherverbrauch vermieden.
Bergi
2
Ich bin verblüfft, dass ES6 Set / Map keine effizienten Zusammenführungsmethoden bietet.
Andy
47

Hier ist meine Lösung mit Generatoren:

Für Karten:

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

Für Sets:

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
quelle
35
(IIGFE = sofort aufgerufene Generatorfunktionsausdruck)
Oriol
3
schön auch m2.forEach((k,v)=>m1.set(k,v))wenn du einfache browser unterstützung
willst
9
@caub nette Lösung, aber denken Sie daran, dass der erste Parameter von forEach der Wert ist, daher sollte Ihre Funktion m2.forEach ((v, k) => m1.set (k, v)) sein;
David Noreña
43

Aus Gründen, die ich nicht verstehe, können Sie den Inhalt eines Sets nicht direkt mit einer integrierten Operation zu einem anderen hinzufügen. Operationen wie Vereinigung, Überschneiden, Zusammenführen usw. sind ziemlich einfache Mengenoperationen, aber nicht integriert. Glücklicherweise können Sie diese alle ziemlich einfach selbst konstruieren.

Um einen Zusammenführungsvorgang zu implementieren (Zusammenführen des Inhalts eines Sets in einem anderen oder einer Map in einem anderen), können Sie dies mit einer einzelnen .forEach()Zeile tun :

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

Und zum einen Mapkönnten Sie dies tun:

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

Oder in der ES6-Syntax:

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

Zu Ihrer Information, wenn Sie eine einfache Unterklasse des integrierten SetObjekts möchten , das eine .merge()Methode enthält , können Sie Folgendes verwenden:

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

Dies soll in der eigenen Datei setex.js sein, die Sie dann require()in node.js einfügen und anstelle des integrierten Sets verwenden können.

jfriend00
quelle
3
Ich glaube nicht new Set(s, t). funktioniert. Der tParameter wird ignoriert. Es ist auch offensichtlich kein vernünftiges Verhalten, addden Typ seines Parameters zu erkennen und wenn eine Menge die Elemente der Menge hinzufügt, da es dann keine Möglichkeit gibt, eine Menge selbst zu einer Menge hinzuzufügen.
@torazaburo - Was die .add()Methode betrifft, mit der ein Set erstellt wird, verstehe ich Ihren Standpunkt. Ich finde nur, dass dies weitaus weniger nützlich ist, als Sets kombinieren zu können, .add()da ich noch nie ein Set oder Sets benötigt habe, aber ich musste Sets oft zusammenführen. Nur eine Frage der Meinung über die Nützlichkeit eines Verhaltens gegenüber dem anderen.
jfriend00
Argh, ich hasse es, dass dies für Maps nicht funktioniert: n.forEach(m.add, m)- Es invertiert Schlüssel / Wert-Paare!
Bergi
@Bergi - ja, es ist seltsam, dass Map.prototype.forEach()und Map.prototype.set()Argumente umgekehrt haben. Scheint wie ein Versehen von jemandem. Es erzwingt mehr Code, wenn versucht wird, sie zusammen zu verwenden.
jfriend00
@ jfriend00: OTOH, es macht irgendwie Sinn. setDie Parameterreihenfolge ist für Schlüssel / Wert-Paare natürlich und forEachrichtet sich nach der Methode Arrays forEach(und nach Dingen wie $.eachoder _.eachdie auch Objekte aufzählen).
Bergi
20

Bearbeiten :

Ich habe meine ursprüngliche Lösung mit anderen hier vorgeschlagenen Lösungen verglichen und festgestellt, dass sie sehr ineffizient ist.

Der Benchmark selbst ist sehr interessant ( Link ). Er vergleicht 3 Lösungen (höher ist besser):

  • @ bfred.its Lösung, die Werte nacheinander addiert (14.955 op / sec)
  • @ jameslks Lösung, die einen selbstaufrufenden Generator verwendet (5.089 op / sec)
  • meine eigene, die Reduce & Spread verwendet (3.434 op / sec)

Wie Sie sehen können, ist die Lösung von @ bfred.it definitiv der Gewinner.

Leistung + Unveränderlichkeit

In diesem Sinne ist hier eine leicht modifizierte Version, die die ursprüngliche Menge nicht mutiert und eine variable Anzahl von Iterablen ausnimmt, die als Argumente kombiniert werden sollen:

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

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

  return set;
}

Verwendung:

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}

Ursprüngliche Antwort

Ich möchte einen anderen Ansatz vorschlagen, der reduceund den spreadOperator verwendet:

Implementierung

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

Verwendung:

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}

Trinkgeld:

Wir können auch den restOperator verwenden, um die Oberfläche ein bisschen schöner zu machen:

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

Anstatt ein Array von Mengen zu übergeben, können wir jetzt eine beliebige Anzahl von Argumenten von Mengen übergeben:

union(a, b, c) // {1, 2, 3, 4, 5, 6}
Asaf Katz
quelle
Das ist schrecklich ineffizient.
Bergi
1
Hallo @Bergi, du hast recht. Vielen Dank, dass Sie mein Bewusstsein geschärft haben (: Ich habe meine Lösungen gegen andere hier vorgeschlagene getestet und es selbst bewiesen. Außerdem habe ich meine Antwort bearbeitet, um dies widerzuspiegeln. Bitte ziehen Sie in Betracht, Ihre Ablehnung zu entfernen.
Asaf Katz
1
Super, danke für den Leistungsvergleich. Komisch, wie die "unelegante" Lösung am schnellsten ist;) Kam hierher, um nach einer Verbesserung gegenüber forofund zu suchen add, was einfach sehr ineffizient zu sein scheint. Ich wünsche mir wirklich eine addAll(iterable)Methode für Sets
Bruno Schäpper
1
Typoskript-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 }
ndp
4
Der jsperf-Link oben in Ihrer Antwort scheint kaputt zu sein. Sie beziehen sich auch auf die Lösung von bfred, die ich hier nirgendwo sehe.
jfriend00
12

Die genehmigte Antwort ist großartig, aber das schafft jedes Mal ein neues Set.

Wenn Sie stattdessen ein vorhandenes Objekt mutieren möchten , verwenden Sie eine Hilfsfunktion.

einstellen

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

Verwendung:

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

Karte

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

Verwendung:

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]
fregante
quelle
5

Sie können die Sets in den Array-Sets zusammenführen

var Sets = [set1, set2, set3];

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

Es ist mir etwas rätselhaft, warum das Folgende, das gleichwertig sein sollte, zumindest in Babel versagt:

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

quelle
Array.fromnimmt zusätzliche Parameter an, wobei der zweite eine Zuordnungsfunktion ist. Array.prototype.mapÜbergibt drei Argumente an seinen Rückruf : (value, index, array), also ruft er effektiv auf Sets.map((set, index, array) => Array.from(set, index, array). Offensichtlich indexist eine Zahl und keine Zuordnungsfunktion, so dass es fehlschlägt.
Nickf
1

Basierend auf Asaf Katz 'Antwort ist hier eine maschinengeschriebene Version:

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
quelle
1

Es macht keinen Sinn , aufzurufen, new Set(...anArrayOrSet)wenn mehrere Elemente (entweder aus einem Array oder einem anderen Satz) zu einem vorhandenen Satz hinzugefügt werden .

Ich benutze dies in einer reduceFunktion, und es ist einfach nur albern. Selbst wenn Sie den ...arraySpread-Operator zur Verfügung haben, sollten Sie ihn in diesem Fall nicht verwenden, da er Prozessor-, Speicher- und Zeitressourcen verschwendet.

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

Demo-Snippet

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

Transformieren Sie die Sets in Arrays, reduzieren Sie sie und schließlich wird der Konstruktor vereinheitlicht.

const union = (...sets) => new Set(sets.map(s => [...s]).flat());
NicoAdrian
quelle
Bitte posten Sie nicht nur Code als Antwort, sondern geben Sie auch eine Erklärung, was Ihr Code tut und wie er das Problem der Frage löst. Antworten mit einer Erklärung sind normalerweise von höherer Qualität und ziehen eher positive Stimmen an.
Mark Rotteveel
0

Nein, es gibt keine integrierten Operationen für diese, aber Sie können sie ganz einfach selbst erstellen:

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
quelle
2
Lass ihn machen was er will.
Pishpish
1
Wenn jeder tut, was er will, wird es ein weiteres Smoosh-Debakel geben
Dwelle,
0

Beispiel

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

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

    return dataMap
}

Verwendung

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

Sie können die Spread-Syntax verwenden , um sie zusammenzuführen:

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
quelle
-1

mit Karte zusammenführen

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