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)?

246
jameslk 14 Aug. 2015 im 04:11

12 Antworten

Beste Antwort

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 letzten zusammengeführten Karte mit diesem Schlüssel ist, wenn mehrere Karten denselben Schlüssel haben.

241
Oriol 14 Aug. 2015 im 01:22

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)));
5
14 Aug. 2015 im 02:49

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]
12
fregante 13 Jän. 2017 im 04:46

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 eine Zusammenführungsoperation zu implementieren (Zusammenführen des Inhalts eines Sets in ein anderes oder einer Map in ein anderes), 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 für ein Map kö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 Set -Objekts 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.

39
jfriend00 25 Aug. 2019 im 23:34

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' ]
46
Bergi 14 Aug. 2015 im 05:05

Beispiel:

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

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

=> {a: 5, b: 1, c: 2}
1
Nadiar AS 21 März 2020 im 16:56

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 nacheinander Werte hinzufügt (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, bei dem reduce und der Operator spread verwendet werden:

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}

Tipp:

Wir können auch den Operator rest 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}
19
Asaf Katz 22 Sept. 2018 im 12:29

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;
};
0
Bergi 14 Aug. 2015 im 05:10

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

Ich benutze dies in einer reduce Funktion und es ist einfach nur albern. Selbst wenn Sie den Spread-Operator ...array 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()))
0
Steven Spungin 9 Aug. 2019 im 12:50

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']
0
dimpiax 28 Nov. 2017 im 21:50

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
}
1
ndp 30 Jän. 2019 im 00:39

Mit Karte zusammenführen

 let merge = {...map1,...map2};
1
Danilo Santos 16 Dez. 2019 im 15:49