Common Lisp 有许多不同的相等性谓词。
这与其他编程语言不同,那些语言可能只有一两个(例如 == 和 ===)。
在 Common Lisp 中,有些谓词只针对特定类型,另一些则是通用的。
这里要讲的是后者。
通用的相等性谓词有四个,它们对什么算作“相等”的严格程度各不相同。
按从最严格到最宽松的顺序,它们分别是:eq、eql、equal 和 equalp。
下面是一组简要的定义(省略了一些细节):
eq:把相等定义为两个对象完全相同。
例如:(eq 'a 'a) ; => T
(eq (list 1 2) (list 1 2)) ; => NIL
eql:把相等定义为:类型和值都相同的两个数字、相同的两个字符,或者其他任何东西只要它们eq即算相等。
例如:(eql 1 1) ; => T
(eql #\a #\a) ; => T
(eql 1 1.0) ; => NIL
(eql #\a #\A) ; => NIL
equal:把相等定义为:如果两个列表的每个元素都equal,那么这两个列表就equal;如果两个字符串的每个元素都eql,那么这两个字符串就equal;如果两个数组eq,那么这两个数组就equal;其他一切东西只要eql就算equal。
例如:(equal (list 1 2) (list 1 2)) ; => T
(equal #(1 2) #(1 2)) ; => NIL
(equal "foo" "foo") ; => T
equalp:把相等定义为:字符串和字符的比较不区分大小写;数字在比较时会做某种类型转换;如果每个元素都equalp,那么列表和数组就equalp;如果类型相同且所有槽都equalp,那么结构体就equalp;如果键和值都equalp,那么哈希表就equalp
例如:(equalp "foo" "FoO") ; => T
(equalp 3 3.0) ; => T
(equalp (make-a-structure :slot1 1 :slot2 2)
(make-a-structure :slot1 1 :slot2 2)) ; => T
Lisp 外星人 Leslie 正在给自己的迷宫机器人编程(年轻的 Lisp 外星人总爱干这种事),需要一些帮助。迷宫由一系列房间组成。每个房间都有若干扇门可供选择。每扇门都需要一把钥匙。如果在某扇门上用错了钥匙,那扇门很可能会爆炸(不用担心机器人,它不会受损,只是要把焦痕打磨掉,对 Leslie 来说相当烦人)。如果机器人没有能打开任何一扇门的钥匙,整个房间都会爆炸!(同样不用担心,不会造成损坏,只是清理起来很烦。)
你要帮 Leslie 做的,就是为每个房间给机器人正确的钥匙。机器人会按顺序把钥匙用在该房间的每一扇门上,并从第一扇打开的门穿过去。
每个测试代表一个迷宫,每个断言就是一个房间。你需要编写谓词函数,当参数相等时求值为T,不相等时求值为NIL。
这个迷宫只有一个房间。你需要提供一个函数,用来判断两个对象是否是同一个对象。
例如:
a ; => "pizza"
b ; => "pizza"
(key-object-identity a a) ; => T
(key-object-identity a b) ; => NIL
因为虽然a和b看起来一样,它们却不是同一个对象。
这个迷宫有两个房间。第一个房间里有一扇门,如果你的钥匙能判断两个数字是否严格相等,就可以打开它。第二个房间需要的钥匙,在判断数字是否相等时允许一定的灵活性。
例如:
a ; => 13
b ; => 13.0
c ; => 13
(key-numbers a b) ; => NIL
(key-numbers a c) ; => T
(key-numbers-of-different-types a b) ; => T
因为虽然a和b在数值上相同,它们的类型却不同。
这个迷宫有两个房间。第一个房间里有一扇门,如果你的钥匙能判断两个字符是否严格相等,就可以打开它。第二个房间需要的钥匙,要能对字符进行不区分大小写的比较。
例如:
a ; => #\X
b ; => #\x
c ; => #\X
(key-characters a b) ; => NIL
(key-characters a c) ; => T
(key-characters-case-insensitively a b) ; => T
因为只有非常宽松的相等谓词才会在比较两个字符时忽略大小写。
和字符迷宫一样,第一个房间需要的钥匙要能判断两个字符串是否相等;第二个房间需要的钥匙要能进行不区分大小写的比较。
例如:
a ; => "pizza"
b ; => "PIZZA"
c ; => "pizza"
(key-strings a b) ; => NIL
(key-strings a c) ; => T
(key-strings-case-insensitively a b) ; => T
因为只有非常宽松的相等谓词才会在比较两个字符串时忽略大小写。
这是一个有很多房间的大迷宫。每个房间需要的钥匙都要检查 cons 是否_包含_相等的元素。每个房间都会检查内容不同的 cons 是否相等:符号、数字、字符,其中一些房间还需要为这些内容使用更灵活的相等定义。
例如:
syms-a ; => (left . right)
syms-b ; => (up . down)
syms-c ; => (left . right)
chars-a ; => (#\x . #\y)
chars-b ; => (#\x . #\Y)
chars-c ; => (#\x . #\y)
nums-a ; => (13 . 23)
nums-b ; => (13 . 23.0)
nums-c ; => (13 . 23)
(key-conses-of-symbols syms-a syms-b) ; => NIL
(key-conses-of-symbols syms-a syms-c) ; => T
(key-conses-of-characters chars-a chars-b) ; => NIL
(key-conses-of-characters chars-a chars-c) ; => T
(key-conses-of-characters-case-insensitively
chars-a chars-b) ; => T
(key-conses-of-numbers nums-a nums-b) ; => NIL
(key-conses-of-numbers nums-a nums-c) ; => T
(key-conses-of-numbers-of-different-types
nums-a nums-b) ; => T
因为即使相等谓词会深入 cons 内部查看,它在比较时可能仍不够宽松,无法忽略大小写或数字类型。
这个迷宫更简单,只有两个房间。 第一个房间需要的钥匙要检查两个数组是否是同一个数组。 第二个房间需要的钥匙要检查数组的内容。
例如:
a ; => #[13 23]
b ; => #[13 23]
c ; => #[13 23.0]
(key-arrays a b) ; => NIL
(key-arrays a c) ; => NIL
(key-arrays-loosely a b) ; => T
(key-arrays-loosely a c) ; => T
因为只有非常宽松的相等检查器才会去检查数组的内容是否相等。