作为CL的新手,我用简单的算法玩了很多。例如,我尝试实现一个删除列表中所有唯一元素的函数。
(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
对它们的工作方式不同?
答案 0 :(得分:2)
数字,字符和字符串的相等比较确实不同。相等,你应该谨慎使用,因为它更昂贵,结构相等(所以它下降到一些对象)。 eq确实是对象平等。对于大多数情况,eql会对象进行对等,除了数字(它们检查类型和值)和字符(它们检查'value')
答案 1 :(得分: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 :(得分:1)
字符串与列表更相关,而不是数字,因为列表和字符串都是序列。
"Hello"
是一个序列(compund数据类型),以原始字符值#\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
提供。