common-lisp
Gleichheit und andere Vergleichsprädikate
Suche…
Der Unterschied zwischen EQ und EQL
EQ
prüft, ob zwei Werte die gleiche Speicheradresse haben. Mit anderen Worten, es wird geprüft, ob die beiden Werte tatsächlich das gleiche , identische Objekt sind. Es handelt sich also um den Identitätstest und sollte nur auf Strukturen angewendet werden: konses, Arrays, Strukturen, Objekte. In der Regel sehen Sie, ob Sie tatsächlich mit demselben Objekt zu tun haben, das über verschiedene Pfade „erreicht“ wird oder durch ein Aliasing durcheinander verläuft verschiedene Variablen.EQL
prüft, ob zwei Strukturen dasselbe Objekt sind (wieEQ
) oder ob es sich um dieselben nicht strukturierten Werte handelt (d.EQL
numerischen Werte für Zahlen des gleichen Typs oder die Zeichenwerte). Da er denEQ
Operator enthält und auch für nicht strukturierte Werte verwendet werden kann, ist er der wichtigste und am häufigsten verwendete Operator, und fast alle primitiven Funktionen, die einen Gleichheitsvergleich erfordern, wie beispielsweiseMEMBER
, verwenden diesen Operator standardmäßig .
Es ist also immer wahr, dass (EQ XY)
(EQL XY)
impliziert, während die Umkehrung nicht gilt.
Einige Beispiele können den Unterschied zwischen den beiden Operatoren deutlich machen:
(eq 'a 'a)
T ;; => since two s-expressions (QUOTE A) are “internalized” as the same symbol by the reader.
(eq (list 'a) (list 'a))
NIL ;; => here two lists are generated as different objects in memory
(let* ((l1 (list 'a))
(l2 l1))
(eq l1 l2))
T ;; => here there is only one list which is accessed through two different variables
(eq 1 1)
?? ;; it depends on the implementation: it could be either T or NIL if integers are “boxed”
(eq #\a #\a)
?? ;; it depends on the implementation, like for numbers
(eq 2d0 2d0)
?? ;; => dependes on the implementation, but usually is NIL, since numbers in double
;; precision are treated as structures in many implementations
(let ((a1 2d0)
(a2 2d0))
(eq a1 a2))
?? ;; => also in this case the results depends on the implementation
Lass uns die gleichen Beispiele mit EQL
versuchen:
(eql 'a 'a)
T ;; => equal because they are the same value, as for EQ
(eql (list 'a) (list 'a))
NIL ;; => different because they different objects in memory, as for EQ
(let* ((l1 (list 'a))
(l2 l1))
(eql l1 l2))
T ;; => as above
(eql 1 1)
T ;; they are the same number, even if integers are “boxed”
(eql #\a #\a)
T ;; they are the same character
(eql 2d0 2d0)
T ;; => they are the same number, even if numbers in double precision are treated as
;; structures in many implementations
(let ((a1 2d0)
(a2 2d0))
(eql a1 a2))
T ;; => as before
(eql 2 2.0)
NIL;; => since the two values are of a different numeric type
An den Beispielen können wir sehen, warum der EQL
Operator verwendet werden sollte, um portabel auf „Gleichheit“ für alle strukturierten und nicht strukturierten Werte zu prüfen und warum eigentlich viele Experten von der Verwendung von EQ
generell abraten.
Strukturelle Gleichheit mit EQUAL, EQUALP, TREE-EQUAL
Diese drei Operatoren implementieren die strukturelle Äquivalenz, dh sie prüfen, ob verschiedene, komplexe Objekte eine äquivalente Struktur mit einer äquivalenten Komponente haben.
EQUAL
verhält sich wie EQL
für nicht strukturierte Daten, während für Strukturen, die durch Konsolen (Listen und Bäume) und die beiden speziellen Typen von Arrays, Strings und Bit-Vektoren erstellt werden, eine strukturelle Äquivalenz ausgeführt wird , die auf zwei isomorphen und deren Strukturen true zurückgibt Elementarkomponenten sind bei EQUAL
entsprechend gleich. Zum Beispiel:
(equal (list 1 (cons 2 3)) (list 1 (cons 2 (+ 2 1))))
T ;; => since the two arguments are both equal to (1 (2 . 3))
(equal "ABC" "ABC")
T ;; => equality on strings
(equal "Abc" "ABC")
NIL ;; => case sensitive equality on strings
(equal '(1 . "ABC") '(1 . "ABC"))
T ;; => equal since it uses EQL on 1 and 1, and EQUAL on "ABC" and "ABC"
(let* ((a (make-array 3 :initial-contents '(1 2 3)))
(b (make-array 3 :initial-contents '(1 2 3)))
(c a))
(values (equal a b)
(equal a c)))
NIL ;; => the structural equivalence is not used for general arrays
T ;; => a and c are alias for the same object, so it is like EQL
EQUALP
gibt in allen Fällen, in denen EQUAL
wahr ist, true zurück, verwendet jedoch auch die strukturelle Äquivalenz für Arrays jeder Art und Dimension, für Strukturen und für Hashtabellen (nicht jedoch für Klasseninstanzen!). Darüber hinaus wird für Zeichenfolgen die Groß- und Kleinschreibung verwendet.
(equalp "Abc" "ABC")
T ;; => case insensitive equality on strings
(equalp (make-array 3 :initial-contents '(1 2 3))
(make-array 3 :initial-contents (list 1 2 (+ 2 1))))
T ;; => the structural equivalence is used also for any kind of arrays
(let ((hash1 (make-hash-table))
(hash2 (make-hash-table)))
(setf (gethash 'key hash1) 42)
(setf (gethash 'key hash2) 42)
(print (equalp hash1 hash2))
(setf (gethash 'another-key hash1) 84)
(equalp hash1 hash2))
T ;; => after the first two insertions, hash1 and hash2 have the same keys and values
NIL ;; => after the third insertion, hash1 and hash2 have different keys and values
(progn (defstruct s) (equalp (make-s) (make-s)))
T ;; => the two values are structurally equal
(progn (defclass c () ()) (equalp (make-instance 'c) (make-instance 'c)))
NIL ;; => two structurally equivalent class instances returns NIL, it's up to the user to
;; define an equality method for classes
TREE-EQUAL
kann schließlich auf Strukturen angewendet werden, die durch Ein-und cons
, ob diese isomorph sind, wie EQUAL
Die Wahl der zu verwendenden Funktion für den Vergleich der Blätter, dh des angetroffenen Nicht-Kons (Atom), TREE-EQUAL
dem Benutzer überlassen. EQL
kann ein beliebiger anderer Datentyp sein (standardmäßig wird EQL
für atom EQL
). Zum Beispiel:
(let ((l1 '(1 . ("A" . 2)))
(l2 '(1 . ("A" . 2))))
(tree-equal l1 l2 :test #'eql))
NIL ;; => since (eql "A" "A") gives NIL
(let ((l1 '(1 . ("A" . 2)))
(l2 '(1 . ("A" . 2))))
(tree-equal l1 l2 :test #'equal))
T ;; since (equal "A" "A") gives T
Vergleichsoperatoren für numerische Werte
Numerische Werte können mit =
und den anderen numerischen Vergleichsoperatoren ( /=
, <
, <=
, >
, >=
) verglichen werden, die den Unterschied in der physikalischen Darstellung der verschiedenen Arten von Zahlen ignorieren und den Vergleich der entsprechenden mathematischen Werte durchführen . Zum Beispiel:
(= 42 42)
T ;; => both number have the sme numeric type and the same value
(= 1 1.0 1d0)
T ;; => all the tree values represent the number 1, while for instance (eql 1 1d0) => NIL
;; since it returns true only if the operands have the same numeric type
(= 0.0 -0.0)
T ;; => again, the value is the same, while (eql 0.0 -0.0) => NIL
(= 3.0 #c(3.0 0.0))
T ;; => a complex number with 0 imaginary part is equal to a real number
(= 0.33333333 11184811/33554432)
T ;; => since a float number is passed to RATIONAL before comparing it to another number
;; => and (RATIONAL 0.33333333) => 11184811/33554432 in 32-bit IEEE floats architectures
(= 0.33333333 0.33333334)
T ;; => since the result of RATIONAL on both numbers is equal in 32-bit IEEE floats architectures
(= 0.33333333d0 0.33333334d0)
NIL ;; => since the RATIONAL of the two numbers in double precision is different
Aus diesen Beispielen können wir folgern, dass =
der Operator ist, der normalerweise zum Vergleich zwischen numerischen Werten verwendet werden sollte, es sei denn, wir möchten streng darauf achten, dass zwei numerische Werte nur dann gleich sind , wenn sie denselben numerischen Typ in haben In welchem Fall sollte EQL
verwendet werden.
Vergleichsoperatoren für Zeichen und Strings
Common Lisp verfügt über 12 typspezifische Operatoren, um zwei Zeichen zu vergleichen, von denen 6 die Groß- und Kleinschreibung und die anderen nicht die Groß- und Kleinschreibung berücksichtigt. Ihre Namen haben ein einfaches Muster, um sich an ihre Bedeutung zu erinnern:
Groß- / Kleinschreibung | Groß- und Kleinschreibung |
---|---|
CHAR = | CHAR-EQUAL |
CHAR / = | CHAR-NOT-GLEICH |
CHAR < | CHAR-LESSP |
CHAR <= | CHAR-NOT-GREATERP |
CHAR> | CHAR-GREATERP |
CHAR> = | CHAR-NOT-LESSP |
Zwei Zeichen desselben Falls sind in derselben Reihenfolge wie die entsprechenden Codes, die durch CHAR-CODE
, während bei Vergleichen ohne a..z
Groß- und Kleinschreibung die relative Reihenfolge zwischen zwei beliebigen Zeichen aus den beiden Bereichen a..z
, A..Z
Implementierung abhängt . Beispiele:
(char= #\a #\a)
T ;; => the operands are the same character
(char= #\a #\A)
NIL ;; => case sensitive equality
(CHAR-EQUAL #\a #\A)
T ;; => case insensitive equality
(char> #\b #\a)
T ;; => since in all encodings (CHAR-CODE #\b) is always greater than (CHAR-CODE #\a)
(char-greaterp #\b \#A)
T ;; => since for case insensitive the ordering is such that A=a, B=b, and so on,
;; and furthermore either 9<A or Z<0.
(char> #\b #\A)
?? ;; => the result is implementation dependent
Für Strings sind die spezifischen Operatoren STRING=
, STRING-EQUAL
usw. mit dem Wort STRING anstelle von CHAR. Zwei Zeichenfolgen sind gleich, wenn sie dieselbe Anzahl von Zeichen haben, und die entsprechenden Zeichen sind gemäß CHAR=
oder CHAR-EQUAL
wenn bei dem Test die Groß- / Kleinschreibung beachtet wird oder nicht.
Die Reihenfolge zwischen den Saiten ist die lexikographische Reihenfolge der Zeichen der beiden Saiten. Wenn ein Sortiervergleich erfolgreich ist, ist das Ergebnis nicht T
, sondern der Index des ersten Zeichens, in dem sich die beiden Zeichenfolgen unterscheiden (was mit true übereinstimmt, da jedes Nicht-NIL-Objekt in Common Lisp ein „generalized boolean“ ist).
Wichtig ist, dass alle Vergleichsoperatoren für string vier Schlüsselwortparameter akzeptieren: start1
, end1
, start2
, end2
, mit denen der Vergleich auf eine zusammenhängende Zeichenfolge innerhalb einer oder beider Zeichenfolgen beschränkt werden kann. Wenn der Startindex weggelassen wird, ist der Startindex 0, der Endindex wird weggelassen und entspricht der Länge des Strings. Der Vergleich wird für den Teilstring durchgeführt, der mit dem Zeichen mit Index :start
und endet mit dem Zeichen mit Index :end - 1
eingeschlossen.
Beachten Sie schließlich, dass eine Zeichenfolge auch mit einem einzelnen Zeichen nicht mit einem Zeichen verglichen werden kann.
Beispiele:
(string= "foo" "foo")
T ;; => both strings have the same lenght and the characters are `CHAR=` in order
(string= "Foo" "foo")
NIL ;; => case sensitive comparison
(string-equal "Foo" "foo")
T ;; => case insensitive comparison
(string= "foobar" "barfoo" :end1 3 :start2 3)
T ;; => the comparison is perform on substrings
(string< "fooarr" "foobar")
3 ;; => the first string is lexicographically less than the second one and
;; the first character different in the two string has index 3
(string< "foo" "foobar")
3 ;; => the first string is a prefix of the second and the result is its length
Als Sonderfall können die String-Vergleichsoperatoren auch auf Symbole angewendet werden, und der Vergleich wird am SYMBOL-NAME
des Symbols vorgenommen. Zum Beispiel:
(string= 'a "A")
T ;; since (SYMBOL-NAME 'a) is "A"
(string-equal '|a| 'a)
T ;; since the the symbol names are "a" and "A" respectively
Als letzte Bemerkung entspricht EQL
für Zeichen CHAR=
; EQUAL
für Strings entspricht STRING=
, EQUALP
für Strings entspricht STRING-EQUAL
.
Übersicht
In Common Lisp gibt es viele verschiedene Prädikate für den Vergleich von Werten. Sie können in folgende Kategorien eingeteilt werden:
- Generische Gleichheitsoperatoren: EQ, EQL, EQUAL, EQUALP. Sie können für Werte eines beliebigen Typs verwendet werden und geben immer einen booleschen Wert T oder NIL zurück.
- Geben Sie spezifische Gleichheitsoperatoren ein: = und = für Zahlen, CHAR = CHAR = CHAR-EQUAL CHAR-NOT-EQUAL für Zeichen, STRING = STRING = STRING-EQUAL STRING-NOT-EQUAL für Zeichenfolgen, TREE-EQUAL für Konsolen.
- Vergleichsoperatoren für numerische Werte: <, <=,>,> =. Sie können auf jeden Zahlentyp angewendet werden und vergleichen den mathematischen Wert der Zahl unabhängig vom tatsächlichen Typ.
- Vergleichsoperatoren für Zeichen wie CHAR <, CHAR-LESSP usw., die Zeichen entweder nach Groß- oder Kleinschreibung oder nach Groß- und Kleinschreibung vergleichen, je nach implementierungsabhängiger Reihenfolge, die die natürliche alphabetische Reihenfolge beibehält.
- Vergleichsoperatoren für Strings wie STRING <, STRING-LESSP usw., die Strings lexikographisch entweder case case oder in case case nicht vergleichen, indem Sie die Zeichenvergleichsoperatoren verwenden.