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
因為只有非常寬鬆的相等檢查器,才會進一步檢查陣列的內容是否相等。