common-lisp
Равенство и другие предикаты сравнения
Поиск…
Разница между EQ и EQL
EQ
проверяет, имеют ли два значения один и тот же адрес памяти: другими словами, он проверяет, являются ли эти два значения одинаковыми , идентичный объект. Таким образом, это можно считать тестом идентификации, и его следует применять только к структурам: conses, массивам, структурам, объектам, как правило, чтобы увидеть, действительно ли вы имеете дело с тем же объектом, «достигшим» через разные пути или с помощью различные переменные.EQL
проверяет, являются ли две структуры одним и тем же объектом (например,EQ
) или являются одинаковыми неструктурированными значениями (то есть теми же численными значениями для чисел одного и того же типа или символьных значений). Поскольку он включает в себя операторEQ
и может использоваться также для неструктурированных значений, является наиболее важным и наиболее часто используемым оператором, и почти все примитивные функции, для которых требуется сравнение равенства, например,MEMBER
, используют по умолчанию этот оператор .
Таким образом, всегда верно, что (EQ XY)
подразумевает (EQL XY)
, а наоборот - не выполняется.
Несколько примеров могут устранить разницу между двумя операторами:
(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
Попробуем те же примеры с 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
Из примеров видно, почему оператор EQL
следует использовать для надёжной проверки «идентичности» для всех значений, структурированных и неструктурированных, и почему многие эксперты советуют не использовать EQ
в целом.
Структурное равенство с EQUAL, EQUALP, TREE-EQUAL
Эти три оператора реализуют структурную эквивалентность, то есть проверяют, имеют ли разные сложные объекты эквивалентную структуру с эквивалентным компонентом.
EQUAL
ведет себя как EQL
для неструктурированных данных, тогда как для структур, созданных conses (списки и деревья) и двух специальных типов массивов, строк и битовых векторов, он выполняет структурную эквивалентность , возвращая true на две структуры, которые являются изоморфными и чьи элементарные компоненты соответственно равны EQUAL
. Например:
(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
возвращает true во всех случаях, когда EQUAL
является истинным, но он использует также структурную эквивалентность для массивов любого вида и размерности, для структур и для хэш-таблиц (но не для экземпляров класса!). Более того, для строк используется нечувствительность к регистру.
(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
может применяться к структурам, построенным через cons
и проверяет, являются ли они изоморфными, как EQUAL
, но оставляя пользователю выбор функции, которую можно использовать для сравнения листьев, т. Е. Несовпадающих (атомов) который может быть любого другого типа данных (по умолчанию тест, используемый для атома, является EQL
). Например:
(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
Операторы сравнения по числовым значениям
Числовые значения могут сравниваться с =
и другими операторами числового сравнения ( /=
, <
, <=
, >
, >=
), которые игнорируют разницу в физическом представлении различных типов чисел и выполняют сравнение соответствующих математических значений , Например:
(= 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
Из этих примеров можно сделать вывод, что =
- это оператор, который обычно должен использоваться для сравнения между числовыми значениями, если мы не хотим быть строгими в отношении того, что два числовых значения равны, только если они имеют одинаковый числовой тип, в в каком случае следует использовать EQL
.
Операторы сравнения по символам и строкам
Common Lisp имеет 12 типов специфических операторов для сравнения двух символов, 6 из которых чувствительны к регистру, а остальные - нечувствительны к регистру. Их имена имеют простой шаблон, позволяющий легко запомнить их смысл:
С учетом регистра | Без учета регистра |
---|---|
СИМВОЛ = | CHAR-EQUAL |
СИМ / = | CHAR-NOT-РАВНО |
CHAR < | СИМ-LESSP |
CHAR <= | CHAR-NOT-GREATERP |
CHAR> | СИМ-GREATERP |
CHAR> = | CHAR-NOT-LESSP |
Два символа одного и того же случая находятся в том же порядке, что и соответствующие коды, полученные CHAR-CODE
, тогда как для нечувствительных к регистру сравнений относительный порядок между любыми двумя символами, взятыми из двух диапазонов a..z
, A..Z
зависит от реализации , Примеры:
(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
Для строк конкретными операторами являются STRING=
, STRING-EQUAL
и т. Д. Со словом STRING вместо CHAR. Две строки равны, если они имеют одинаковое количество символов, а соответствующие символы равны в соответствии с CHAR=
или CHAR-EQUAL
если тест чувствителен к регистру или нет.
Порядок между строками является лексикографическим порядком для символов двух строк. Когда сравнение заказов выполняется успешно, результатом является не T
, а индекс первого символа, в котором две строки отличаются (что эквивалентно true, так как каждый объект, отличный от NIL, является «обобщенным логическим» в Common Lisp).
Важным является то , что все операторы сравнения на строке принимают четыре параметра: ключевых слов start1
, end1
, start2
, end2
, которые могут быть использованы для ограничения сравнения только к прилежащему пробегу символов внутри одной или оба строк. Начальный индекс, если опущен, равен 0, оконечный индекс опущен, равен длине строки, а сравнение выполняется в подстроке, начинающейся с символа с индексом :start
и заканчивается символом с индексом :end - 1
включен.
Наконец, обратите внимание, что строка, даже с одним символом, не может сравниваться с символом.
Примеры:
(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
В качестве особого случая операторы сравнения строк также могут применяться к символам, и сравнение производится по символу SYMBOL-NAME
. Например:
(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
Как окончательное замечание, EQL
для символов эквивалентно CHAR=
; EQUAL
в строках эквивалентен STRING=
, а EQUALP
на строках эквивалентен STRING-EQUAL
.
Overwiew
В Common Lisp существует множество разных предикатов для сравнения значений. Они могут быть классифицированы в следующих категориях:
- Общие операторы равенства: EQ, EQL, EQUAL, EQUALP. Они могут использоваться для значений любого типа и всегда возвращать логическое значение T или NIL.
- Тип конкретных операторов равенства: = и = для чисел, CHAR = CHAR = CHAR-EQUAL CHAR-NOT-EQUAL для символов, STRING = STRING = STRING-EQUAL STRING-NOT-EQUAL для строк, TREE-EQUAL для conses.
- Операторы сравнения для числовых значений: <, <=,>,> =. Они могут применяться к любому типу числа и сравнивать математическое значение числа, независимо от фактического типа.
- Операторы сравнения для символов, такие как CHAR <, CHAR-LESSP и т. Д., Которые сравнивают символы либо чувствительным к делу способом, либо нечувствительным к регистру образом в соответствии с зависимым от реализации порядком, который сохраняет естественное алфавитное упорядочение.
- Операторы сравнения для строк, такие как STRING <, STRING-LESSP и т. Д., Которые сравнивают строки лексикографически, либо чувствительным к делу способом, либо нечувствительным к регистру образом, используя операторы сравнения символов.