La manera práctica
Creo que es incorrecto decir que una implementación particular es "The Right Way ™" si solo es "correcta" ("correcta") en contraste con una solución "incorrecta". La solución de Tomáš es una mejora clara sobre la comparación de matriz basada en cadenas, pero eso no significa que sea objetivamente "correcta". ¿Qué es lo correcto de todos modos? ¿Es el más rápido? ¿Es el más flexible? ¿Es lo más fácil de comprender? ¿Es el más rápido de depurar? ¿Utiliza las operaciones menos? ¿Tiene efectos secundarios? Ninguna solución puede tener la mejor de todas las cosas.
Tomáš podría decir que su solución es rápida, pero también diría que es innecesariamente complicada. Intenta ser una solución todo en uno que funcione para todos los arreglos, anidados o no. De hecho, incluso acepta algo más que matrices como entrada y aún intenta dar una respuesta "válida".
Los genéricos ofrecen reutilización
Mi respuesta abordará el problema de manera diferente. Comenzaré con un arrayCompare
procedimiento genérico que solo se refiere a recorrer los arreglos. A partir de ahí, crearemos nuestras otras funciones básicas de comparación como arrayEqual
y arrayDeepEqual
, etc.
// 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)
En mi opinión, el mejor tipo de código ni siquiera necesita comentarios, y esto no es una excepción. Aquí sucede tan poco que puede comprender el comportamiento de este procedimiento sin casi ningún esfuerzo. Claro, parte de la sintaxis de ES6 puede parecerle extraña ahora, pero eso es solo porque ES6 es relativamente nuevo.
Como sugiere el tipo, arrayCompare
toma la función de comparación f
, y dos matrices de entrada, xs
y ys
. En su mayor parte, todo lo que hacemos es llamar f (x) (y)
a cada elemento en las matrices de entrada. Regresamos temprano false
si el definido por el usuario f
regresa false
, gracias a &&
la evaluación de cortocircuito. Entonces sí, esto significa que el comparador puede detener la iteración temprano y evitar que se repita el resto del conjunto de entrada cuando sea innecesario.
Comparación estricta
Luego, usando nuestra arrayCompare
función, podemos crear fácilmente otras funciones que podamos necesitar. Comenzaremos con la primaria 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
Simple como eso. arrayEqual
se puede definir con arrayCompare
y una función de comparación que se compara a
con el b
uso ===
(para igualdad estricta).
Tenga en cuenta que también definimos equal
como su propia función. Esto resalta el papel de arrayCompare
una función de orden superior para utilizar nuestro comparador de primer orden en el contexto de otro tipo de datos (matriz).
Comparación suelta
Podríamos definirlo fácilmente arrayLooseEqual
usando un ==
en su lugar. Ahora, al comparar 1
(Número) con '1'
(Cadena), el 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
Comparación profunda (recursiva)
Probablemente hayas notado que esto es solo una comparación superficial. Seguramente la solución de Tomáš es "The Right Way ™" porque hace una comparación profunda implícita, ¿verdad?
Bueno, nuestro arrayCompare
procedimiento es lo suficientemente versátil como para usarlo de una manera que hace que una prueba de igualdad profunda sea muy fácil ...
// 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
Simple como eso. Construimos un comparador profundo usando otra función de orden superior. Esta vez estamos envolviendo arrayCompare
el uso de un comparador personalizado que se compruebe si a
y b
son matrices. Si es así, vuelva a aplicar la arrayDeepCompare
comparación a
y b
al comparador especificado por el usuario ( f
). Esto nos permite mantener el comportamiento de comparación profunda separado de cómo realmente comparamos los elementos individuales. Es decir, como muestra el ejemplo anterior, podemos comparar profunda usando equal
, looseEqual
o cualquier otro comparador que hacer.
Debido a que arrayDeepCompare
es curry, también podemos aplicarlo parcialmente como lo hicimos en los ejemplos anteriores
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
Para mí, esto ya es una mejora clara sobre la solución de Tomáš porque puedo elegir explícitamente una comparación superficial o profunda para mis matrices, según sea necesario.
Comparación de objetos (ejemplo)
¿Y qué pasa si tienes una variedad de objetos o algo? Tal vez desee considerar esas matrices como "iguales" si cada objeto tiene el mismo id
valor ...
// 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
Simple como eso. Aquí he usado objetos vanilla JS, pero este tipo de comparador podría funcionar para cualquier tipo de objeto; incluso tus objetos personalizados. La solución de Tomáš necesitaría ser completamente revisada para soportar este tipo de prueba de igualdad
¿Matriz profunda con objetos? No es un problema. Creamos funciones genéricas y muy versátiles para que funcionen en una amplia variedad 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
Comparación arbitraria (ejemplo)
¿O qué pasaría si quisieras hacer algún otro tipo de comparación completamente arbitraria? Tal vez quiero saber si cada uno x
es mayor que cada uno 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 es más
Puedes ver que en realidad estamos haciendo más con menos código. No hay nada complicado en arrayCompare
sí mismo y cada uno de los comparadores personalizados que hemos creado tiene una implementación muy simple.
Con facilidad, podemos definir exactamente cómo deseamos para dos matrices que deben compararse - superficial, profundo, estricta, suelto, algunos propiedad de objeto, o algún cálculo arbitrario, o cualquier combinación de éstos - todo ello utilizando un procedimiento , arrayCompare
. ¡Quizás hasta sueñe con un RegExp
comparador! Sé cómo los niños aman esas expresiones regulares ...
¿Es el más rápido? No Pero probablemente tampoco sea necesario. Si la velocidad es la única métrica utilizada para medir la calidad de nuestro código, se desechará una gran cantidad de código realmente excelente. Es por eso que llamo a este enfoque The Practical Way . O tal vez para ser más justos, una forma práctica. Esta descripción es adecuada para esta respuesta porque no digo que esta respuesta solo sea práctica en comparación con alguna otra respuesta; Es objetivamente cierto. Hemos alcanzado un alto grado de practicidad con muy poco código sobre el cual es muy fácil razonar. Ningún otro código puede decir que no nos hemos ganado esta descripción.
¿Eso lo convierte en la solución "adecuada" para usted? Eso depende de ti para decidir. Y nadie más puede hacer eso por ti; solo tú sabes cuáles son tus necesidades. En casi todos los casos, valoro el código directo, práctico y versátil en vez de inteligente y rápido. Lo que valoras puede diferir, así que elige lo que funcione para ti.
Editar
Mi vieja respuesta estaba más centrada en descomponerse arrayEqual
en pequeños procedimientos. Es un ejercicio interesante, pero en realidad no es la mejor (más práctica) forma de abordar este problema. Si está interesado, puede ver este historial de revisiones.