У 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; два рядки тексту (англ. string) є equal, якщо кожен елемент є eql; два масиви є equal, якщо вони eq; будь-що інше є equal, якщо воно eql.
наприклад:(equal (list 1 2) (list 1 2)) ; => T
(equal #(1 2) #(1 2)) ; => NIL
(equal "foo" "foo") ; => T
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, програмує свого робота для лабіринту (як і заведено в молодих прибульців із Lisp) і потребує невеликої допомоги. Лабіринт складається з низки кімнат. У кожній кімнаті на вибір є кілька дверей. Кожні двері потребують ключа. Якщо ми використаємо неправильний ключ, двері, найімовірніше, вибухнуть (за робота не хвилюймося: шкоди не буде, але відполіровувати сліди від опіків Леслі буде досить прикро). Якщо в робота не буде ключа, який відчиняє хоч якісь двері, то вся кімната вибухне! (Знову ж таки, не хвилюймося: шкоди це не завдасть, але прибирати буде досить прикро.)
Щоб допомогти Леслі, нам треба дати роботу правильний ключ для кожної кімнати. Робот використає ключ на кожних дверях у кімнаті по черзі й пройде через перші двері, які відчиняться.
Кожен тест представляє лабіринт, а кожне твердження - кімнату. Нам потрібно буде написати функції-предикати, які дають 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 із різним вмістом: символів Lisp, чисел, символів, а деяким знадобляться гнучкіші визначення рівності для цього вмісту.
Наприклад:
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
адже лише дуже поблажливі перевірки на рівність зазирають у вміст масиву.
Зареєструйтеся на Exercism, щоб вивчати й опановувати Common Lisp, а також 27 концепцій92 вправи та справжнє наставництво від людей, і все це безкоштовно.