ES6マップ/セットをマージする最も簡単な方法は?


170

ES6マップを一緒にマージする簡単な方法はありObject.assignますか(など)?では、ES6セット(などArray.concat)についてはどうでしょうか?


4
このブログ投稿にはいくつかの洞察があります。2ality.com/2015/01/es6-set-operations.html
lemieuxster

どんなタイプでもよいので、使用する必要のあるマップの AFAIKfor..ofkey
Paul S.

回答:


265

セットの場合:

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

マップの場合:

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

複数のマップが同じキーを持っている場合、マージされたマップの値は、そのキーを持つ最後のマージマップの値になることに注意してください。


4
ドキュメントMap:「コンストラクタ:new Map([iterable])」、「」iterableは、要素がキーと値のペア(2要素の配列)である配列またはその他の反復可能なオブジェクトです。キーと値の各ペアが新しいマップに追加されます。」—参考として。
user4642212 2015

34
大きなセットの場合、これにより両方のセットのコンテンツが2回反復され、1回は2つのセットの和集合を含む一時配列が作成され、次にその一時配列がセットコンストラクターに渡され、そこで反復されて新しいセットが作成されます。 。
jfriend00

2
@ jfriend00:より良い方法については、以下のjameslkの回答を参照してください
ベルギ

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年

44

私が理解していない理由で、組込み操作で1つのセットの内容を別のセットの内容に直接追加することはできません。ユニオン、インターセクト、マージなどの操作はかなり基本的なセット操作ですが、組み込みではありません。幸いなことに、これらすべてをかなり簡単に自分で構築できます。

したがって、マージ操作(1つのセットの内容を別のセットに、または1つのマップを別のマップにマージ)を実装するには、1 .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パラメータのタイプを検出し、セットがセットの要素を追加する場合、セット自体をセットに追加する方法がないため、明らかに妥当な動作ではありません。

@虎三郎-Set .add()をとる方法は、ご指摘のとおりです。私はこれまでに.add()1つまたは複数のセットを必要としていたことがないので、を使用してセットを組み合わせることができるよりもはるかに使い道が少ないことに気付きましたが、セットを何度もマージする必要がありました。ある行動と他の行動の有用性についての意見の問題です。
jfriend00

うーん、これはマップでは機能しないのが嫌いですn.forEach(m.add, m)-キー/値のペアを反転させます!
ベルギ2015

@Bergi-ええ、それは奇妙でMap.prototype.forEach()ありMap.prototype.set()、逆の引数を持っています。誰かの見落としのようです。それらを一緒に使用しようとすると、より多くのコードが強制されます。
jfriend00

@ jfriend00:OTOH、それはちょっと理にかなっています。setパラメータの順序は、キー/値ペアのための自然であるforEachと整列しているArraySのforEachメソッド(およびのようなもの$.each_.each、また列挙オブジェクトこと)。
ベルギ

20

編集

元のソリューションと他のソリューションを比較してベンチマークを比較したところ、非常に非効率的であることがわかりました。

ベンチマーク自体は非常に興味深いです(リンク)3つのソリューションを比較します(高いほど良い):

  • @ bfred.itのソリューション。値を1つずつ追加します(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}

これはひどく非効率的です。
ベルギ

1
こんにちは、@ Bergi、あなたは正しい。。私の意識を高めるためのおかげで(:私は他の人に対する私のソリューションは、ここで提案し、自分のためにそれを証明してテストしてみた。また、私はそれを反映するために私の答えを編集したあなたのdownvoteを削除することを検討してください。。
アサフカッツ

1
パフォーマンス比較に感謝します。「不格好な」ソリューションがいかに速いかおかしい;)forofand に対する改善を探すためにここに来ましたがadd、これは非常に非効率的です。私は本当にaddAll(iterable)セットのメソッドを望んでいます
BrunoSchäpper18年

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 }
Typescript

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追加のパラメーターを受け取り、2番目はマッピング関数です。Array.prototype.mapコールバックに3つの引数を渡します:(value, index, array)なので、を効果的に呼び出していSets.map((set, index, array) => Array.from(set, index, array)ます。明らかに、indexは数値であり、マッピング関数ではないため、失敗します。
nickf

1

Asaf Katzの答えに基づいて、typescriptバージョンは次のとおりです:

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
彼に自分のやりたいことをさせてください。
ピッシュピッシュ

1
誰もが、彼らが何をしたいんなら、私たちは別のsmoosh大失敗に終わるだろう
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']


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.