ES6 Haritalarını / Setlerini birleştirmenin en kolay yolu?


Yanıtlar:


265

Setler için:

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

Haritalar için:

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

Birden fazla harita aynı anahtara sahipse, birleştirilmiş haritanın değerinin o anahtarla son birleştirme haritasının değeri olacağını unutmayın.


4
Map“Yapıcı: new Map([iterable])”, “ üzerindeki belgeler iterable, öğeleri anahtar / değer çiftleri (2 öğeli Diziler) olan bir Array veya diğer yinelenebilir bir nesnedir. Her bir anahtar / değer çifti yeni Haritaya eklenir. ” - sadece referans olarak.
user4642212 14:15

34
Büyük Kümeler için, iki kümenin birleşimini içeren geçici bir dizi oluşturmak için bir kez her iki Kümenin içeriğini iki kez yinelediği konusunda uyarılır, ardından bu geçici diziyi yeni Küme oluşturmak için tekrar tekrar yineleneceği Set yapıcısına geçirir. .
jfriend00

2
@ jfriend00: daha iyi bir yöntem için jameslk'in cevabına bakınız
Bergi

2
@ torazaburo: jfriend00'ün dediği gibi, Oriols çözümü gereksiz ara diziler yaratıyor. Yapıcıya bir yineleyici iletilmesi Mapbellek tüketimini önler.
Bergi

2
ES6 Set / Map uygulamasının verimli birleştirme yöntemleri sağlamadığı söyleniyor.
Andy

47

İşte jeneratörler kullanarak benim çözüm:

Haritalar için:

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

Setler için:

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 = Hemen Çalıştırılmış Jeneratör İşlev İfadesi)
Oriol

3
m2.forEach((k,v)=>m1.set(k,v))kolay tarayıcı desteği istiyorsanız da güzel
caub

9
@ caub güzel bir çözüm ancak forEach'ın ilk parametresinin değer olduğunu unutmayın, bu nedenle işleviniz m2.forEach ((v, k) => m1.set (k, v)) olmalıdır;
David Noreña

44

Anlamadığım nedenlerden dolayı, yerleşik bir işlemle bir Setin içeriğini doğrudan diğerine ekleyemezsiniz. Birlik, kesişme, birleştirme, vb. Gibi işlemler oldukça basit küme işlemleridir, ancak yerleşik değildir. Neyse ki, bunları kolayca kendiniz inşa edebilirsiniz.

Bu nedenle, bir birleştirme işlemi uygulamak için (bir Setin içeriğini başka bir haritaya veya bir Haritaya diğerine birleştirmek), bunu tek bir .forEach()satırla yapabilirsiniz:

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

Ve, bir için Map, bunu yapabilirsin:

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

Veya ES6 sözdiziminde:

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

FYI, yöntem Setiçeren yerleşik nesnenin basit bir alt sınıfını istiyorsanız .merge(), bunu kullanabilirsiniz:

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

Bu, daha require()sonra node.js içine girebileceğiniz ve yerleşik Set yerine kullanabileceğiniz kendi setex.js dosyasında olması amaçlanmıştır .


3
Düşünmüyorum new Set(s, t). İşler. tParametre yok sayılır. Ayrıca, addparametresinin türünü algılamak mantıklı bir davranış değildir ve bir küme kümenin öğelerini eklerse, çünkü o zaman kümeye bir kümenin kendisini eklemenin bir yolu olmazdı.

@torazaburo - .add()bir Set alma yöntemine gelince , anlıyorum. Sadece .add()bir Set veya Set'e hiç ihtiyaç duymadığım için setleri birleştirmekten çok daha az kullanıyorum , ancak setleri birçok kez birleştirmeye ihtiyacım vardı. Sadece bir davranışın diğerine karşı yararlılığı hakkında bir görüş meselesi.
jfriend00

Argh, bunun haritalarda çalışmadığından nefret ediyorum: n.forEach(m.add, m)- anahtar / değer çiftlerini tersine çeviriyor!
Bergi

@Bergi - evet, bu garip Map.prototype.forEach()ve Map.prototype.set()tartışmaları tersine çevirdi. Birinin gözetimi gibi görünüyor. Bunları birlikte kullanmaya çalışırken daha fazla kod zorlar.
jfriend00

@ jfriend00: OTOH, biraz mantıklı. setparametre sırası anahtar / değer çiftleri için doğaldır, s yöntemiyle (ve nesneler gibi veya numaralandırılan şeyler) forEachhizalanır . ArrayforEach$.each_.each
Bergi

20

Düzenle :

Orijinal çözümümü burada önerdiğim diğer çözümlerle karşılaştırdım ve çok verimsiz olduğunu gördüm.

Karşılaştırmanın kendisi çok ilginç ( bağlantı ) 3 çözümü karşılaştırıyor (daha yüksek daha iyidir):

  • @ bfred.it'in tek tek değer katan çözümü (14.955 op / sn)
  • @ jameslk'ın kendi kendini çağıran bir jeneratör kullanan çözümü (5.089 op / sn)
  • azalt ve yaymayı kullanan kendim (3.434 op / sn)

Gördüğünüz gibi @ bfred.it'in çözümü kesinlikle kazanır.

Performans + Değişmezlik

Bunu göz önünde bulundurarak, orijinal seti mutasyona uğratmayan ve argüman olarak birleştirmek için değişken sayıda yinelenebilir hariç tutan biraz değiştirilmiş bir sürüm:

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

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

  return set;
}

Kullanımı:

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}

Orijinal Yanıt

Kullanmak reduceve spreadoperatör başka bir yaklaşım önermek istiyorum :

uygulama

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

Kullanımı:

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}

İpucu:

restArayüzü biraz daha hoş hale getirmek için operatörü de kullanabiliriz :

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

Şimdi, bir dizi diziyi geçmek yerine, rastgele sayıda argüman geçirebiliriz :

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

Bu çok verimsiz.
Bergi

1
Selam @Bergi, haklısın. Farkındalığımı arttırdığın için teşekkürler (: Çözümlerimi burada önerilen başkalarına karşı test ettim ve kendim için kanıtladım. Ayrıca, cevabımı bunu yansıtacak şekilde düzenledim. Lütfen downvote'unuzu kaldırmayı düşünün.
Asaf Katz

1
Harika, performans karşılaştırması için teşekkürler. Komik "unelegant" çözüm en hızlı nasıl;) üzerinde bir gelişme aramak için buraya geldi forofve addbu sadece çok verimsiz görünüyor. Gerçekten addAll(iterable)Setler üzerinde bir yöntem diliyorum
Bruno Schäpper

1
Patlama sürümü: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
Cevabınızın üst kısmındaki jsperf bağlantısı kopmuş gibi görünüyor. Ayrıca, burada hiçbir yerde görmediğim bfred çözümüne atıfta bulunuyorsunuz.
jfriend00

12

Onaylanan cevap harika ama her seferinde yeni bir set oluşturuyor.

Bunun yerine varolan bir nesneyi değiştirmek istiyorsanız yardımcı işlevi kullanın.

Ayarlamak

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

Kullanımı:

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

Harita

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

Kullanımı:

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

Kümeler dizisindeki kümeleri birleştirmek için şunları yapabilirsiniz:

var Sets = [set1, set2, set3];

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

Eşdeğer olması gereken aşağıdakilerin en azından Babil'de neden başarısız olduğu benim için biraz gizemli:

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

Array.fromek parametreler alır, ikincisi bir eşleme işlevidir. Array.prototype.mapgeri (value, index, array)çağrısına üç argüman iletir: etkin bir şekilde çağırır Sets.map((set, index, array) => Array.from(set, index, array). Açıkçası, indexbir sayıdır ve bir eşleme işlevi değildir, bu yüzden başarısız olur.
nickf

1

Asaf Katz'ın cevabından yola çıkarak, bir daktilo versiyonu:

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

Bu herhangi bir anlam ifade etmez çağırmak için new Set(...anArrayOrSet)(bir dizi veya başka bir set birinden) birden çok öğe eklerken varolan kümesine .

Bunu bir reduceişlevde kullanıyorum ve sadece aptalca. ...arrayYayılma operatörünüz olsa bile , bu durumda işlemciyi, belleği ve zaman kaynaklarını harcadığı için kullanmamalısınız.

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

Demo Parçacığı

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

Kümeleri dizilere dönüştürün, düzleştirin ve son olarak yapıcı tek kelimeyle değişecektir.

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

Lütfen yalnızca kodu yanıt olarak yayınlamayın, aynı zamanda kodunuzun ne yaptığını ve sorunun sorununu nasıl çözdüğünü de açıklayın. Açıklamalı cevaplar genellikle daha yüksek kalitededir ve yukarı oy alma olasılığı daha yüksektir.
Mark Rotteveel

0

Hayır, bunlar için yerleşik işlemler yoktur, ancak bunları kolayca kendiniz oluşturabilirsiniz:

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
Bırak istediği şeyi yapsın.
pishpish

1
Herkes istediklerini yaparsa, başka bir smoosh felaketi ile sonuçlanır
dwelle

0

Misal

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

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

    return dataMap
}

kullanım

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

0

Bunları birleştirmek için forma sözdizimini kullanabilirsiniz:

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

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

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.