O Caminho Prático
Eu acho errado dizer que uma implementação específica é "The Right Way ™" se for apenas "certa" ("correta") em contraste com uma solução "errada". A solução de Tomáš é uma clara melhoria na comparação de matrizes baseada em strings, mas isso não significa que seja objetivamente "correto". O que é certo, afinal? É o mais rápido? É o mais flexível? É o mais fácil de entender? É o mais rápido para depurar? Utiliza menos operações? Isso tem algum efeito colateral? Nenhuma solução pode ter o melhor de todas as coisas.
Tomás poderia dizer que sua solução é rápida, mas eu também diria que é desnecessariamente complicado. Ele tenta ser uma solução completa que funciona para todas as matrizes, aninhadas ou não. De fato, ele aceita mais do que apenas matrizes como entrada e ainda tenta dar uma resposta "válida".
Os genéricos oferecem reutilização
Minha resposta abordará o problema de maneira diferente. Começarei com um arrayCompareprocedimento genérico que se preocupa apenas em percorrer as matrizes. A partir daí, criaremos nossas outras funções básicas de comparação, como arrayEquale arrayDeepEqualetc.
// arrayCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayCompare = f => ([x,...xs]) => ([y,...ys]) =>
x === undefined && y === undefined
? true
: Boolean (f (x) (y)) && arrayCompare (f) (xs) (ys)
Na minha opinião, o melhor tipo de código nem precisa de comentários, e isso não é exceção. Há tão pouco acontecendo aqui que você pode entender o comportamento deste procedimento quase sem nenhum esforço. Claro, algumas das sintaxes do ES6 podem parecer estranhas para você agora, mas isso ocorre apenas porque o ES6 é relativamente novo.
Como o tipo sugere, arrayCompareassume a função de comparação,, fe duas matrizes de entrada, xse ys. Na maior parte, tudo o que fazemos é chamar f (x) (y)cada elemento nas matrizes de entrada. Retornamos cedo falsese o fretorno definido pelo usuário false- graças à &&avaliação de curto-circuito. Portanto, sim, isso significa que o comparador pode interromper a iteração antecipadamente e impedir o loop pelo restante da matriz de entrada quando desnecessário.
Comparação estrita
Em seguida, usando nossa arrayComparefunção, podemos criar facilmente outras funções que possamos precisar. Vamos começar com o elementar arrayEqual...
// equal :: a -> a -> Bool
const equal = x => y =>
x === y // notice: triple equal
// arrayEqual :: [a] -> [a] -> Bool
const arrayEqual =
arrayCompare (equal)
const xs = [1,2,3]
const ys = [1,2,3]
console.log (arrayEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2) && (3 === 3) //=> true
const zs = ['1','2','3']
console.log (arrayEqual (xs) (zs)) //=> false
// (1 === '1') //=> false
Simples assim. arrayEqualpode ser definido com arrayComparee uma função comparadora que se compara aao buso=== (para igualdade estrita).
Observe que também definimos equalcomo sua própria função. Isso destaca o papel de arrayCompareuma função de ordem superior para utilizar nosso comparador de primeira ordem no contexto de outro tipo de dados (Matriz).
Comparação fraca
Poderíamos ser facilmente definidos arrayLooseEqualusando um ==. Agora, ao comparar 1(Number) a '1'(String), o resultado será true…
// looseEqual :: a -> a -> Bool
const looseEqual = x => y =>
x == y // notice: double equal
// arrayLooseEqual :: [a] -> [a] -> Bool
const arrayLooseEqual =
arrayCompare (looseEqual)
const xs = [1,2,3]
const ys = ['1','2','3']
console.log (arrayLooseEqual (xs) (ys)) //=> true
// (1 == '1') && (2 == '2') && (3 == '3') //=> true
Comparação profunda (recursiva)
Você provavelmente já reparou que essa é apenas uma comparação superficial. Certamente a solução de Tomáš é "The Right Way ™" porque implica uma comparação profunda implícita, certo?
Bem, nosso arrayCompareprocedimento é versátil o suficiente para ser usado de uma maneira que torna fácil um teste de igualdade profundo…
// isArray :: a -> Bool
const isArray =
Array.isArray
// arrayDeepCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayDeepCompare = f =>
arrayCompare (a => b =>
isArray (a) && isArray (b)
? arrayDeepCompare (f) (a) (b)
: f (a) (b))
const xs = [1,[2,[3]]]
const ys = [1,[2,['3']]]
console.log (arrayDeepCompare (equal) (xs) (ys)) //=> false
// (1 === 1) && (2 === 2) && (3 === '3') //=> false
console.log (arrayDeepCompare (looseEqual) (xs) (ys)) //=> true
// (1 == 1) && (2 == 2) && (3 == '3') //=> true
Simples assim. Construímos um comparador profundo usando outra função de ordem superior. Desta vez, estamos encerrando arrayCompareusando um comparador personalizado que verificará se ae bsão matrizes. Nesse arrayDeepComparecaso , aplique novamente a comparação ae bo comparador especificado pelo usuário ( f). Isso nos permite manter o profundo comportamento de comparação separado de como realmente comparamos os elementos individuais. Ou seja, como mostra o exemplo acima, podemos comparar profundamente usando equal,looseEqual ou qualquer outra comparação que fazemos.
Por arrayDeepCompareser curry, podemos aplicá-lo parcialmente como fizemos nos exemplos anteriores
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
Para mim, isso já é uma clara melhoria em relação à solução de Tomáš, porque eu posso escolher explicitamente uma comparação superficial ou profunda para minhas matrizes, conforme necessário.
Comparação de objetos (exemplo)
Agora, e se você tiver uma variedade de objetos ou algo assim? Talvez você queira considerar essas matrizes como "iguais" se cada objeto tiver o mesmo idvalor ...
// idEqual :: {id: Number} -> {id: Number} -> Bool
const idEqual = x => y =>
x.id !== undefined && x.id === y.id
// arrayIdEqual :: [a] -> [a] -> Bool
const arrayIdEqual =
arrayCompare (idEqual)
const xs = [{id:1}, {id:2}]
const ys = [{id:1}, {id:2}]
console.log (arrayIdEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2) //=> true
const zs = [{id:1}, {id:6}]
console.log (arrayIdEqual (xs) (zs)) //=> false
// (1 === 1) && (2 === 6) //=> false
Simples assim. Aqui eu usei objetos JS vanilla, mas esse tipo de comparador pode funcionar para qualquer tipo de objeto; até seus objetos personalizados. A solução de Tomáš precisaria ser completamente reformulada para suportar esse tipo de teste de igualdade
Matriz profunda com objetos? Não é um problema. Criamos funções genéricas altamente versáteis, para que funcionem em uma ampla variedade de casos de uso.
const xs = [{id:1}, [{id:2}]]
const ys = [{id:1}, [{id:2}]]
console.log (arrayCompare (idEqual) (xs) (ys)) //=> false
console.log (arrayDeepCompare (idEqual) (xs) (ys)) //=> true
Comparação arbitrária (exemplo)
Ou, e se você quisesse fazer algum outro tipo de comparação completamente arbitrária? Talvez eu queira saber se cada um xé maior que cada um y...
// gt :: Number -> Number -> Bool
const gt = x => y =>
x > y
// arrayGt :: [a] -> [a] -> Bool
const arrayGt = arrayCompare (gt)
const xs = [5,10,20]
const ys = [2,4,8]
console.log (arrayGt (xs) (ys)) //=> true
// (5 > 2) && (10 > 4) && (20 > 8) //=> true
const zs = [6,12,24]
console.log (arrayGt (xs) (zs)) //=> false
// (5 > 6) //=> false
Menos é mais
Você pode ver que estamos realmente fazendo mais com menos código. Não há nada de complicado em arrayComparesi e cada um dos comparadores personalizados que criamos tem uma implementação muito simples.
Com facilidade, podemos definir exatamente como nós desejamos para duas matrizes para ser comparado - superficial, profunda, rigorosa, solto, alguma propriedade objeto, ou alguma computação arbitrária, ou qualquer combinação destes - todos usando um único procedimento , arrayCompare. Talvez até sonhe com um RegExpcomparador! Eu sei como as crianças adoram esses regexps ...
É o mais rápido? Não. Mas provavelmente também não precisa ser. Se a velocidade for a única métrica usada para medir a qualidade do nosso código, muitos códigos realmente ótimos serão descartados. É por isso que estou chamando essa abordagem de The Practical Way . Ou talvez para ser mais justo, uma maneira prática. Esta descrição é adequada para esta resposta porque não estou dizendo que esta resposta é apenas prática em comparação com alguma outra resposta; é objetivamente verdadeiro. Atingimos um alto grau de praticidade com muito pouco código e muito fácil de raciocinar. Nenhum outro código pode dizer que não recebemos essa descrição.
Isso faz com que seja a solução "certa" para você? Isso depende de você decidir. E ninguém mais pode fazer isso por você; só você sabe quais são suas necessidades. Em quase todos os casos, valorizo códigos simples, práticos e versáteis, em vez de tipos inteligentes e rápidos. O que você valoriza pode ser diferente, então escolha o que funciona melhor para você.
Editar
Minha resposta antiga estava mais focada em decompor-se arrayEqualem procedimentos minúsculos. É um exercício interessante, mas não é realmente a melhor (mais prática) maneira de abordar esse problema. Se você estiver interessado, poderá ver este histórico de revisões.