Der praktische Weg
Ich denke, es ist falsch zu sagen, dass eine bestimmte Implementierung "The Right Way ™" ist, wenn sie im Gegensatz zu einer "falschen" Lösung nur "richtig" ("richtig") ist. Die Lösung von Tomáš ist eine deutliche Verbesserung gegenüber dem String-basierten Array-Vergleich, aber das bedeutet nicht, dass sie objektiv "richtig" ist. Was ist überhaupt richtig ? Ist es das schnellste? Ist es das flexibelste? Ist es am einfachsten zu verstehen? Ist es das schnellste Debugging? Verwendet es die wenigsten Operationen? Hat es irgendwelche Nebenwirkungen? Keine Lösung kann das Beste von allem haben.
Tomáš könnte sagen, dass seine Lösung schnell ist, aber ich würde auch sagen, dass sie unnötig kompliziert ist. Es wird versucht, eine All-in-One-Lösung zu sein, die für alle Arrays funktioniert, ob verschachtelt oder nicht. Tatsächlich akzeptiert es sogar mehr als nur Arrays als Eingabe und versucht immer noch, eine "gültige" Antwort zu geben.
Generika bieten Wiederverwendbarkeit
Meine Antwort wird das Problem anders angehen. Ich beginne mit einer generischen arrayCompare
Prozedur, die sich nur mit dem Durchlaufen der Arrays befasst. Von dort aus bauen wir unsere anderen grundlegenden Vergleichsfunktionen wie arrayEqual
und arrayDeepEqual
usw.
// 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)
Meiner Meinung nach benötigt die beste Art von Code nicht einmal Kommentare, und dies ist keine Ausnahme. Hier passiert so wenig, dass Sie das Verhalten dieses Verfahrens fast mühelos verstehen können. Sicher, einige der ES6-Syntax scheinen Ihnen jetzt fremd zu sein, aber das liegt nur daran, dass ES6 relativ neu ist.
Wie der Typ andeutet, arrayCompare
übernimmt die Vergleichsfunktion f
und zwei Eingabearrays xs
und ys
. Zum größten Teil rufen wir nur f (x) (y)
jedes Element in den Eingabearrays auf. Wir geben frühzeitig zurück, false
wenn das benutzerdefinierte Ergebnis f
zurückkehrt false
- dank der &&
Kurzschlussbewertung. Ja, dies bedeutet, dass der Komparator die Iteration vorzeitig stoppen und verhindern kann, dass der Rest des Eingabearrays durchlaufen wird, wenn dies nicht erforderlich ist.
Strenger Vergleich
Als nächstes arrayCompare
können wir mit unserer Funktion leicht andere Funktionen erstellen, die wir möglicherweise benötigen. Wir werden mit der Grundschule beginnen 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
So einfach ist das. arrayEqual
kann mit arrayCompare
und einer Komparatorfunktion definiert werden, die a
mit der b
Verwendung verglichen wird ===
(für strikte Gleichheit).
Beachten Sie, dass wir auch equal
als eigene Funktion definieren. Dies unterstreicht die Rolle arrayCompare
einer Funktion höherer Ordnung bei der Verwendung unseres Komparators erster Ordnung im Kontext eines anderen Datentyps (Arrays).
Loser Vergleich
Wir könnten genauso einfach arrayLooseEqual
mit einem definieren ==
. Wenn Sie nun 1
(Number) mit '1'
(String) vergleichen, ist das Ergebnis 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
Tiefer Vergleich (rekursiv)
Sie haben wahrscheinlich bemerkt, dass dies nur ein flacher Vergleich ist. Sicherlich ist Tomášs Lösung "The Right Way ™", weil sie einen tiefen Vergleich impliziert, oder?
Nun, unser arrayCompare
Verfahren ist vielseitig genug, um es so anzuwenden, dass ein tiefer Gleichstellungstest zum Kinderspiel wird…
// 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
So einfach ist das. Wir bauen einen tiefen Komparator mit einer anderen Funktion höherer Ordnung. Dieses Mal werden wir arrayCompare
mit einem benutzerdefinierten Komparator verpackt , der prüft, ob a
und b
sind Arrays. Wenn ja, wenden Sie arrayDeepCompare
anderweitig den Vergleich a
und b
den benutzerdefinierten Komparator ( f
) an. Dies ermöglicht es uns, das tiefe Vergleichsverhalten von dem tatsächlichen Vergleich der einzelnen Elemente zu trennen. Das heißt, wie das Beispiel zeigt, können wir tief vergleichen mit equal
, looseEqual
oder jede andere Komparator wir machen.
Da arrayDeepCompare
es sich um Curry handelt, können wir es teilweise anwenden, wie wir es auch in den vorherigen Beispielen getan haben
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
Für mich ist dies bereits eine deutliche Verbesserung gegenüber Tomášs Lösung, da ich je nach Bedarf explizit einen flachen oder tiefen Vergleich für meine Arrays auswählen kann .
Objektvergleich (Beispiel)
Was ist nun, wenn Sie eine Reihe von Objekten oder etwas haben? Vielleicht möchten Sie diese Arrays als "gleich" betrachten, wenn jedes Objekt den gleichen id
Wert hat ...
// 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
So einfach ist das. Hier habe ich Vanilla JS-Objekte verwendet, aber dieser Komparatortyp kann für jeden Objekttyp verwendet werden. sogar Ihre benutzerdefinierten Objekte. Die Lösung von Tomáš müsste komplett überarbeitet werden, um diese Art von Gleichstellungstest zu unterstützen
Deep Array mit Objekten? Kein Problem. Wir haben vielseitige, generische Funktionen entwickelt, damit sie in einer Vielzahl von Anwendungsfällen funktionieren.
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
Beliebiger Vergleich (Beispiel)
Oder was wäre, wenn Sie einen völlig willkürlichen Vergleich anstellen wollten? Vielleicht möchte ich wissen, ob jeder x
größer ist als jeder 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
Weniger ist mehr
Sie sehen, wir machen tatsächlich mehr mit weniger Code. Es ist nichts Kompliziertes an arrayCompare
sich und jeder der von uns erstellten benutzerdefinierten Komparatoren hat eine sehr einfache Implementierung.
Mit Leichtigkeit können wir genau definieren , wie wir wollen für zwei Arrays verglichen werden - flach, tief, streng, lose, einige Objekteigenschaft oder eine willkürliche Berechnung oder eine beliebige Kombination davon - alle eine Prozedur , arrayCompare
. Vielleicht sogar einen RegExp
Komparator ausdenken! Ich weiß, wie Kinder diese Regexps lieben ...
Ist es das schnellste? Nee. Aber es muss wahrscheinlich auch nicht sein. Wenn Geschwindigkeit die einzige Metrik ist, die zur Messung der Qualität unseres Codes verwendet wird, wird eine Menge wirklich großartiger Code weggeworfen. Deshalb nenne ich diesen Ansatz den praktischen Weg . Oder vielleicht um fairer zu sein, ein praktischer Weg. Diese Beschreibung ist für diese Antwort geeignet, da ich nicht sage, dass diese Antwort nur im Vergleich zu einer anderen Antwort praktisch ist. es ist objektiv wahr. Wir haben ein hohes Maß an Praktikabilität mit sehr wenig Code erreicht, über den man sehr leicht nachdenken kann. Kein anderer Code kann sagen, dass wir diese Beschreibung nicht verdient haben.
Ist es damit die "richtige" Lösung für Sie? Das liegt an Ihnen zu entscheiden. Und niemand sonst kann das für Sie tun; Nur Sie wissen, was Ihre Bedürfnisse sind. In fast allen Fällen schätze ich einfachen, praktischen und vielseitigen Code gegenüber cleverer und schneller Art. Was Sie schätzen, kann unterschiedlich sein. Wählen Sie also aus, was für Sie funktioniert.
Bearbeiten
Meine alte Antwort konzentrierte sich mehr auf die Zerlegung arrayEqual
in winzige Prozeduren. Es ist eine interessante Übung, aber nicht wirklich der beste (praktischste) Weg, um dieses Problem anzugehen. Wenn Sie interessiert sind, können Sie diesen Revisionsverlauf sehen.