common-lisp
Uguaglianza e altri predicati di confronto
Ricerca…
La differenza tra EQ ed EQL
EQ
controlla se due valori hanno lo stesso indirizzo di memoria: in altre parole, controlla se i due valori sono effettivamente lo stesso oggetto identico . Quindi, può essere considerato il test di identità e dovrebbe essere applicato solo alle strutture: conses, matrici, strutture, oggetti, in genere per vedere se si sta affrontando in effetti con lo stesso oggetto "raggiunto" attraverso percorsi diversi o alias attraverso diverse variabili.EQL
verifica se due strutture sono lo stesso oggetto (comeEQ
) o se sono gli stessi valori non strutturati (ovvero, gli stessi valori numerici per i numeri dello stesso tipo oi valori dei caratteri). Poiché include l'operatoreEQ
e può essere utilizzato anche su valori non strutturati, è l'operatore più importante e più comunemente utilizzato, e quasi tutte le funzioni primitive che richiedono un confronto di uguaglianza, comeMEMBER
, utilizzano per impostazione predefinita questo operatore .
Quindi, è sempre vero che (EQ XY)
implica (EQL XY)
, mentre il viceversa non regge.
Alcuni esempi possono chiarire la differenza tra i due operatori:
(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
Proviamo gli stessi esempi con 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
Dagli esempi possiamo vedere perché l'operatore EQL
dovrebbe essere usato per controllare "l'identità" per tutti i valori, strutturati e non strutturati, e perché in realtà molti esperti sconsigliano l'uso EQ
in generale.
Uguaglianza strutturale con EQUAL, EQUALP, TREE-EQUAL
Questi tre operatori implementano l'equivalenza strutturale, ovvero controllano se oggetti diversi e complessi hanno una struttura equivalente con componenti equivalenti.
EQUAL
comporta come EQL
per i dati non strutturati, mentre per le strutture costruite da conse (liste e alberi) e i due tipi speciali di array, stringhe e bit vettori, esegue l' equivalenza strutturale , restituendo true su due strutture che sono isomorfe e di cui le componenti elementari sono corrispondentemente uguali a quelle di EQUAL
. Per esempio:
(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
restituisce true in tutti i casi in cui EQUAL
è true, ma utilizza anche l'equivalenza strutturale per gli array di qualsiasi tipo e dimensione, per le strutture e per le tabelle hash (ma non per le istanze di classe!). Inoltre, utilizza l'equivalenza case-insensitive per le stringhe.
(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
Infine, TREE-EQUAL
può essere applicato a strutture costruite attraverso cons
e controlla se sono isomorfe, come EQUAL
, ma lasciando all'utente la scelta di quale funzione usare per confrontare le foglie, cioè i non-cons (atomi) incontrati, che può essere di qualsiasi altro tipo di dati (per impostazione predefinita, il test utilizzato su atomo è EQL
). Per esempio:
(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
Confronto tra operatori su valori numerici
I valori numerici possono essere confrontati con =
e gli altri operatori di confronto numerico ( /=
, <
, <=
, >
, >=
) che ignorano la differenza nella rappresentazione fisica dei diversi tipi di numeri ed eseguono il confronto dei corrispondenti valori matematici . Per esempio:
(= 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
Da questi esempi, possiamo concludere che =
è l'operatore che dovrebbe essere usato normalmente per eseguire il confronto tra valori numerici, a meno che non vogliamo essere rigorosi sul fatto che due valori numerici sono uguali solo se hanno anche lo stesso tipo numerico, in quale caso EQL
dovrebbe essere usato.
Operatori di confronto su personaggi e stringhe
Common Lisp ha 12 operatori specifici del tipo per confrontare due caratteri, 6 dei quali maiuscoli e altri maiuscoli. I loro nomi hanno uno schema semplice per rendere facile ricordare il loro significato:
Che tiene conto del maiuscolo o minuscolo | Case Insensitive |
---|---|
CHAR = | Char-EQUAL |
Car / = | Char-NON-EQUAL |
CHAR < | Char-LESSP |
CHAR <= | Char-NON-GREATERP |
char> | Char-GREATERP |
Char> = | Char-NON-LESSP |
Due caratteri dello stesso caso sono nello stesso ordine dei codici corrispondenti ottenuti da CHAR-CODE
, mentre per i confronti senza distinzione tra maiuscole e minuscole l'ordine relativo tra due caratteri presi dai due intervalli a..z
, A..Z
dipende dall'implementazione . Esempi:
(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
Per le stringhe gli operatori specifici sono STRING=
, STRING-EQUAL
, ecc. Con la parola STRING invece di CHAR. Due stringhe sono uguali se hanno lo stesso numero di caratteri e i caratteri corrispondenti sono uguali secondo CHAR=
o CHAR-EQUAL
se il test è sensibile al maiuscolo e al minuscolo.
L'ordinamento tra stringhe è l'ordine lessicografico sui caratteri delle due corde. Quando un confronto di ordinamenti ha esito positivo, il risultato non è T
, ma l'indice del primo carattere in cui le due stringhe differiscono (che è equivalente a true, poiché ogni oggetto non NIL è un "booleano generalizzato" in Common Lisp).
Una cosa importante è che tutti gli operatori di confronto sulla stringa accettano quattro parametri di parole chiave: start1
, end1
, start2
, end2
, che possono essere utilizzati per limitare il confronto solo a una serie contigua di caratteri all'interno di una o entrambe le stringhe. L'indice iniziale se omesso è 0, l'indice finale omesso è uguale alla lunghezza della stringa e il confronto eseguito sulla sottostringa a partire dal carattere con indice :start
e termina con il carattere con indice :end - 1
incluso.
Infine, nota che una stringa, anche con un singolo carattere, non può essere paragonata a un personaggio.
Esempi:
(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
Come caso speciale, gli operatori di confronto delle stringhe possono anche essere applicati ai simboli e il confronto viene eseguito sul SYMBOL-NAME
del simbolo. Per esempio:
(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
Come nota finale, EQL
sui caratteri è equivalente a CHAR=
; EQUAL
su stringhe è equivalente a STRING=
, mentre EQUALP
su stringhe è equivalente a STRING-EQUAL
.
Overwiew
In Common Lisp ci sono molti predicati diversi per confrontare i valori. Possono essere classificati nelle seguenti categorie:
- Operatori generici di uguaglianza: EQ, EQL, EQUAL, EQUALP. Possono essere utilizzati per valori di qualsiasi tipo e restituiscono sempre un valore booleano T o NIL.
- Digitare operatori specifici di uguaglianza: = e = per i numeri, CHAR = CHAR = CHAR-EQUAL CHAR-NOT-EQUAL per i caratteri, STRING = STRING = STRING-EQUAL STRING-NOT-EQUAL per le stringhe, TREE-EQUAL per le conses.
- Operatori di confronto per valori numerici: <, <=,>,> =. Possono essere applicati a qualsiasi tipo di numero e confrontare il valore matematico del numero, indipendentemente dal tipo effettivo.
- Operatori di confronto per caratteri, come CHAR <, CHAR-LESSP, ecc., Che confrontano i caratteri in modo sensibile al maiuscolo o minuscolo, in base a un ordine di implementazione che preserva l'ordinamento alfabetico naturale.
- Operatori di confronto per stringhe, come STRING <, STRING-LESSP, ecc., Che confrontano le stringhe lessicograficamente, in modo sensibile al maiuscolo o minuscolo, utilizzando gli operatori di confronto dei caratteri.