ES6 맵 / 세트를 병합하는 가장 간단한 방법은 무엇입니까?


170

ES6 Maps를 같이 통합하는 간단한 방법이 Object.assign있습니까? 그리고 우리가 그것을하고있는 동안 ES6 세트는 Array.concat어떻습니까?


4
이 블로그 게시물에는 약간의 통찰력이 있습니다. 2ality.com/2015/01/es6-set-operations.html
lemieuxster 1

AFAIK는 대한 지도 는 사용해야 for..of하기 때문에 key어떤 유형이 될 수 있습니다
폴 S.

답변:


265

세트의 경우 :

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

지도 :

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

여러 맵에 동일한 키가있는 경우 병합 된 맵의 값은 해당 키와 마지막으로 병합 된 맵의 값이됩니다.


4
이 문서에서 Map"생성자 : new Map([iterable])", " iterable배열이나 요소가 키 - 값 쌍 (2 소자 어레이) 반복 가능한 다른 목적이다. 각 키-값 쌍이 새 맵에 추가됩니다.” — 참조 용으로 만 사용하십시오.
user4642212 1

34
큰 세트의 경우 두 세트의 내용을 두 번 반복하고 두 세트의 합집합을 포함하는 임시 배열을 한 번 생성 한 다음 해당 임시 배열을 Set 반복자에게 전달하여 새 Set을 만들기 위해 다시 반복한다는 점에 유의하십시오. .
jfriend00

2
@ jfriend00 : 더 나은 방법은 아래 jameslk의 답변 참조
Bergi

2
@torazaburo : jfriend00이 말했듯이 Oriols 솔루션은 불필요한 중간 배열을 만듭니다. 반복자를 Map생성자에 전달하면 메모리 소비를 피할 수 있습니다.
Bergi

2
ES6 Set / Map은 효율적인 병합 방법을 제공하지 않습니다.
Andy

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의 첫 번째 매개 변수는 value이므로 함수는 m2.forEach ((v, k) => m1.set (k, v))이어야합니다.
David Noreña

44

내가 이해하지 못하는 이유로 내장 작업으로 한 세트의 내용을 다른 세트에 직접 추가 할 수 없습니다. 공용체, 교차, 병합 등과 같은 작업은 기본 설정 작업이지만 기본 제공 작업은 아닙니다. 다행스럽게도이 모든 것을 상당히 쉽게 구성 할 수 있습니다.

따라서 병합 작업을 구현하려면 (한 세트의 내용을 다른 세트로 병합하거나 하나의 맵을 다른 맵으로 병합) 한 .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에 들어가 내장 세트 대신 사용할 .


3
나는 생각하지 않는다 new Set(s, t). 공장. 이 t매개 변수는 무시됩니다. 또한 add매개 변수의 유형을 감지하고 세트가 세트의 요소를 추가하는 경우 세트 자체를 세트에 추가 할 수있는 방법이 없기 때문에 합리적인 동작이 아닙니다 .

@torazaburo- .add()세트를 취하는 방법은 요점을 이해합니다. .add()세트 또는 세트가 필요하지 않았기 때문에 세트를 결합 할 수있는 것보다 훨씬 적은 사용을 발견 했지만 세트를 여러 번 병합해야했습니다. 한 행동과 다른 행동의 유용성에 대한 의견의 문제.
jfriend00

아아, 나는 이것이 맵에서 작동하지 않는 것을 싫어한다 : n.forEach(m.add, m)-그것은 키 / 값 쌍을 반전시킵니다!
Bergi

@Bergi은 - 그래, 그 홀수 Map.prototype.forEach()Map.prototype.set()인수를 반대했다. 누군가가 감독하는 것 같습니다. 함께 사용할 때 더 많은 코드가 필요합니다.
jfriend00

@ jfriend00 : OTOH, 그것은 이해가됩니다. set매개 변수 순서는 키 / 값 쌍에 대해 자연스럽고 s 메소드 (및 객체를 열거 하거나 열거하는 것)와 forEach정렬됩니다 . ArrayforEach$.each_.each
Bergi

20

편집하다 :

다른 솔루션 제안과 비교하여 원래 솔루션을 벤치마킹했으며 매우 비효율적이라는 것을 알았습니다.

벤치 마크 자체는 매우 흥미 롭습니다 ( link ) 3 가지 솔루션을 비교합니다 (높을수록 좋습니다).

  • @ bfred.it의 솔루션으로 값을 하나씩 추가합니다 (14,955 op / sec)
  • 자체 호출 생성기를 사용하는 @jameslk의 솔루션 (5,089 op / sec)
  • 내 자신의 감소 및 확산 (3,434 op / sec)

보시다시피 @ bfred.it의 솔루션이 확실히 승자입니다.

성능 + 불변성

이를 염두에두고, 원래 세트를 변경하지 않고 인수로 결합 할 수있는 반복 가능한 변수를 제외하고 약간 수정 된 버전이 있습니다.

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}

원래 답변

를 사용하여 다른 접근법을 제안하고 싶습니다 reduce.spread 연산자를 .

이행

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

이제 배열배열 을 전달하는 대신 임의의 수의 인수 를 전달할 수 있습니다 .

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

이것은 엄청나게 비효율적입니다.
Bergi

1
안녕하세요 @ Bergi, 당신이 맞아요. . (내 인식 제고를위한 감사합니다 : 다른 사람들이 여기에 제안하고 나 자신을 위해 그것을 증명에 대한 내 솔루션을 테스트 한 또한, 나는 그것을 반영하기 위해 내 대답을 편집 한 당신의 downvote을 제거하는 것을 고려하십시오..
Asaf 카츠

1
성능 비교에 감사드립니다. 재미는 방법 "unelegant"솔루션은 가장 빠른) 과도 개선을 찾기 위해 여기에 온 forofadd단지 매우 비효율적 인 것 같습니다. 나는 addAll(iterable)세트 에 대한 방법을 정말로 원합니다
Bruno Schäpper

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

적어도 바벨에서 다음과 같은 것이 왜 실패하는지는 나에게 약간의 신비입니다.

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숫자이며 매핑 기능이 아니므로 실패합니다.
nickf

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

집합을 배열로 변환하고 평평하게하면 결국 생성자가 uniqify됩니다.

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

코드는 답변으로 만 게시 할뿐만 아니라 코드의 기능과 질문의 문제를 해결하는 방법에 대한 설명도 제공하십시오. 설명이 포함 된 답변은 일반적으로 품질이 높으며 투표를 유도 할 가능성이 높습니다.
마크 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
모두가 원하는 것을하면 우린 또 다른 매끄러운 대결로 끝날 것입니다
dwelle

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}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.