La voie pratique
Je pense qu'il est faux de dire qu'une implémentation particulière est "The Right Way ™" si elle n'est que "correcte" ("correcte") contrairement à une "mauvaise" solution. La solution de Tomáš est une nette amélioration par rapport à la comparaison de tableaux basés sur des chaînes, mais cela ne signifie pas qu'elle est objectivement "correcte". Qu'est-ce qui est juste toute façon? Est-ce le plus rapide? Est-ce le plus flexible? Est-ce le plus simple à comprendre? Est-ce le débogage le plus rapide? Utilise-t-il le moins d'opérations? Est-ce que cela a des effets secondaires? Aucune solution ne peut avoir le meilleur de toutes choses.
Tomáš pourrait dire que sa solution est rapide mais je dirais aussi que c'est inutilement compliqué. Il essaie d'être une solution tout-en-un qui fonctionne pour toutes les baies, imbriquées ou non. En fait, il accepte même plus que de simples tableaux en entrée et tente toujours de donner une réponse "valide".
Les génériques offrent une réutilisabilité
Ma réponse abordera le problème différemment. Je vais commencer par une arrayCompare
procédure générique qui ne concerne que la navigation dans les tableaux. À partir de là, nous allons construire nos autres fonctions de comparaison de base comme arrayEqual
et 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)
À mon avis, le meilleur type de code n'a même pas besoin de commentaires, et cela ne fait pas exception. Il se passe tellement peu de choses ici que vous pouvez comprendre le comportement de cette procédure sans presque aucun effort. Bien sûr, une partie de la syntaxe ES6 peut vous sembler étrangère maintenant, mais c'est uniquement parce que ES6 est relativement nouveau.
Comme le type le suggère, arrayCompare
prend la fonction de comparaison f
, et deux tableaux d'entrée, xs
et ys
. Pour la plupart, tout ce que nous faisons est d'appeler f (x) (y)
pour chaque élément dans les tableaux d'entrée. Nous retournons tôt false
si la valeur définie par l'utilisateur f
revient false
- grâce à &&
l'évaluation des courts-circuits de. Donc oui, cela signifie que le comparateur peut arrêter l'itération plus tôt et empêcher le bouclage à travers le reste du tableau d'entrée lorsqu'il n'est pas nécessaire.
Comparaison stricte
Ensuite, en utilisant notre arrayCompare
fonction, nous pouvons facilement créer d'autres fonctions dont nous pourrions avoir besoin. Commençons par l'élémentaire 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
Aussi simple que cela. arrayEqual
peut être défini avec arrayCompare
et une fonction de comparaison qui se compare a
à l' b
utilisation===
(pour une stricte égalité).
Notez que nous définissons également equal
comme sa propre fonction. Cela met en évidence le rôle d' arrayCompare
une fonction d'ordre supérieur pour utiliser notre comparateur de premier ordre dans le contexte d'un autre type de données (Array).
Comparaison lâche
Nous pourrions tout aussi facilement définir en arrayLooseEqual
utilisant un à la ==
place. Maintenant, en comparant 1
(Number) à '1'
(String), le résultat sera 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
Comparaison approfondie (récursive)
Vous avez probablement remarqué que ce n'est qu'une comparaison superficielle. La solution de Tomáš est sûrement "The Right Way ™" car elle implique une comparaison profonde implicite, n'est-ce pas?
Eh bien, notre arrayCompare
procédure est suffisamment polyvalente pour être utilisée de manière à ce qu'un test d'égalité approfondi soit un jeu d'enfant…
// 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
Aussi simple que cela. Nous construisons un comparateur profond en utilisant une autre fonction d'ordre supérieur. Cette fois, nous arrayCompare
utilisons un comparateur personnalisé qui vérifiera si a
et b
sont des tableaux. Si tel est le cas, réappliquez arrayDeepCompare
sinon comparer a
et b
au comparateur spécifié par l'utilisateur ( f
). Cela nous permet de séparer le comportement de comparaison approfondie de la façon dont nous comparons réellement les éléments individuels. C'est-à-dire, comme le montre l'exemple ci-dessus, nous pouvons comparer en profondeur en utilisant equal
,looseEqual
ou tout autre comparateur que nous faisons.
Parce qu'il arrayDeepCompare
est curry, nous pouvons l'appliquer partiellement comme nous l'avons fait dans les exemples précédents
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
Pour moi, c'est déjà une nette amélioration par rapport à la solution de Tomáš car je peux explicitement choisir une comparaison superficielle ou profonde pour mes tableaux, selon les besoins.
Comparaison d'objets (exemple)
Et si vous avez un tableau d'objets ou quelque chose? Peut-être que vous voulez considérer ces tableaux comme «égaux» si chaque objet a la même id
valeur…
// 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
Aussi simple que cela. Ici, j'ai utilisé des objets JS vanilla, mais ce type de comparateur pourrait fonctionner pour n'importe quel type d'objet; même vos objets personnalisés. La solution de Tomáš devrait être complètement retravaillée pour prendre en charge ce type de test d'égalité
Tableau profond avec des objets? Pas de problème. Nous avons créé des fonctions génériques très polyvalentes, afin qu'elles fonctionnent dans une grande variété de cas d'utilisation.
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
Comparaison arbitraire (exemple)
Ou si vous vouliez faire une autre sorte de comparaison complètement arbitraire? Peut-être que je veux savoir si chacun x
est plus grand que chacun 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
Moins est plus
Vous pouvez voir que nous faisons plus avec moins de code. Il n'y a rien de compliqué en arrayCompare
soi et chacun des comparateurs personnalisés que nous avons créés a une implémentation très simple.
Avec facilité, nous pouvons définir exactement comment nous voulons deux tableaux à comparer - peu profond, profond, stricte, lâche, une propriété d'objet, ou un calcul arbitraire, ou toute combinaison de ceux - ci - tout en utilisant une procédure , arrayCompare
. Peut-être même imaginer un RegExp
comparateur! Je sais à quel point les enfants adorent ces expressions régulières…
Est-ce le plus rapide? Nan. Mais ce n'est probablement pas nécessaire non plus. Si la vitesse est la seule métrique utilisée pour mesurer la qualité de notre code, beaucoup de très bon code serait jeté - C'est pourquoi j'appelle cette approche The Practical Way . Ou peut-être pour être plus juste, A pratique. Cette description convient à cette réponse parce que je ne dis pas que cette réponse n'est pratique que par rapport à une autre réponse; c'est objectivement vrai. Nous avons atteint un degré élevé de fonctionnalité avec très peu de code très facile à raisonner. Aucun autre code ne peut dire que nous n'avons pas mérité cette description.
Est-ce que cela en fait la «bonne» solution pour vous? C'est à vous de décider. Et personne d'autre ne peut le faire pour vous; vous seul savez quels sont vos besoins. Dans presque tous les cas, j'apprécie le code simple, pratique et polyvalent plutôt que le type intelligent et rapide. Ce que vous appréciez peut différer, alors choisissez ce qui vous convient.
Éditer
Mon ancienne réponse était plus axée sur la décomposition arrayEqual
en minuscules procédures. C'est un exercice intéressant, mais ce n'est pas vraiment la meilleure façon (la plus pratique) d'aborder ce problème. Si vous êtes intéressé, vous pouvez voir cet historique de révision.