2

Being new with CL, I play a lot with simple algorithms. For instance, I tried to implement a function for removing all unique elements in a list.

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

First attempt lead to this code:

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

This works ok with strings but does always return NIL for numbers. Reading a bit more about set-difference I've learned that it isn't suppose to work with duplicate populated lists at all, it just works somehow in my case, so I abandoned the approach as unportable and moved along.

Another attempt is:

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

And this works ok with numbers, but returns NIL for strings.

Apparently there is a core difference between strings and numbers I don't understand. How come list processing functions such as member and set-difference work differently on them?

4

3 回答 3

2

数字、字符和字符串的相等比较确实是不同的。平等,你应该谨慎使用,因为它更昂贵,结构平等(所以它下降到某些对象)。eq 做对象相等。并且 eql 在大多数情况下都执行对象相等性,除了数字(它们检查类型和值)和字符(它们检查“值”)

有关更多信息,请参阅equaleqleq的 hyperspec 条目。

于 2013-08-14T06:49:15.213 回答
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
于 2013-08-13T09:52:41.760 回答
1

与数字相比,字符串与列表的相关性更高,因为列表和字符串都是序列。

"Hello"是一个序列(复合数据类型),以原始字符值开头,以 .#\H结尾#\o

'(1 2 3)是一个序列(复合数据类型),以原始数值 1 开始,以 3 结束。

字符类似于数字,因为它们是原始值。原始值可以使用eqlwhile 序列进行比较,它们不是同一个对象,可以使用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.

于 2013-08-13T22:10:15.500 回答