common-lisp
Igualdad y otros predicados de comparación.
Buscar..
La diferencia entre EQ y EQL.
EQ
verifica si dos valores tienen la misma dirección de memoria: en otras palabras, verifica si los dos valores son en realidad el mismo objeto idéntico . Por lo tanto, se puede considerar la prueba de identidad y se debe aplicar solo a estructuras: conses, arrays, estructuras, objetos, generalmente para ver si se trata de hecho con el mismo objeto "alcanzado" a través de diferentes caminos, o alias a través de diferentes variablesEQL
verifica si dos estructuras son el mismo objeto (comoEQ
) o si son los mismos valores no estructurados (es decir, los mismos valores numéricos para números del mismo tipo o los valores de caracteres). Dado que incluye el operadorEQ
y se puede usar también en valores no estructurados, es el operador más importante y más utilizado, y casi todas las funciones primitivas que requieren una comparación de igualdad, comoMEMBER
, usan este operador de forma predeterminada .
Entonces, siempre es cierto que (EQ XY)
implica (EQL XY)
, mientras que viceversa no se cumple.
Algunos ejemplos pueden aclarar la diferencia entre los dos operadores:
(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
Probemos los mismos ejemplos 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
A partir de los ejemplos que podemos ver por qué la EQL
operador debe utilizar para comprobar de forma portátil para la “identidad” para todos los valores, estructurado y no estructurado, y por qué en realidad muchos expertos desaconsejan el uso de EQ
en general.
Igualdad estructural con EQUAL, EQUALP, TREE-EQUAL
Estos tres operadores implementan la equivalencia estructural, es decir, comprueban si diferentes objetos complejos tienen una estructura equivalente con un componente equivalente.
EQUAL
comporta como EQL
para datos no estructurados, mientras que para las estructuras construidas por pares (listas y árboles), y los dos tipos especiales de matrices, cadenas y vectores de bits, realiza una equivalencia estructural , volviéndose verdadera en dos estructuras que son isomorfas y cuyos Los componentes elementales son igualmente iguales por EQUAL
. Por ejemplo:
(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
devuelve verdadero en todos los casos en que EQUAL
es verdadero, pero también usa equivalencia estructural para arreglos de cualquier tipo y dimensión, para estructuras y para tablas hash (¡pero no para instancias de clase!). Por otra parte, utiliza equivalencia insensible a mayúsculas y minúsculas para cadenas.
(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
Finalmente, TREE-EQUAL
se puede aplicar a las estructuras creadas a través de cons
y verifica si son isomorfas, como EQUAL
, pero dejando al usuario la opción de qué función usar para comparar las hojas, es decir, los no contras (átomo) encontrados. que puede ser de cualquier otro tipo de datos (de forma predeterminada, la prueba utilizada en el átomo es EQL
). Por ejemplo:
(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
Operadores de comparación en valores numéricos
Los valores numéricos pueden compararse con =
y los otros operadores de comparación numéricos ( /=
, <
, <=
, >
, >=
) que ignoran la diferencia en la representación física de los diferentes tipos de números, y realizan la comparación de los valores matemáticos correspondientes . Por ejemplo:
(= 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
A partir de estos ejemplos, podemos concluir que =
es el operador que normalmente se debe usar para realizar la comparación entre valores numéricos, a menos que queramos ser estrictos en el hecho de que dos valores numéricos son iguales solo si tienen el mismo tipo numérico, en En qué caso se debe utilizar EQL
.
Operadores de comparación en caracteres y cadenas.
Common Lisp tiene 12 operadores de tipo específico para comparar dos caracteres, 6 de ellos sensibles a las mayúsculas y los otros no. Sus nombres tienen un patrón simple para hacer fácil recordar su significado:
Distingue mayúsculas y minúsculas | Caso Insensible |
---|---|
CHAR = | CHAR-IGUAL |
CHAR / = | CHAR-NO-IGUAL |
CHAR < | CHAR-LESSP |
CHAR <= | CHAR-NOT-GREATERP |
CHAR> | CHAR-GREATERP |
CHAR> = | CHAR-NOT-LESSP |
Dos caracteres del mismo caso están en el mismo orden que los códigos correspondientes obtenidos por CHAR-CODE
, mientras que para comparaciones no sensibles al caso, el orden relativo entre dos caracteres cualquiera de los dos rangos a..z
, A..Z
depende de la implementación . Ejemplos:
(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
Para cadenas, los operadores específicos son STRING=
, STRING-EQUAL
, etc. con la palabra STRING en lugar de CHAR. Dos cadenas son iguales si tienen el mismo número de caracteres y los caracteres correspondientes son iguales según CHAR=
o CHAR-EQUAL
si la prueba distingue entre mayúsculas y minúsculas.
El orden entre las cadenas es el orden lexicográfico de los caracteres de las dos cadenas. Cuando una comparación de ordenación tiene éxito, el resultado no es T
, pero el índice del primer carácter en el que difieren las dos cadenas (lo que equivale a verdadero, ya que cada objeto no NIL es un "booleano generalizado" en Common Lisp).
Una cosa importante es que todos los operadores de comparación en la cadena aceptan cuatro parámetros de palabras clave: start1
, end1
, start2
, end2
, que pueden usarse para restringir la comparación a solo una serie de caracteres contiguos dentro de una o ambas cadenas. El índice de inicio si se omite es 0, el índice final se omite es igual a la longitud de la cadena, y la comparación se realiza en la subcadena comenzando en el carácter con índice :start
y termina con el carácter con índice :end - 1
incluido.
Finalmente, tenga en cuenta que una cadena, incluso con un solo carácter, no se puede comparar con un carácter.
Ejemplos:
(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
Como un caso especial, los operadores de comparación de cadenas también se pueden aplicar a los símbolos, y la comparación se realiza en el SYMBOL-NAME
del símbolo. Por ejemplo:
(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
Como nota final, EQL
en los caracteres es equivalente a CHAR=
; EQUAL
en cadenas es equivalente a STRING=
, mientras que EQUALP
en cadenas es equivalente a STRING-EQUAL
.
Overwiew
En Common Lisp hay muchos predicados diferentes para comparar valores. Se pueden clasificar en las siguientes categorías:
- Operadores de igualdad genéricos: EQ, EQL, EQUAL, EQUALP. Se pueden usar para valores de cualquier tipo y devolver siempre un valor booleano T o NIL.
- Operadores de igualdad específicos de tipo: = y = para números, CHAR = CHAR = CHAR-EQUAL CHAR-NOT-EQUAL para los caracteres, STRING = STRING = STRING-IGUAL STRING-NOT-IGUAL para las cadenas, TREE-IGUAL para las cuentas.
- Operadores de comparación para valores numéricos: <, <=,>,> =. Se pueden aplicar a cualquier tipo de número y comparar el valor matemático del número, independientemente del tipo real.
- Operadores de comparación para caracteres, como CHAR <, CHAR-LESSP, etc., que comparan caracteres de una manera sensible a las mayúsculas y minúsculas o de una manera no sensible a las mayúsculas y minúsculas, de acuerdo con un orden de implementación que preserva el orden alfabético natural.
- Operadores de comparación para cadenas, como STRING <, STRING-LESSP, etc., que comparan cadenas por lexicografía, ya sea de forma sensible a las mayúsculas y minúsculas, mediante el uso de operadores de comparación de caracteres.