2013-08-13 2 views
2

Будучи новым с CL, я много играю с простыми алгоритмами. Например, я попытался реализовать функцию для удаления всех уникальных элементов в списке.В чем основное отличие строк и чисел от Common Lisp?

(1 2 2 3 3 4 5 3) -> (2 2 3 3 3)

Первая попытка привести к этому коду:

(defun remove-unique (items) 
    (let ((duplicates (set-difference items (remove-duplicates items :test #'equal)))) 
    (append duplicates (remove-duplicates duplicates :test #'equal)))) 

Это хорошо работает со строками но всегда возвращает NIL для чисел. Чтение немного больше о set-difference Я узнал, что не следует работать с дублирующимися заполненными списками вообще, это как-то работает в моем случае, поэтому я отказался от подхода как неспортивного и двинулся вперед.

Другая попытка:

(defun remove-unique (items) 
    (loop for item in items 
    when (member item (cdr (member item items))) 
    collect item)) 

И это хорошо работает с цифрами, но возвращает NIL для строк.

По-видимому, существует основное различие между строками и числами, которые я не понимаю. Почему функции обработки списка, такие как member и set-difference, работают по-другому?

+1

'member' также принимает предикат, который указывает, какое сравнение вы хотите применить к элементам списка. –

+0

Спасибо! Я действительно забыл об этом. Но это все равно 'NIL' для строк. – akalenuk

+0

Это действительно пропустил 'test', я просто накормил его списком без дубликатов при тестировании во второй раз :-) Неудивительно, что это дало мне ноль. – akalenuk

ответ

1

Строки в большей степени связаны со списками, чем числа, так как списки и строки последовательности.

"Hello" представляет собой последовательность (составной тип данных), начинающуюся с примитивного символьного значения #\H и заканчивающегося #\o.

'(1 2 3) представляет собой последовательность (compond типа данных), начиная с примитивным числовым значением 1 и заканчивая 3.

Символов подобны номерам в том, что они являются примитивными значениями. Примитивные значения могут быть сравнены с использованием eql в то время как последовательности, которые не являются и тот же объект, можно сравнить с использованием equal

(setq list1 (list 1 2 3)) 
(setq list2 (list 1 2 3)) 

(eql list1 list2) 
;==> NIL 

(equal list1 list2) 
;==> T 

;; comparing first element of both lists using eql 
(eql (car list1) (car list2)) 
;==> T 

(setq string1 "Hello") 
(setq string2 "Hello") 

(eql string1 string2) 
;==> NIL 

(equal string1 string2) 
;==> T 

;; comparing first character of both strings using eql 
(eql (elt string1 0) (elt string2 0)) 
;==> T 

Большинство (если не все) функции в Common Lisp, который сравнивает то, как правило, имеет необязательный именованный аргумент :test где вы можете указать, как сравнивать элементы. по умолчанию обычно eql. Чтобы заставить их вести себя правильно с последовательностями, вам необходимо поставить #'equal как :test.

1
(defun remove-unique (items &key (test 'eql)) 
    (loop 
    :with table := (make-hash-table :test test) 
    :for element :in items :do 
    (setf (gethash element table) 
      (1+ (gethash element table 0))) 
    :finally 
    (return 
     (loop 
      :for k :being :the :hash-keys :of table 
      :using (:hash-value v) 
      :when (> v 1) :nconc (make-list v :initial-element k))))) 

(defun remove-unique (items &key (test 'eql)) 
    (loop 
    :with table := (make-hash-table :test test) 
    :for element :in items :do 
    (setf (gethash element table) 
      (1+ (gethash element table 0))) 
    :finally 
    (return 
     (loop 
      :for element :in items 
      :unless (= 1 (gethash element table)) 
      :collect element)))) 

Я бы, вероятно, использовать первый вариант, поскольку он делает меньше читает из хэш-таблицы, но вы должны проверить, что элементы списка не изменяются позже на месте.

(remove-unique '("1" "2" "2" "3" "3" "4" "5" "3") :test #'equal) 

дает:

("2" "2" "3" "3" "3") 

но

(remove-unique '("1" "2" "2" "3" "3" "4" "5" "3")) 

дает:

NIL 
2

Сравнение равенства чисел, символов и строк действительно отличается.Равный, который вы должны осторожно использовать, потому что он дороже, имеет структурное равенство (поэтому он опускается на некоторые объекты). eq делает объектное равенство. И EQL делает объект равенства для большинства случаев для чисел, за исключением (когда они проверяют тип и значение) и символов (где они проверяют значение «»)

Посмотреть HyperSpec записи для equal, eql и eq для получения дополнительной информации.

Смежные вопросы