合并ES6地图/集的最简单方法?


Answers:


264

对于套:

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

对于地图:

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

请注意,如果多个映射具有相同的键,则合并映射的值将是具有该键的最后一个合并映射的值。


4
关于Map“构造函数:new Map([iterable])”,“”的文档iterable是数组或其他可迭代对象,其元素是键值对(2元素数组)。每个键值对均已添加到新地图中。” -仅供参考。
user4642212

34
对于大型Set,请注意,这将两次迭代两个Set的内容,一次创建一个包含两个Set的并集的临时数组,然后将该临时数组传递给Set构造函数,在该构造函数中再次对其进行迭代以创建新的Set 。
jfriend00

2
@ jfriend00:请参阅下面的jameslk答案以获得更好的方法
Bergi,2015年

2
@torazaburo:正如jfriend00所说,Oriols解决方案确实会创建不必要的中间数组。将迭代器传递给Map构造函数可避免其内存消耗。
Bergi

2
我为ES6 Set / Map没有提供有效的合并方法而感到难过。
安迪

47

这是我使用发电机的解决方案:

对于地图:

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

对于套装:

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 =立即调用的生成器函数表达式)
Oriol

3
m2.forEach((k,v)=>m1.set(k,v))如果您想要简单的浏览器支持,也很好
caub

9
@caub解决方案不错,但请记住forEach的第一个参数是值,因此您的函数应为m2.forEach((v,k)=> m1.set(k,v));
DavidNoreña18年

43

由于我不明白的原因,您无法使用内置操作将一个Set的内容直接添加到另一个Set中。诸如联合,相交,合并等操作是非常基本的集合操作,但不是内置的。幸运的是,您可以自己轻松构建所有这些。

因此,要实现合并操作(将一个Set的内容合并到另一个中,或将一个Map合并到另一个中),可以用.forEach()一行完成:

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

而且,对于Map,您可以执行以下操作:

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

或者,使用ES6语法:

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

仅供参考,如果您想要一个Set包含.merge()方法的内置对象的简单子类,则可以使用以下方法:

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

这意味着将其保存在自己的文件setex.js中,然后您可以将其require()放入node.js中并代替内置Set使用。


3
我不认为new Set(s, t)。作品。该t参数将被忽略。而且,显然,add检测其参数的类型以及是否将集合添加集合中的元素也是不合理的行为,因为那样便无法将集合本身​​添加到集合中。

@torazaburo-关于.add()采用Set 的方法,我理解您的意思。我发现它的用途远少于能够使用来组合集合,.add()因为我从来不需要一个或多个Set,但是我需要多次合并集合。只是关于一种行为与另一种行为的有用性的观点问题。
jfriend00

哎呀,我讨厌这不适用于地图:n.forEach(m.add, m)-确实会反转键/值对!
Bergi 2015年

@Bergi -是的,这是奇怪的是,Map.prototype.forEach()Map.prototype.set()互换了争论。似乎是某人的疏忽。尝试一起使用它们时,将强制使用更多代码。
jfriend00

@ jfriend00:太好了,这很有道理。set参数顺序对于键/值对是很自然的,forEachArrays forEach方法(以及类似$.each_.each也枚举对象的东西)对齐。
Bergi

20

编辑

我将原始解决方案与此处提出的其他解决方案进行了基准比较,发现效率很低。

基准测试本身非常有趣(链接),它比较了3个解决方案(越高越好):

  • @ bfred.it的解决方案,将值一一添加(14,955 op / sec)
  • @jameslk的解决方案,使用自调用生成器(5,089 op / sec)
  • 我自己的,使用减少和传播(3,434 op / sec)

如您所见,@ bfred.it的解决方案绝对是赢家。

性能+不变性

考虑到这一点,这是一个经过稍微修改的版本,它不会变异原始集合,并且除了可变数量的Iterables可以组合为参数之外:

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

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

  return set;
}

用法:

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}

原始答案

我想建议另一种方法,使用reducespread运算符:

实作

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

用法:

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}

小费:

我们还可以利用rest运算符来使界面更好看:

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

现在,我们可以传递任意数量的set 参数,而不是传递set 数组

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

这是非常低效的。
Bergi '18年

1
嗨@Bergi,您是对的。。感谢养育我的意识(:我测试过我的解决方案,对他人的建议在这里和证明了这一点为自己另外,我已经编辑我的答案,以反映请考虑删除您的downvote。
阿萨夫·卡茨

1
很好,谢谢您的性能比较。有趣的是,“简单”的解决方案是最快的;)来到这里寻求对forofand 的改进add,这似乎效率很低。我真的很想addAll(iterable)在Sets上找到一种方法
BrunoSchäpper'18

1
打字稿版本: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
答案顶部附近的jsperf链接似乎无效。另外,您指的是bfred的解决方案,我在这里没有看到。
jfriend00

12

批准的答案很好,但是每次都会创建一个新的答案。

如果你想变异现有的对象,而不是使用一个辅助功能。

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

用法:

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

地图

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

用法:

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

要将集合合并到数组集合中,可以执行

var Sets = [set1, set2, set3];

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

对于我来说有点神秘,为什么以下这些应该等效的至少在Babel中失败了:

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

Array.from需要其他参数,第二个是映射函数。Array.prototype.map将三个参数传递给其回调:(value, index, array),因此它实际上是在调用Sets.map((set, index, array) => Array.from(set, index, array)。显然,index是数字而不是映射函数,因此它失败了。
尼克

1

根据Asaf Katz的回答,这是打字稿版本:

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

没有任何意义调用new Set(...anArrayOrSet)当添加多个元素(来自阵列或另一组)到现有的一组

我在reduce函数中使用它,这简直是愚蠢的。即使您可以使用...array扩展运算符,在这种情况下也不要使用它,因为它浪费了处理器,内存和时间资源。

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

演示片段

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

将集合转换为数组,将它们展平,最后构造函数将进行唯一化处理。

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

请不要只发布代码作为答案,还请提供解释代码的作用以及如何解决问题的方法。带有解释的答案通常质量更高,并且更有可能吸引投票。
Mark Rotteveel

0

不,没有这些的内置操作,但是您可以轻松地自己创建它们:

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
让他做他想做的。
pishpish

1
如果每个人都按照自己的意愿去做,我们最终将遭受一场混乱的
打击

0

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

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

    return dataMap
}

用法

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

0

您可以使用传播语法将它们合并在一起:

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.