Cara termudah untuk menggabungkan ES6 Maps / Sets?

170

Apakah ada cara sederhana untuk menggabungkan ES6 Maps bersama (seperti Object.assign)? Dan sementara kita melakukannya, bagaimana dengan ES6 Sets (seperti Array.concat)?

jameslk
sumber
4
Posting blog ini memiliki beberapa wawasan. 2ality.com/2015/01/es6-set-operations.html
lemieuxster
AFAIK untuk Peta yang harus Anda gunakan for..ofkarena keydapat jenis apa pun
Paul S.

Jawaban:

265

Untuk set:

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

Untuk peta:

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

Perhatikan bahwa jika beberapa peta memiliki kunci yang sama, nilai peta yang digabungkan akan menjadi nilai peta gabungan terakhir dengan kunci itu.

Oriol
sumber
4
Dokumentasi pada Map: "Konstruktor: new Map([iterable])", " iterableadalah Array atau objek lain yang dapat diulang-ulang yang unsur-unsurnya adalah pasangan nilai kunci (Array 2-elemen). Setiap pasangan nilai kunci ditambahkan ke Peta baru. " - hanya sebagai referensi.
user4642212
34
Untuk Set besar, hanya diperingatkan bahwa ini mengulangi isi dari kedua Set dua kali, sekali untuk membuat array sementara yang berisi gabungan dari dua set, kemudian meneruskan array sementara itu ke konstruktor Set di mana ia diulang lagi untuk membuat Set baru .
jfriend00
2
@ jfriend00: lihat jawaban jameslk di bawah ini untuk metode yang lebih baik
Bergi
2
@torazaburo: seperti kata jfriend00, solusi Oriols membuat array perantara yang tidak perlu. Melewati iterator ke Mapkonstruktor akan menghindari konsumsi memori mereka.
Bergi
2
Saya kecewa bahwa ES6 Set / Map tidak menyediakan metode penggabungan yang efisien.
Andy
47

Inilah solusi saya menggunakan generator:

Untuk Peta:

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

Untuk Set:

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' ]
jameslk
sumber
35
(IIGFE = Ekspresi Fungsi Generator Segera Dibatalkan)
Oriol
3
bagus juga m2.forEach((k,v)=>m1.set(k,v))jika Anda menginginkan dukungan browser yang mudah
caub
9
@ memasukkan solusi yang bagus tetapi ingat bahwa parameter pertama forEach adalah nilai sehingga fungsi Anda harus m2.forEach ((v, k) => m1.set (k, v));
David Noreña
44

Untuk alasan yang saya tidak mengerti, Anda tidak dapat langsung menambahkan konten dari satu Set ke yang lain dengan operasi bawaan. Operasi seperti union, intersect, merge, dll ... adalah operasi set yang cukup mendasar, tetapi tidak terintegrasi. Untungnya, Anda dapat membangun semua ini sendiri dengan cukup mudah.

Jadi, untuk menerapkan operasi penggabungan (menggabungkan konten dari satu Set ke yang lain atau satu Peta ke yang lain), Anda dapat melakukan ini dengan satu .forEach()baris:

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

Dan, untuk a Map, Anda bisa melakukan ini:

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

Atau, dalam sintaks ES6:

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

FYI, jika Anda menginginkan subkelas sederhana dari Setobjek bawaan yang berisi .merge()metode, Anda bisa menggunakan ini:

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

Ini dimaksudkan untuk berada di file setex.js sendiri yang kemudian bisa Anda require()masuki ke node.js dan digunakan sebagai pengganti Set bawaan .

pacar00
sumber
3
Saya tidak berpikir new Set(s, t). bekerja. The tparameter diabaikan. Selain itu, jelas bukan perilaku yang masuk akal untuk addmendeteksi tipe parameternya dan jika suatu himpunan menambahkan elemen himpunan, karena dengan demikian tidak akan ada cara untuk menambahkan himpunan itu sendiri ke himpunan.
@torazaburo - untuk .add()metode pengambilan Set, saya mengerti maksud Anda. Saya hanya menemukan bahwa penggunaan yang jauh lebih sedikit daripada kemampuan untuk menggabungkan set menggunakan .add()karena saya tidak pernah memiliki kebutuhan untuk Set atau Set, tetapi saya memiliki kebutuhan untuk menggabungkan set berkali-kali. Hanya masalah pendapat tentang kegunaan dari satu perilaku vs yang lain.
jfriend00
Argh, saya benci ini tidak berfungsi untuk peta: n.forEach(m.add, m)- itu membalikkan pasangan kunci / nilai!
Bergi
@Bergi - ya, itu aneh Map.prototype.forEach()dan Map.prototype.set()memiliki argumen yang terbalik. Sepertinya pengawasan oleh seseorang. Ini memaksa lebih banyak kode ketika mencoba menggunakannya bersama-sama.
jfriend00
@ jfriend00: OTOH, agak masuk akal. seturutan parameter adalah alami untuk pasangan kunci / nilai, forEachdisejajarkan dengan metode Arrays forEach(dan hal-hal seperti $.eachatau _.eachyang juga menyebutkan objek).
Bergi
20

Edit :

Saya membandingkan solusi asli saya dengan solusi lain yang disarankan di sini dan ternyata sangat tidak efisien.

Benchmark itu sendiri sangat menarik ( tautan ) Ini membandingkan 3 solusi (lebih tinggi lebih baik):

  • solusi @ bfred.it, yang menambahkan nilai satu per satu (14.955 op / detik)
  • Solusi @ jameslk, yang menggunakan generator yang dapat dipanggil sendiri (5.089 op / detik)
  • saya sendiri, yang menggunakan perkecil & sebar (3.434 op / detik)

Seperti yang Anda lihat, solusi @ bfred.it jelas merupakan pemenangnya.

Performa + Kekekalan

Dengan mengingat hal itu, inilah versi yang sedikit dimodifikasi yang tidak mengubah set asli dan mengecualikan sejumlah variabel iterables untuk digabungkan sebagai argumen:

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

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

  return set;
}

Pemakaian:

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}

Jawaban Asli

Saya ingin menyarankan pendekatan lain, menggunakan reducedan spreadoperator:

Penerapan

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

Pemakaian:

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:

Kami juga dapat menggunakan restoperator untuk membuat antarmuka sedikit lebih baik:

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

Sekarang, alih-alih melewati array set, kita dapat melewati sejumlah argumen set arbitrary :

union(a, b, c) // {1, 2, 3, 4, 5, 6}
Asaf Katz
sumber
Ini sangat tidak efisien.
Bergi
1
Hai @Bergi, kamu benar. Terima kasih telah meningkatkan kesadaran saya (: Saya telah menguji solusi saya terhadap orang lain yang disarankan di sini dan membuktikannya untuk diri saya sendiri. Juga, saya telah mengedit jawaban saya untuk mencerminkan hal itu. Harap pertimbangkan untuk menghapus downvote Anda.
Asaf Katz
1
Hebat, terima kasih atas perbandingan kinerja. Lucu bagaimana solusi "tidak penting" tercepat;) Datang ke sini untuk mencari perbaikan forofdan add, yang sepertinya sangat tidak efisien. Saya benar-benar berharap untuk sebuah addAll(iterable)metode pada Sets
Bruno Schäpper
1
Versi naskah: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
Tautan jsperf di dekat bagian atas jawaban Anda tampaknya rusak. Juga, Anda merujuk pada solusi bfred yang tidak saya lihat di mana pun di sini.
jfriend00
12

Jawaban yang disetujui bagus tetapi itu membuat set baru setiap saat.

Jika Anda ingin bermutasi objek yang ada, gunakan fungsi helper.

Set

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

Pemakaian:

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

Peta

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

Pemakaian:

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]
fregante
sumber
5

Untuk menggabungkan set di set array, Anda bisa melakukannya

var Sets = [set1, set2, set3];

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

Bagiku sedikit misterius mengapa yang berikut, yang seharusnya setara, setidaknya gagal di Babel:

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

sumber
Array.frommengambil parameter tambahan, yang kedua adalah fungsi pemetaan. Array.prototype.mapmelewati tiga argumen ke callback nya:, (value, index, array)jadi itu secara efektif memanggil Sets.map((set, index, array) => Array.from(set, index, array). Jelas, indexitu angka dan bukan fungsi pemetaan sehingga gagal.
nickf
1

Didasarkan atas jawaban Asaf Katz, berikut ini adalah versi naskah:

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
}
ndp
sumber
1

Tidak masuk akal untuk memanggil new Set(...anArrayOrSet)ketika menambahkan beberapa elemen (dari array atau set lain) ke set yang ada .

Saya menggunakan ini dalam suatu reducefungsi, dan itu benar-benar konyol. Bahkan jika Anda memiliki ...arrayoperator spread, Anda tidak boleh menggunakannya dalam kasus ini, karena ia memboroskan sumber daya prosesor, memori, dan waktu.

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

Cuplikan Demo

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

Steven Spungin
sumber
1

Ubah set ke dalam array, ratakan mereka dan akhirnya konstruktor akan menyatukan.

const union = (...sets) => new Set(sets.map(s => [...s]).flat());
NicoAdrian
sumber
Tolong jangan memposting kode hanya sebagai jawaban, tetapi juga memberikan penjelasan apa yang kode Anda lakukan dan bagaimana memecahkan masalah pertanyaan. Jawaban dengan penjelasan biasanya berkualitas lebih tinggi, dan lebih cenderung menarik upvotes.
Mark Rotteveel
0

Tidak, tidak ada operasi builtin untuk ini, tetapi Anda dapat dengan mudah membuatnya sendiri:

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;
};
Bergi
sumber
2
Biarkan dia melakukan apa yang dia inginkan.
pishpish
1
Jika semua orang melakukan apa yang mereka inginkan, kita akan berakhir dengan bencana smoosh lagi
dwelle
0

Contoh

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

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

    return dataMap
}

Pemakaian

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

Anda bisa menggunakan sintaks spread untuk menggabungkannya:

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

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

=> {a: 5, b: 1, c: 2}
Nadiar AS
sumber
-1

bergabung ke Map

 let merge = {...map1,...map2};
Danilo Santos
sumber