Самый простой способ объединить карты / наборы ES6?


171

Есть ли простой способ объединить карты ES6 вместе (как Object.assign)? И в то время как мы на этом, как насчет наборов ES6 (как Array.concat)?


4
Этот блог имеет некоторое понимание. 2ality.com/2015/01/es6-set-operations.html
lemieuxster

AFAIK для карты, которую вам нужно использовать, for..ofпотому что это keyможет быть любой тип
Пол С.

Ответы:


266

Для наборов:

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

Для карт:

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

Обратите внимание, что если несколько карт имеют один и тот же ключ, значением объединенной карты будет значение последней карты слияния с этим ключом.


4
Документация Map: «Конструктор: new Map([iterable])», « iterableэто массив или другой итерируемый объект, элементами которого являются пары ключ-значение (двухэлементные массивы). Каждая пара ключ-значение добавляется на новую карту ». - просто как ссылка.
user4642212

34
Для больших наборов просто предупреждаем, что при этом выполняется повторение содержимого обоих наборов дважды, один раз для создания временного массива, содержащего объединение двух наборов, а затем передача этого временного массива в конструктор набора, где он повторяется снова для создания нового набора ,
Jfriend00

2
@ jfriend00: см. ответ jameslk ниже для лучшего метода
Берги

2
@torazaburo: как сказал jfriend00, решение Oriols создает ненужные промежуточные массивы. Передача итератора Mapконструктору позволяет избежать потребления памяти.
Берги

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))если вы хотите легкую поддержку браузера
совещание

9
@caub хорошее решение, но помните, что первым параметром forEach является значение, поэтому ваша функция должна быть m2.forEach ((v, k) => m1.set (k, v));
Дэвид Норена,

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

И, для 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обнаруживать тип его параметра и, если набор добавляет элементы набора, потому что тогда не было бы способа добавить сам набор в набор.

@torazaburo - что касается .add()метода получения набора, я понимаю вашу точку зрения. Я просто нахожу это гораздо менее полезным, чем возможность комбинировать наборы с использованием, .add()поскольку у меня никогда не было необходимости в наборе или наборах, но мне приходилось много раз объединять наборы. Просто вопрос о полезности одного поведения против другого.
jfriend00

Ага, я ненавижу, что это не работает для карт: n.forEach(m.add, m)- это инвертирует пары ключ / значение!
Берги

@ Берги - да, странно, что Map.prototype.forEach()и Map.prototype.set()изменили аргументы. Похоже на недосмотр кого-то. Это заставляет больше кода при попытке использовать их вместе.
jfriend00

@ Jfriend00: ОТО, это своего рода имеет смысл. setПорядок параметров является естественным для пар ключ / значение, forEachвыровнен по методу Arrays forEach(и тому подобное $.eachили _.eachтакже перечисляет объекты).
Берги

21

Редактировать :

Я сравнил свое первоначальное решение с другими предложениями и обнаружил, что оно очень неэффективно.

Сам бенчмарк очень интересный ( ссылка ) Он сравнивает 3 решения (чем выше, тем лучше):

  • Решение @ bfred.it, которое добавляет значения одно за другим (14 955 оп / с)
  • Решение @ jameslk, которое использует генератор самозапуска (5,089 оп / сек)
  • мой собственный, который использует уменьшение и распространение (3434 оп / сек)

Как видите, решение @ 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
Привет @ Берги, ты прав. Спасибо за повышение моей осведомленности (: я проверил свои решения в сравнении с другими, предложенными здесь, и доказал это для себя. Кроме того, я отредактировал свой ответ, чтобы отразить это. Пожалуйста, рассмотрите возможность удаления своего отрицательного голоса.
Асаф Кац

1
Отлично, спасибо за сравнение производительности. Забавно, как «нелегальное» решение является самым быстрым;) Пришло сюда, чтобы искать улучшения по сравнению forofс add, что кажется просто очень неэффективным. Я действительно хочу addAll(iterable)метод на множествах
Бруно Шеппер

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

Чтобы объединить наборы в массиве Sets, вы можете сделать

var Sets = [set1, set2, set3];

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

Мне немного загадочно, почему следующее, что должно быть эквивалентно, терпит неудачу по крайней мере в Вавилоне:

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

Основываясь на ответе Асафа Каца, вот машинописная версия:

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

Пожалуйста, не публикуйте только код в качестве ответа, а также предоставьте объяснение того, что делает ваш код и как он решает проблему вопроса. Ответы с объяснением, как правило, более высокого качества и, скорее всего, привлекут положительные отзывы.
Марк Роттвил

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
Если все будут делать то, что они хотят, мы закончим очередной неудачей в смуте
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']


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.