common-lisp
Gelijkheid en andere vergelijkingspredikaten
Zoeken…
Het verschil tussen EQ en EQL
EQ
controleert of twee waarden hetzelfde geheugenadres hebben: met andere woorden, het controleert of de twee waarden hetzelfde , identieke object zijn. Het kan dus worden beschouwd als de identiteitstest en moet alleen worden toegepast op structuren: conses, arrays, structuren, objecten, meestal om te zien of u in feite te maken hebt met hetzelfde object dat via verschillende paden wordt "bereikt", of alias via verschillende variabelen.EQL
controleert of twee structuren hetzelfde object zijn (zoalsEQ
) of dat ze dezelfde niet-gestructureerde waarden zijn (dat wil zeggen dezelfde numerieke waarden voor nummers van hetzelfde type of de tekenwaarden). Omdat het deEQ
operator bevat en ook kan worden gebruikt op niet-gestructureerde waarden, is het de belangrijkste en meest gebruikte operator, en bijna alle primitieve functies die een gelijkheidsvergelijking vereisen, zoalsMEMBER
, gebruiken deze operator standaard .
Het is dus altijd waar dat (EQ XY)
impliceert (EQL XY)
, terwijl de viceversa niet geldt.
Een paar voorbeelden kunnen het verschil tussen de twee operatoren duidelijk maken:
(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
Laten we dezelfde voorbeelden proberen met EQL
:
(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
Uit de voorbeelden kunnen we zien waarom de EQL
operator moet worden gebruikt om portabel te controleren op "gelijkheid" voor alle waarden, gestructureerd en niet-gestructureerd, en waarom eigenlijk veel experts het gebruik van EQ
in het algemeen afraden.
Structurele gelijkheid met EQUAL, EQUALP, TREE-EQUAL
Deze drie operatoren implementeren structurele equivalentie, dat wil zeggen ze controleren of verschillende, complexe objecten een vergelijkbare structuur hebben met een vergelijkbare component.
EQUAL
gedraagt zich als EQL
voor niet-gestructureerde gegevens, terwijl voor structuren gebouwd door conses (lijsten en bomen), en de twee speciale soorten arrays, strings en bitvectoren, het structurele gelijkwaardigheid uitvoert en waar terugkomt op twee isomorfe structuren waarvan elementaire componenten zijn overeenkomstig gelijk aan EQUAL
. Bijvoorbeeld:
(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
retourneert true in alle gevallen waarin EQUAL
waar is, maar het gebruikt ook structurele equivalentie voor arrays van elke soort en dimensie, voor structuren en voor hashtabellen (maar niet voor klasse-instanties!). Bovendien wordt hoofdlettergevoelige equivalentie voor tekenreeksen gebruikt.
(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
Ten slotte kan TREE-EQUAL
worden toegepast op structuren die zijn gebouwd via cons
en controleren of ze isomorf zijn, zoals EQUAL
, maar de gebruiker de keuze laten welke functie hij gebruikt om de bladeren te vergelijken, dwz de aangetroffen non-cons (atoom), dat kan van elk ander gegevenstype zijn (standaard is de test die op het atoom wordt gebruikt EQL
). Bijvoorbeeld:
(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
Vergelijkingsoperatoren op numerieke waarden
Numerieke waarden kunnen worden vergeleken met =
en de andere numerieke vergelijkingsoperatoren ( /=
, <
, <=
, >
, >=
) die het verschil in de fysieke weergave van de verschillende typen getallen negeren en de overeenkomstige wiskundige waarden vergelijken . Bijvoorbeeld:
(= 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
Uit deze voorbeelden kunnen we concluderen dat =
de operator is die normaal moet worden gebruikt om een vergelijking tussen numerieke waarden uit te voeren, tenzij we strikt willen zijn dat twee numerieke waarden alleen gelijk zijn als ze ook hetzelfde numerieke type hebben, in welk geval EQL
moet worden gebruikt.
Vergelijkingsoperatoren op tekens en tekenreeksen
Common Lisp heeft 12 typespecifieke operatoren om twee tekens te vergelijken, waarvan 6 hoofdlettergevoelig en de andere hoofdletterongevoelig. Hun namen hebben een eenvoudig patroon om hun betekenis gemakkelijk te onthouden:
Hoofdlettergevoelig | Hoofdletterongevoelig |
---|---|
CHAR = | CHAR-EQUAL |
CHAR / = | CHAR-NOT-EQUAL |
CHAR < | CHAR-LESSP |
CHAR <= | CHAR-NOT-GREATERP |
CHAR> | CHAR-GREATERP |
CHAR> = | CHAR-NOT-LESSP |
Twee tekens van hetzelfde geval staan in dezelfde volgorde als de overeenkomstige codes verkregen door CHAR-CODE
, terwijl voor hoofdletterongevoelige vergelijkingen de relatieve volgorde tussen twee tekens uit de twee bereiken a..z
, A..Z
afhankelijk is van de implementatie . Voorbeelden:
(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
Voor strings zijn de specifieke operatoren STRING=
, STRING-EQUAL
, etc. met het woord STRING in plaats van CHAR. Twee tekenreeksen zijn gelijk als ze hetzelfde aantal tekens hebben en de bijbehorende tekens zijn gelijk volgens CHAR=
of CHAR-EQUAL
als de test hoofdlettergevoelig is of niet.
De volgorde tussen tekenreeksen is de lexicografische volgorde van de tekens van de twee tekenreeksen. Wanneer een ordeningsvergelijking slaagt, is het resultaat niet T
, maar de index van het eerste teken waarin de twee tekenreeksen verschillen (wat equivalent is aan true, omdat elk niet-NIL-object een "gegeneraliseerde Boolean" is in Common Lisp).
Een belangrijk ding is dat alle vergelijkingsoperatoren op string vier sleutelwoordenparameters accepteren: start1
, end1
, start2
, end2
, die kunnen worden gebruikt om de vergelijking te beperken tot alleen een aaneengesloten reeks tekens binnen een of beide reeksen. De startindex indien weggelaten is 0, de eindindex weggelaten is gelijk aan de lengte van de string, en de vergelijking wordt uitgevoerd op de substring beginnend bij karakter met index :start
en eindigend met het karakter met index :end - 1
inclusief :end - 1
.
Merk ten slotte op dat een string, zelfs met een enkel karakter, niet kan worden vergeleken met een karakter.
Voorbeelden:
(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 een speciaal geval kunnen de tekenreeksvergelijkingsoperatoren ook worden toegepast op symbolen en wordt de vergelijking gemaakt op de SYMBOL-NAME
van het symbool. Bijvoorbeeld:
(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 laatste opmerking is EQL
op tekens gelijk aan CHAR=
; EQUAL
op tekenreeksen is gelijk aan STRING=
, terwijl EQUALP
op tekenreeksen gelijk is aan STRING-EQUAL
.
Overwiew
In Common Lisp zijn er veel verschillende predicaten voor het vergelijken van waarden. Ze kunnen worden ingedeeld in de volgende categorieën:
- Generieke gelijkheidsexploitanten: EQ, EQL, EQUAL, EQUALP. Ze kunnen worden gebruikt voor waarden van elk type en retourneren altijd een Booleaanse waarde T of NIL.
- Typ specifieke gelijkheidsoperatoren: = en = voor getallen, CHAR = CHAR = CHAR-EQUAL CHAR-NOT-EQUAL voor tekens, STRING = STRING = STRING-EQUAL STRING-NIET-EQUAL voor tekenreeksen, TREE-EQUAL voor conses.
- Vergelijkingsoperatoren voor numerieke waarden: <, <=,>,> =. Ze kunnen op elk type nummer worden toegepast en de wiskundige waarde van het nummer vergelijken, onafhankelijk van het werkelijke type.
- Vergelijkingsoperatoren voor tekens, zoals CHAR <, CHAR-LESSP, enz., Die tekens vergelijken op een hoofdlettergevoelige manier of op een hoofdletterongevoelige manier, volgens een implementatie-afhankelijke volgorde die de natuurlijke alfabetische volgorde behoudt.
- Vergelijkingsoperatoren voor tekenreeksen, zoals STRING <, STRING-LESSP, enz., Die tekenreeksen lexicografisch vergelijken, hoofdlettergevoelig of hoofdlettergevoelig, met behulp van de tekenvergelijkingsoperatoren.