用于检查列表是否包含重复项的谓词

时间:2016-01-31 04:04:40

标签: list lisp common-lisp clisp

我正在尝试编写一个接受列表的函数,如果它包含重复的条目则返回true,否则返回false。我知道我应该使用会员。这是我到目前为止的尝试(失败):

(defun dupl (lst)
  (if (null lst) '())
  (if ((member (car lst) (cdr lst)) (cons (car lst) (dupes (cdr lst))))
    (t (dupl (cdr lst)))))

4 个答案:

答案 0 :(得分:4)

您的代码中存在一些问题。

  • 第一个if应使用return-from实际返回值。最好使用nil代替'()
  • 在第二个if中,您尝试使用cond语法。
  • 我甚至不确定您使用cons尝试实现的目标,但这似乎并不合适。

修复后,您的代码将如下所示:

(defun dupl (lst)
  (if (null lst) (return-from dupl nil))
  (if (member (car lst) (cdr lst)) 
      t
      (dupl (cdr lst))))

将两个if转换为单个cond可能会更清晰:

(defun dupl (lst)
  (cond ((null lst) nil)
        ((member (car lst) (cdr lst)) t)
        (t (dupl (cdr lst)))))

答案 1 :(得分:3)

如果函数返回一个布尔值,它很可能表达为布尔表达式。以下二次版本是一种可能的实现:

(defun duplicatesp (list &key (test #'eql))
  (and list
       (or (member (first list) (rest list) :test test)
           (duplicatesp (rest list) :test test))))

随后的懒惰程序员版本也可以解决问题:

(defun duplicatesp (list)
  (not (equal (remove-duplicates list) list)))

您还可以先对列表的副本进行排序,以获得更好的O(n.log(n))时间复杂度。

答案 2 :(得分:2)

Common Lisp中的许多函数使用generalized booleans,根据 nil (空列表) false ,其他一切都是 true < / EM>:

  

Type BOOLEAN

     

...条件运算,如if,允许使用广义   布尔,而不仅仅是布尔;任何非零值,而不仅仅是t,都算作   对于广义布尔值,则为true。但是,作为惯例,   符号t被认为是即使对于a使用的规范值   当没有更好的选择呈现时,通用布尔值。

注意使用 t 的注释&#34;当没有更好的选择出现时。&#34;使返回通用布尔值的函数返回一些其他有用信息作为真值通常很有帮助。例如,member返回列表的尾部,其第一个元素是要检查成员资格的元素。在这种情况下,返回将重复元素映射到它们在列表中出现的次数的关联列表可能很有用。

这是一种做到这一点的方法。它首先遍历列表,构建列表中唯一(根据test and key arguments)元素的哈希表,将每个元素映射到它出现的次数。然后,传递哈希表用于构建出现多次的所有元素的关联列表。

(defun contains-duplicates (list &key (test 'eql) (key 'identity))
  "Returns an association list mapping duplicated elements to the
number of times that they appear in LIST.  TEST is a comparison
operator used to determine whether two elements are the same, and must
be acceptable as a test argument to MAKE-HASH-TABLE.  KEY is used to
extract a value from the elements of LIST, and the extracted values
are compared and returned in the result."
  (let ((table (make-hash-table :test test))
        (result '()))
    (dolist (x list)
      (incf (gethash (funcall key x) table 0)))
    (maphash #'(lambda (key count)
                 (unless (eql 1 count)
                   (push (cons key count) result)))
             table)
    result))
(contains-duplicates '(1 1 2 3 4 4 4))
;;=> ((4 . 3) (1 . 2))

(contains-duplicates '(1 2 3 4)) ; no duplicates 
;;=> NIL

(contains-duplicates '("A" "a" b a) :test 'equal :key 'string)
;;=> (("A" . 2))

(contains-duplicates '("A" "a" b a) :test 'equal :key 'string) ; "A" ~ a, but not "a"
;;=> (("A" . 2))

(contains-duplicates '("A" "a" b a) :test 'equalp :key 'string) ; "A" ~ "a" ~ a
;;=> (("A" . 3))

(contains-duplicates '(1 2 3 5) :key 'evenp) ; two even elements
;;=> ((NIL . 2))  

答案 3 :(得分:1)

效率只是我的两分钱。如果使用memberp来测试重复项,那么您将每个元素与其他元素进行比较,复杂度为O(N ^ 2)。 Joshua在他的回答中建议使用哈希表来测试重复项,这将以空间为代价给出线性运行时间O(N)。对于较小的列表,它可能也会较慢。最后,如果你的列表可以排序,那么你应该得到O(N.log(N))作为coredump- mentions。以下是使用sort测试数字列表中重复项的示例。 (这是一种破坏性的功能。)

(defun duplicatesp (list)
  (mapl (lambda (cdr) (if (eql (first cdr) (second cdr))
                          (return-from duplicatesp T)))
   (sort list '<)) nil)

<强>更新

出于好奇,我测量了最坏情况下建议答案的表现(几乎没有重复)。所以,1万个尝试的10个元素的列表:

  • 使用member(Jan的回答):1.139秒;
  • 使用hash-table(约书亚的回答):1.436秒;
  • 使用sort(见上文,但首先复制列表):1.484秒。

所以,与小名单没有区别。有趣的是,使用哈希表有一些惩罚,但它非常小。让我们尝试1000次尝试1000个元素的列表:

  • 使用member:9.968秒;
  • 使用hash-table:0.234秒;
  • 使用sort:0.390秒。

正如预期的那样,使用member具有更高的复杂性。排序和散列之间的差异在此列表大小不可见。让我们对1,000,000个元素的列表进行10次尝试:

  • 使用hash-table:3.214秒;
  • 使用sort:9.296秒。

所以,sort仍然相当不错,但速度正在减慢。这是我用来分析函数的简单代码:

(defun random-list (length)
  (loop for i from 0 below length collecting
       (random (expt 10 10))))

(defun random-collection (length tries)
  (loop for i from 0 below tries collecting
       (random-list length)))

(defun test-duplicates (function &key length tries)
  (let ((rc (random-collection length tries)))
    (time (mapc function rc))
    nil))

(test-duplicates #'dp_hash :length 1000000 :tries 10)
;etc.