Maneira mais simples de mesclar mapas / conjuntos ES6?

170

Existe uma maneira simples de mesclar o ES6 Maps (como Object.assign)? E enquanto estamos nisso, o que dizer dos Conjuntos ES6 (como Array.concat)?

jameslk
fonte
4
Esta postagem do blog tem algumas dicas. 2ality.com/2015/01/es6-set-operations.html
lemieuxster
AFAIK para o mapa que você precisará usar, for..ofpois keypode ser de qualquer tipo #
Paul S.

Respostas:

265

Para conjuntos:

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

Para mapas:

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

Observe que se vários mapas tiverem a mesma chave, o valor do mapa mesclado será o valor do último mapa mesclado com essa chave.

Oriol
fonte
4
A documentação sobre Map: “Construtor: new Map([iterable])”, “ iterableé uma matriz ou outro objeto iterável cujos elementos são pares de valores-chave (matrizes de 2 elementos). Cada par de valores-chave é adicionado ao novo mapa. ” - apenas como referência.
user4642212
34
Para conjuntos grandes, lembre-se de que isso itera o conteúdo dos dois conjuntos duas vezes, uma vez para criar uma matriz temporária contendo a união dos dois conjuntos e passa essa matriz temporária para o construtor Set, onde é iterado novamente para criar o novo conjunto .
jfriend00
2
@ jfriend00: ver a resposta das jameslk abaixo para um método melhor
Bergi
2
@torazaburo: como jfriend00 disse, a solução Oriols cria matrizes intermediárias desnecessárias. Passar um iterador para o Mapconstrutor evita o consumo de memória.
Bergi 14/08/2015
2
Estou chateado por o ES6 Set / Map não fornecer métodos de mesclagem eficientes.
Andy
47

Aqui está minha solução usando geradores:

Para mapas:

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

Para conjuntos:

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
fonte
35
(IIGFE = Expressão da Função de Gerador Imediatamente Invocada)
Oriol
3
bom também m2.forEach((k,v)=>m1.set(k,v))se você quiser suporte ao navegador fácil
caub
9
@caub solução agradável, mas lembre-se de que o primeiro parâmetro de forEach é value, portanto sua função deve ser m2.forEach ((v, k) => m1.set (k, v));
David Noreña
44

Por razões que não entendo, você não pode adicionar diretamente o conteúdo de um conjunto a outro com uma operação interna. Operações como união, interseção, mesclagem, etc ... são operações de conjunto bastante básicas, mas não são integradas. Felizmente, você pode construir tudo isso com bastante facilidade.

Portanto, para implementar uma operação de mesclagem (mesclando o conteúdo de um conjunto em outro ou de um mapa em outro), você pode fazer isso com uma única .forEach()linha:

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

E, para um Map, você pode fazer o seguinte:

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

Ou, na sintaxe do ES6:

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

Para sua informação, se você deseja uma subclasse simples do Setobjeto interno que contém um .merge()método, pode usar o seguinte:

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

Isso deve estar em seu próprio arquivo setex.js, que você poderá require()usar no node.js e usar no lugar do conjunto interno.

jfriend00
fonte
3
Eu não acho new Set(s, t). trabalho. O tparâmetro é ignorado. Além disso, obviamente não é um comportamento razoável adddetectar o tipo de seu parâmetro e se um conjunto adicionar os elementos do conjunto, porque não haveria como adicionar um conjunto a um conjunto.
@torazaburo - quanto ao .add()método de fazer um set, entendo seu ponto de vista. Eu acho isso muito menos útil do que ser capaz de combinar conjuntos usando .add()como nunca precisei de um conjunto ou conjuntos, mas tive a necessidade de mesclar conjuntos muitas vezes. Apenas uma questão de opinião sobre a utilidade de um comportamento versus o outro.
jfriend00
Argh, eu odeio que isso não funcione para mapas: n.forEach(m.add, m)- inverte pares chave / valor!
Bergi 14/08/2015
@ Bergi - sim, é estranho que Map.prototype.forEach()e Map.prototype.set()tenham invertido argumentos. Parece uma supervisão de alguém. Isso força mais código ao tentar usá-los juntos.
precisa saber é o seguinte
@ jfriend00: OTOH, isso meio que faz sentido. seta ordem dos parâmetros é natural para pares de chave / valor, forEachestá alinhada com Arrayo forEachmétodo s (e coisas como $.eachou _.eachque também enumeram objetos).
Bergi
20

Editar :

Comparei minha solução original com outras soluções sugeridas aqui e descobri que é muito ineficiente.

O benchmark em si é muito interessante ( link ) Compara 3 soluções (quanto maior, melhor):

  • solução do @ bfred.it, que adiciona valores um por um (14.955 op / s)
  • solução de @ jameslk, que usa um gerador de auto-invocação (5.089 op / s)
  • o meu, que usa reduzir e espalhar (3.434 op / s)

Como você pode ver, a solução da @ bfred.it é definitivamente a vencedora.

Desempenho + Imutabilidade

Com isso em mente, aqui está uma versão ligeiramente modificada que não altera o conjunto original e excede um número variável de iteráveis ​​para combinar como argumentos:

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

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

  return set;
}

Uso:

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}

Resposta original

Gostaria de sugerir outra abordagem, usando reducee o spreadoperador:

Implementação

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

Uso:

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}

Dica:

Também podemos usar o restoperador para tornar a interface um pouco melhor:

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

Agora, em vez de passar uma matriz de conjuntos, podemos passar um número arbitrário de argumentos de conjuntos:

union(a, b, c) // {1, 2, 3, 4, 5, 6}
Asaf Katz
fonte
Isso é terrivelmente ineficiente.
Bergi 11/05/19
1
Oi @Bergi, você está certo. . Obrigado por levantar a minha consciência (: Eu testei minhas soluções contra outros sugeriram aqui e provou isso para mim também, eu editei a minha resposta para refletir isso Por favor, considere remover o downvote..
Asaf Katz
1
Ótimo, obrigado pela comparação de desempenho. Engraçado como a solução "deselegante" é a mais rápida;) Veio aqui para procurar uma melhoria forofe add, o que parece ser muito ineficiente. Eu realmente gostaria de um addAll(iterable)método em Conjuntos
de Bruno Schapper
1
Versão datilografada: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
O link jsperf próximo ao topo da sua resposta parece quebrado. Além disso, você se refere à solução do bfred que não vejo em nenhum lugar aqui.
jfriend00
12

A resposta aprovada é ótima, mas isso cria um novo conjunto sempre.

Se você deseja modificar um objeto existente, use uma função auxiliar.

Conjunto

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

Uso:

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

Mapa

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

Uso:

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
fonte
5

Para mesclar os conjuntos na matriz Conjuntos, você pode fazer

var Sets = [set1, set2, set3];

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

É um pouco misterioso para mim por que o seguinte, que deveria ser equivalente, falha pelo menos em Babel:

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

fonte
Array.fromusa parâmetros adicionais, o segundo sendo uma função de mapeamento. Array.prototype.mappassa três argumentos para seu retorno de chamada:, (value, index, array)portanto está efetivamente chamando Sets.map((set, index, array) => Array.from(set, index, array). Obviamente, indexé um número e não uma função de mapeamento, portanto falha.
nickf
1

Baseado na resposta de Asaf Katz, aqui está uma versão datilografada:

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
fonte
1

Não faz sentido chamar new Set(...anArrayOrSet)ao adicionar vários elementos (de uma matriz ou de outro conjunto) a um conjunto existente .

Eu uso isso em uma reducefunção, e é simplesmente bobo. Mesmo se você tiver o ...arrayoperador spread disponível, não deve usá-lo nesse caso, pois desperdiça recursos de processador, memória e tempo.

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

Snippet de demonstração

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

Transforme os conjuntos em matrizes, alise-os e, finalmente, o construtor se unificará.

const union = (...sets) => new Set(sets.map(s => [...s]).flat());
NicoAdrian
fonte
Não publique apenas o código como resposta, mas também forneça uma explicação sobre o que o seu código faz e como ele resolve o problema da pergunta. As respostas com uma explicação geralmente são de qualidade superior e têm maior probabilidade de atrair votos positivos.
Mark Rotteveel
0

Não, não há operações internas para elas, mas você pode criá-las facilmente:

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
fonte
2
Deixe ele fazer o que ele quer.
Pishpish
1
Se todo mundo faz o que quer que vai acabar com outro desastre smoosh
dwelle
0

Exemplo

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

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

    return dataMap
}

Uso

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

Você pode usar a sintaxe de propagação para mesclá-los:

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
fonte
-1

mesclar para mapear

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