Simplest way to merge ES6 Maps/Sets?

前端 未结 13 1205
广开言路
广开言路 2020-11-29 17:24

Is there a simple way to merge ES6 Maps together (like Object.assign)? And while we\'re at it, what about ES6 Sets (like Array.concat)?

相关标签:
13条回答
  • 2020-11-29 17:48

    For sets:

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

    For maps:

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

    Note that if multiple maps have the same key, the value of the merged map will be the value of the last merging map with that key.

    0 讨论(0)
  • 2020-11-29 17:48

    merge to Map

     let merge = {...map1,...map2};
    
    0 讨论(0)
  • 2020-11-29 17:56

    You can use the spread syntax to merge them together:

    const map1 = {a: 1, b: 2}
    const map2 = {b: 1, c: 2, a: 5}
    
    const mergedMap = {...a, ...b}
    
    => {a: 5, b: 1, c: 2}
    
    0 讨论(0)
  • 2020-11-29 17:57

    Edit:

    I benchmarked my original solution against other solutions suggests here and found that it is very inefficient.

    The benchmark itself is very interesting (link) It compares 3 solutions (higher is better):

    • @bfred.it's solution, which adds values one by one (14,955 op/sec)
    • @jameslk's solution, which uses a self invoking generator (5,089 op/sec)
    • my own, which uses reduce & spread (3,434 op/sec)

    As you can see, @bfred.it's solution is definitely the winner.

    Performance + Immutability

    With that in mind, here's a slightly modified version which doesn't mutates the original set and excepts a variable number of iterables to combine as 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}
    

    Original Answer

    I would like to suggest another approach, using reduce and the spread operator:

    Implementation

    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}
    

    Tip:

    We can also make use of the rest operator to make the interface a bit nicer:

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

    Now, instead of passing an array of sets, we can pass an arbitrary number of arguments of sets:

    union(a, b, c) // {1, 2, 3, 4, 5, 6}
    
    0 讨论(0)
  • 2020-11-29 18:01

    For reasons I do not understand, you cannot directly add the contents of one Set to another with a built-in operation. Operations like union, intersect, merge, etc... are pretty basic set operations, but are not built-in. Fortunately, you can construct these all yourself fairly easily.

    So, to implement a merge operation (merging the contents of one Set into another or one Map into another), you can do this with a single .forEach() line:

    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
    

    And, for a Map, you could do this:

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

    Or, in ES6 syntax:

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

    FYI, if you want a simple subclass of the built-in Set object that contains a .merge() method, you can use this:

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

    This is meant to be in it's own file setex.js that you can then require() into node.js and use in place of the built-in Set.

    0 讨论(0)
  • 2020-11-29 18:01

    To merge the sets in the array Sets, you can do

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

    It is slightly mysterious to me why the following, which should be equivalent, fails at least in Babel:

    var merged = new Set([].concat(...Sets.map(Array.from)));
    
    0 讨论(0)
提交回复
热议问题