Cách đơn giản nhất để hợp nhất Bản đồ / Bộ ES6?


170

Có cách nào đơn giản để hợp nhất Bản đồ ES6 lại với nhau (như Object.assign) không? Và trong khi chúng ta đang ở đó, còn Bộ ES6 (như thế nào Array.concat) thì sao?


4
Bài đăng blog này có một số cái nhìn sâu sắc. 2ality.com/2015/01/es6-set-operations.html
lemieuxster 14/8/2015

AFAIK cho Bản đồ bạn sẽ cần sử dụng for..ofkeycó thể là bất kỳ loại nào
Paul S.

Câu trả lời:


265

Đối với bộ:

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

Đối với bản đồ:

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

Lưu ý rằng nếu nhiều bản đồ có cùng một khóa, giá trị của bản đồ được hợp nhất sẽ là giá trị của bản đồ hợp nhất cuối cùng với khóa đó.


4
Tài liệu về Map: Trình xây dựng của người dùng: new Map([iterable])Trực tiếp, iterablelà một mảng hoặc đối tượng lặp khác có các phần tử là các cặp giá trị khóa (Mảng 2 phần tử). Mỗi cặp khóa-giá trị được thêm vào Bản đồ mới. - chỉ là một tài liệu tham khảo.
dùng4642212

34
Đối với các Tập lớn, chỉ cần cảnh báo rằng điều này lặp lại nội dung của cả hai Tập hai lần, một lần để tạo một mảng tạm thời chứa liên kết của hai tập hợp, sau đó chuyển mảng tạm thời đó đến Trình xây dựng tập hợp, nơi nó được lặp lại một lần nữa để tạo Tập mới .
jfriend00

2
@ jfriend00: xem câu trả lời của jameslk bên dưới để biết phương pháp tốt hơn
Bergi

2
@torazaburo: như jfriend00 đã nói, giải pháp của Oriols không tạo ra các mảng trung gian không cần thiết. Truyền một trình vòng lặp cho hàm Maptạo sẽ tránh tiêu thụ bộ nhớ của chúng.
Bergi

2
Tôi cảm thấy thất vọng khi Bộ / Bản đồ ES6 không cung cấp các phương thức hợp nhất hiệu quả.
Andy

47

Đây là giải pháp của tôi bằng cách sử dụng máy phát điện:

Đối với Bản đồ:

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

Đối với Bộ:

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 = Biểu hiện chức năng của trình tạo được gọi ngay lập tức)
Oriol

3
cũng tốt m2.forEach((k,v)=>m1.set(k,v))nếu bạn muốn hỗ trợ trình duyệt dễ dàng
caub

9
@caub giải pháp hay nhưng lưu ý rằng tham số đầu tiên của forEach là giá trị nên hàm của bạn phải là m2.forEach ((v, k) => m1.set (k, v));
David Noreña

44

Vì những lý do tôi không hiểu, bạn không thể trực tiếp thêm nội dung của Bộ này vào bộ khác với thao tác tích hợp. Các hoạt động như union, giao nhau, hợp nhất, v.v ... là các hoạt động tập hợp khá cơ bản, nhưng không được tích hợp sẵn. May mắn thay, bạn có thể tự mình xây dựng những thứ này khá dễ dàng.

Vì vậy, để triển khai thao tác hợp nhất (hợp nhất nội dung của một Tập hợp thành một Bản đồ khác hoặc một Bản đồ khác), bạn có thể thực hiện việc này bằng một .forEach()dòng duy nhất :

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

Và, đối với a Map, bạn có thể làm điều này:

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

Hoặc, theo cú pháp ES6:

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

FYI, nếu bạn muốn một lớp con đơn giản của Setđối tượng tích hợp có chứa một .merge()phương thức, bạn có thể sử dụng điều này:

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

Điều này có nghĩa là trong tập tin setex.js của chính nó mà sau đó bạn có thể require()vào tệp node.js và sử dụng thay cho Tập hợp tích hợp.


3
Tôi không nghĩ new Set(s, t). làm. Các ttham số được bỏ qua. Ngoài ra, rõ ràng không phải là hành vi hợp lý khi addphát hiện loại tham số của nó và nếu một tập hợp thêm các phần tử của tập hợp, bởi vì sau đó sẽ không có cách nào để thêm một tập hợp vào tập hợp.

@torazaburo - đối với .add()phương pháp lấy Set, tôi hiểu quan điểm của bạn. Tôi chỉ thấy rằng việc sử dụng ít hơn nhiều so với việc có thể kết hợp các bộ bằng cách sử dụng .add()vì tôi chưa bao giờ có nhu cầu về Bộ hoặc Bộ, nhưng tôi đã có nhu cầu hợp nhất các bộ nhiều lần. Chỉ là vấn đề quan điểm về sự hữu ích của một hành vi so với hành vi khác.
jfriend00

Argh, tôi ghét rằng điều này không hoạt động đối với bản đồ: n.forEach(m.add, m)- nó đảo ngược các cặp khóa / giá trị!
Bergi

@Bergi - yeah, thật kỳ quặc Map.prototype.forEach()Map.prototype.set()có những lý lẽ đảo ngược. Có vẻ như một sự giám sát của ai đó. Nó buộc nhiều mã hơn khi cố gắng sử dụng chúng cùng nhau.
jfriend00

@ jfriend00: OTOH, nó có ý nghĩa. setthứ tự tham số là tự nhiên cho các cặp khóa / giá trị,forEach được căn chỉnh với phương thức Arrays forEach(và những thứ như $.eachhoặc _.eachcũng liệt kê các đối tượng).
Bergi

20

Biên tập :

Tôi đã điểm chuẩn giải pháp ban đầu của tôi so với các giải pháp khác cho thấy ở đây và thấy rằng nó rất không hiệu quả.

Bản thân điểm chuẩn rất thú vị ( liên kết ) Nó so sánh 3 giải pháp (cao hơn là tốt hơn):

  • Giải pháp của @ bfred.it, bổ sung từng giá trị một (14.955 op / giây)
  • Giải pháp của @ jameslk, sử dụng trình tạo tự gọi (5.089 op / giây)
  • của riêng tôi, sử dụng giảm & lây lan (3,434 op / giây)

Như bạn có thể thấy, giải pháp của @ bfred.it chắc chắn là người chiến thắng.

Hiệu suất + Bất biến

Với ý nghĩ đó, đây là một phiên bản sửa đổi một chút, không làm thay đổi bộ gốc và chấp nhận một số lượng lặp có thể thay đổi để kết hợp làm đối số:

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

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

  return set;
}

Sử dụng:

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}

Câu trả lời gốc

Tôi muốn đề xuất một cách tiếp cận khác, sử dụng reducespread toán tử:

Thực hiện

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

Sử dụng:

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}

Tiền boa:

Chúng ta cũng có thể sử dụng resttoán tử để làm cho giao diện đẹp hơn một chút:

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

Bây giờ, thay vì truyền một mảng các tập hợp, chúng ta có thể truyền một số lượng đối số tùy ý của các tập hợp:

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

Điều này là không hiệu quả khủng khiếp.
Bergi

1
Xin chào @Bergi, bạn đã đúng. . Cám ơn nâng cao nhận thức của tôi (: Tôi đã thử nghiệm các giải pháp của tôi với những người khác gợi ý ở đây và chứng tỏ nó cho bản thân mình Ngoài ra, tôi đã chỉnh sửa câu trả lời của tôi để phản ánh rằng hãy xem xét loại bỏ downvote của bạn..
Asaf Katz

1
Tuyệt vời, cảm ơn đã so sánh hiệu suất. Thật buồn cười là giải pháp "không liên kết" là nhanh nhất;) Đến đây để tìm kiếm một cải tiến hơn forofadd, có vẻ như rất không hiệu quả. Tôi thực sự mong muốn một addAll(iterable)phương pháp trên Bộ
Bruno Schäpper

1
Phiên bản bản in: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
Liên kết jsperf gần đầu câu trả lời của bạn có vẻ bị hỏng. Ngoài ra, bạn tham khảo giải pháp của bfred mà tôi không thấy ở đây.
jfriend00

12

Câu trả lời được phê duyệt là tuyệt vời nhưng điều đó tạo ra một bộ mới mỗi lần.

Nếu bạn muốn đột biếnThay vào đó, một đối tượng hiện có, hãy sử dụng hàm trợ giúp.

Bộ

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

Sử dụng:

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

Bản đồ

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

Sử dụng:

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

Để hợp nhất các bộ trong mảng Bộ, bạn có thể làm

var Sets = [set1, set2, set3];

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

Nó hơi bí ẩn đối với tôi tại sao những điều sau đây, tương đương, lại thất bại ít nhất là ở Babel:

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

Array.fromcó các tham số bổ sung, thứ hai là một chức năng ánh xạ. Array.prototype.mapchuyển ba đối số cho cuộc gọi lại của nó: (value, index, array)vì vậy, nó thực sự gọi Sets.map((set, index, array) => Array.from(set, index, array). Rõ ràng, indexlà một số và không phải là một chức năng ánh xạ nên nó thất bại.
nickf

1

Dựa trên câu trả lời của Asaf Katz, đây là phiên bản bản in:

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

Sẽ không có ý nghĩa gìnew Set(...anArrayOrSet) khi gọi khi thêm nhiều phần tử (từ một mảng hoặc một tập hợp khác) vào một tập hợp hiện có .

Tôi sử dụng điều này trong một reducechức năng, và nó chỉ đơn giản là ngớ ngẩn. Ngay cả khi bạn có ...arraysẵn toán tử trải, bạn không nên sử dụng nó trong trường hợp này, vì nó làm lãng phí tài nguyên bộ xử lý, bộ nhớ và thời gian.

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

Đoạn giới thiệu

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

Chuyển đổi các tập hợp thành các mảng, làm phẳng chúng và cuối cùng hàm tạo sẽ uniqify.

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

Vui lòng không chỉ đăng mã dưới dạng câu trả lời mà còn cung cấp giải thích về mã của bạn làm gì và cách giải quyết vấn đề của câu hỏi. Câu trả lời với một lời giải thích thường có chất lượng cao hơn, và có nhiều khả năng thu hút upvote.
Mark Rotteveel

0

Không, không có thao tác dựng sẵn nào cho các thao tác này, nhưng bạn có thể dễ dàng tạo chúng cho riêng mình:

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
Hãy để anh ấy làm những gì anh ấy muốn.
pishpish

1
Nếu mọi người làm những gì họ muốn, chúng tôi sẽ kết thúc với một cuộc tranh cãi smoosh khác
dwelle 17/2/19

0

Thí dụ

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

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

    return dataMap
}

Sử dụng

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

0

Bạn có thể sử dụng cú pháp lây lan để hợp nhất chúng lại với nhau:

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

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

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.