วิธีที่ง่ายที่สุดในการผสาน ES6 Maps / Sets?


170

มีวิธีง่ายๆในการรวมแผนที่ ES6 เข้าด้วยกัน (เช่นObject.assign) หรือไม่? และในขณะที่เราอยู่ที่นี่แล้ว ES6 เซต (เหมือนArray.concat)


4
โพสต์บล็อกนี้มีข้อมูลเชิงลึกบางอย่าง 2ality.com/2015/01/es6-set-operations.html
lemieuxster

AFAIK สำหรับแผนที่ที่คุณต้องใช้for..ofเพราะkeyสามารถเป็นประเภทใดก็ได้
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

34
สำหรับชุดใหญ่ให้เตือนว่าสิ่งนี้จะทำซ้ำเนื้อหาของทั้งสองชุดสองครั้งหนึ่งครั้งเพื่อสร้างอาร์เรย์ชั่วคราวที่มีการรวมกันของทั้งสองชุดจากนั้นส่งผ่านอาร์เรย์ชั่วคราวนั้นไปยังชุดคอนสตรัคเตอร์ที่มีการทำซ้ำอีกครั้งเพื่อสร้างชุดใหม่ .
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 = นิพจน์ฟังก์ชันตัวสร้างฟังก์ชันที่ถูกเรียกใช้ทันที)
โอริออล

3
ยังดีm2.forEach((k,v)=>m1.set(k,v))ถ้าคุณต้องการการสนับสนุนเบราว์เซอร์ง่าย
caub

9
@caub ทางออกที่ดี แต่โปรดจำไว้ว่าพารามิเตอร์แรกของ forEach มีค่าดังนั้นหน้าที่ของคุณควรเป็น 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

และสำหรับ a 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));

FYI ถ้าคุณต้องการ subclass แบบง่าย ๆ ของ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ลำดับพารามิเตอร์เป็นธรรมชาติสำหรับคู่คีย์ / ค่าforEachสอดคล้องกับวิธีArrays forEach(และสิ่งที่ชอบ$.eachหรือ_.eachที่ยังระบุวัตถุ)
Bergi

20

แก้ไข :

ฉันเปรียบเทียบโซลูชันดั้งเดิมกับโซลูชันอื่น ๆ ที่แนะนำที่นี่และพบว่าไม่มีประสิทธิภาพมาก

มาตรฐานตัวเองนั้นน่าสนใจมาก ( ลิงค์ ) เปรียบเทียบ 3 โซลูชั่น (สูงกว่าดีกว่า):

  • โซลูชันของ @ bfred.it ซึ่งจะเพิ่มค่าหนึ่งต่อหนึ่ง (14,955 op / วินาที)
  • โซลูชันของ @ jameslk ซึ่งใช้ตัวสร้างการเรียกตนเอง (5,089 op / วินาที)
  • ของฉันเองซึ่งใช้การลด & การแพร่กระจาย (3,434 op / วินาที)

อย่างที่คุณเห็นโซลูชั่นของ @ 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}

คำตอบเดิม

ฉันอยากจะแนะนำวิธีการอื่นโดยใช้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 Katz

1
เยี่ยมมากขอบคุณสำหรับการเปรียบเทียบประสิทธิภาพ ขำขันว่าวิธีแก้ปัญหา "ไม่ได้รับอนุญาต" นั้นเร็วที่สุด;) มาที่นี่เพื่อค้นหาวิธีการปรับปรุงforofและaddดูเหมือนไม่มีประสิทธิภาพมาก ฉันต้องการaddAll(iterable)วิธีการในชุด
บรูโน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 }
พิมพ์ดีด

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เป็นตัวเลขและไม่ใช่ฟังก์ชั่นการทำแผนที่จึงล้มเหลว
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());

โปรดอย่าโพสต์รหัสเท่านั้นเป็นคำตอบ แต่ให้คำอธิบายว่าโค้ดของคุณทำอะไรและจะแก้ไขปัญหาของคำถามได้อย่างไร คำตอบที่มีคำอธิบายมักมีคุณภาพสูงกว่าและมีแนวโน้มที่จะดึงดูดผู้โหวตมากขึ้น
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']


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.