Usando lodash para comparar matrizes (existência de itens sem ordem)

124

Sei que posso fazer isso usando loops, mas estou tentando encontrar uma maneira elegante de fazer isso:

Eu tenho duas matrizes:

var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];

Eu quero usar lodashpara confirmar se as duas matrizes acima são iguais. Por 'o mesmo' quero dizer que não há nenhum item array1que não esteja contido em array2.

Em termos de verificação da igualdade entre esses itens:

['a', 'b'] == ['b', 'a'] 

ou

['a', 'b'] == ['a', 'b'] 

Ambos funcionam porque as letras estarão sempre em ordem.

pQuestions123
fonte

Respostas:

224

Se você classificar a matriz externa, poderá usar, _.isEqual()uma vez que a matriz interna já está classificada.

var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];
_.isEqual(array1.sort(), array2.sort()); //true

Observe que isso .sort()causará mutação nas matrizes. Se isso for um problema para você, primeiro faça uma cópia usando (por exemplo) .slice()ou o operador spread ( ...).

Ou faça como Daniel Budick recomenda em um comentário abaixo:

_.isEqual(_.sortBy(array1), _.sortBy(array2))

O de Lodash sortBy()não altera o array.

Trott
fonte
6
Leve em consideração que array.sort () está alterando o array original. Talvez este seja melhor: var array1 = [['a', 'b'], ['b', 'c']]; var array2 = [['b', 'c'], ['a', 'b']]; _.isEqual ([... array1] .sort (), [... array2] .sort ()); // verdadeiro
Yaniv Efraim
3
Adicionadas duas frases observando que isso .sort()muda e sugerindo opções para copiar primeiro, se isso for um problema para o usuário.
Trott
6
Se você já está usando lodash, pode apenas fazer _.isEqual(_.sortBy(array1), _sortBy(array2))para prevenir a mutação.
Daniel Budick
1
@DanielBudick Obrigado! Eu adicionei isso à resposta. Ótima sugestão.
Trott
32

Você pode usar lodashs xorpara isso

doArraysContainSameElements = _.xor(arr1, arr2).length === 0

Se você considerar array [1, 1] diferente de array [1], você pode melhorar o desempenho um pouco assim:

doArraysContainSameElements = arr1.length === arr2.length === 0 && _.xor(arr1, arr2).length === 0
Stephan Hoyer
fonte
1
Os arrays precisam ser classificados de qualquer maneira.
Sovattha Sok
1
Esta deve ser a melhor maneira para a versão mais recente
Leonardo
@Sovattha Sok Os arrays não precisam ser classificados. Fazer _.xor([3,4], [4,3]).length == 0você será verdadeiro.
Nikolay
1
Uma palavra de cautela: esta técnica funciona bem para arrays "pequenos", mas pode ser uma dor de desempenho e no que diz respeito à memória se seus arrays forem enormes e diferentes porque _.xor () continuará avançando muuuito após a primeira diferença. Em outras palavras, ele não retorna rápido na primeira diferença detectada.
XDS de
6

Por 'o mesmo' quero dizer que não há nenhum item na matriz1 que não esteja contido na matriz2.

Você poderia usar flatten () e diferença () para isso, o que funciona bem se você não se importar se há itens array2que não estão em array1. Parece que você está perguntando se array1 é um subconjunto de array2 ?

var array1 = [['a', 'b'], ['b', 'c']];
var array2 = [['b', 'c'], ['a', 'b']];

function isSubset(source, target) {
    return !_.difference(_.flatten(source), _.flatten(target)).length;
}

isSubset(array1, array2); // → true
array1.push('d');
isSubset(array1, array2); // → false
isSubset(array2, array1); // → true
Adam Boduch
fonte
4

Já existem respostas aqui, mas aqui está minha implementação JS pura. Não tenho certeza se é o ideal, mas com certeza é transparente, legível e simples.

// Does array a contain elements of array b?
const contains = (a, b) => new Set([...a, ...b]).size === a.length
const isEqualSet = (a, b) => contains(a, b) && contains(b, a)

O raciocínio contains()é que, se acontiver todos os elementos de b, colocá-los no mesmo conjunto não alteraria o tamanho.

Por exemplo, se const a = [1,2,3,4]e const b = [1,2], então new Set([...a, ...b]) === {1,2,3,4}. Como você pode ver, o conjunto resultante possui os mesmos elementos que a.

A partir daí, para torná-lo mais conciso, podemos resumir o seguinte:

const isEqualSet = (a, b) => {
  const unionSize = new Set([...a, ...b])
  return unionSize === a.length && unionSize === b.length
}
J.Ko
fonte
1

PURE JS (funciona também quando arrays e subarrays têm mais de 2 elementos com ordem arbitrária). Se strings contiver ,use como join('-')caractere de parâmetro (pode ser utf) que não é usado em strings

array1.map(x=>x.sort()).sort().join() === array2.map(x=>x.sort()).sort().join()

Kamil Kiełczewski
fonte
0

Podemos usar a _.differencefunção para ver se há alguma diferença ou não.

function isSame(arrayOne, arrayTwo) {
   var a = _.unique(arrayOne),
       b = _.unique(arrayTwo);

   if (a.length <= b.length) {
      a = arrayTwo;
      b = arrayOne;
      return _.isEmpty(_.difference(a.sort(), b.sort()));
   } else {
      return false;
   }

}

// examples
console.log(isSame([1, 2, 3], [1, 2, 3])); // true
console.log(isSame([1, 2, 4], [1, 2, 3])); // false
console.log(isSame([1, 2], [2, 3, 1])); // false
console.log(isSame([2, 3, 1], [1, 2])); // false

// Test cases pointed by Mariano Desanze, Thanks.
console.log(isSame([1, 2, 3], [1, 2, 2])); // false
console.log(isSame([1, 2, 2], [1, 2, 2])); // true
console.log(isSame([1, 2, 2], [1, 2, 3])); // false

Eu espero que isso te ajude.

Amitesh
fonte
5
Errado, a sua função vai ficar trueparaconsole.log(isSame([1,2], [2,3,1]));
David Lin
3
Obrigado @DavidLin por apontar isso. Eu fiz as alterações para considerar esse caso. Obrigado e desculpe pelo transtorno.
Amitesh de
2
você não precisa trocar de lugar para a & b se os comprimentos não forem iguais. Se os comprimentos forem diferentes, eles não podem ser os mesmos, então se (arrayOne.lenght! == arrayTwo.lenght) retornar falso;
Alex
1
-1 Não adianta ter essas ae bvariáveis. Você única utilizar essas variáveis dentro do if-thenlado, e a primeira coisa que você faz lá é descartar esses valores carregados na linha 2. Eu acho que a seguinte linha única vai funcionar exatamente o mesmo: return arrayOne.length <= arrayTwo.length && _.isEmpty(_.difference(arrayTwo.sort(), arrayTwo.sort());. E <=também pode ser melhorado ===.
Mariano Desanze
1
E _.differenceretornará itens ausentes do primeiro argumento, mas não itens ausentes do segundo. Então, isso vai incorretamente retornar truequando você repetir itens em 1º na 2ª: isSame([1, 2, 3], [1, 2, 2]).
Mariano Desanze
0

Edit: Eu perdi o aspecto multidimensional desta questão, então estou deixando isso aqui no caso de ajudar as pessoas a comparar matrizes unidimensionais

É uma pergunta antiga, mas eu estava tendo problemas com a velocidade de uso de .sort()ou sortBy(), então usei esta:

function arraysContainSameStrings(array1: string[], array2: string[]): boolean {
  return (
    array1.length === array2.length &&
    array1.every((str) => array2.includes(str)) &&
    array2.every((str) => array1.includes(str))
  )
}

Foi planejado para falhar rápido e, para meus propósitos, funciona bem.

charliematters
fonte
A última verificação é realmente necessária? array1.every((str) => array2.includes(str))deve ser suficiente. Além disso, o OP queria usar o lodash, você deveria pelo menos dizer porque propõe uma solução vanillaJS (... agora que temos tudo e inclui ...). Forneça também um exemplo de como aplicar sua função ao problema fornecido (matrizes bidimensionais).
linha de
Esse é um bom ponto - eu não abordei o aspecto multidimensional disso, não a exigência de lodash. Achei que seria útil que qualquer pessoa que procurasse (como eu fiz) para lodash methods to compare arrays without considering orderver uma alternativa em Javascript moderno. A segunda verificação é necessária, pois arraysContainSameStrings(['1', '2', '2'], ['1', '2', '3']), de outra forma, retornaria verdadeiro. Vou deixar aqui, pois pode ajudar, mas agradeço não ter respondido à pergunta
charliematters,