Relatar duplicatas em uma lista?

7

P: como obtenho apenas os elementos duplicados em uma lista?

delete-dups(e cl-delete-duplicates) exclui todos os elementos duplicados de uma lista:

(delete-dups '(a b c c d d))            ; => '(a b c d)

Eu quero o inverso: existe uma função que retorna apenas as duplicatas em uma lista?

(mystery-function '(a b c c d d))       ; => '(c d)
Dan
fonte

Respostas:

6

Eu acho que a maneira mais fácil é usar tabelas de hash :

(defun get-duplicates (list &optional test)
  (let ((ht (make-hash-table :test (or test #'equal))) 
        ret)
    (dolist (x list)
      (incf (gethash x ht 0)))
    (maphash (lambda (key value)
               (when (> value 1)
                 (push key ret)))
             ht)
    ret))
(get-duplicates '(a 2 a b 3 2))
==> (2 a)
sds
fonte
Há uma versão estrelada do defun que aceita argumentos de palavras-chave.
YoungFrog
6

Usando traço:

(defun find-duplicates (list)
  "Return a list that contains each element from LIST that occurs more than once."
  (--> list
       (-group-by #'identity it)
       (-filter (lambda (ele) (> (length ele) 2)) it)
       (mapcar #'car it)))

Um conjunto de testes rápidos:

(ert-deftest nothing ()
  (should-not (find-duplicates '())))

(ert-deftest no-duplicates ()
  (should-not (find-duplicates '(1 2 3 4 5 6 7 "eight"))))

(ert-deftest single-duplicate ()
  (should (equal (find-duplicates '(1 2 3 4 1))
                 '(1))))

(ert-deftest multiple-duplicates ()
  (should (equal (sort (find-duplicates '(1 2 3 4 1 6 7 8 9 2))
                       #'<)
                 '(1 2))))

(ert-deftest string-duplicates ()
  (should (equal (find-duplicates '(1 2 "three" 4 "three"))
                 '("three"))))

Atualmente, parece retornar os itens na ordem da primeira ocorrência de cada duplicata, mas não vejo nada -group-byque garanta isso; portanto, acho que não podemos confiar nisso. Poderia ser mais eficiente, usando hashtables, mas isso funciona.

zck
fonte
3

Aqui está uma versão sem hash:

#+BEGIN_SRC emacs-lisp
(defun find-duplicates (list)
  (loop for (item . count) in (let ((counts '())
                    place)
                (dolist (el list)
                  (setq place (assoc el counts))
                  (if place
                      (incf (cdr place))
                    (push (cons el 1) counts)))
                counts)
    if (> count 1)
    collect item))
#+END_SRC
John Kitchin
fonte
0

Reversão delete-dupsusando ... delete-dups(e seq):

(defun report-dups (list)
  (delete-dups (seq-filter
                (lambda (el) (member el (cdr (member el list))))
                list)))
caseneuve
fonte
0

Isso é semelhante à definição de @ caseneuve.

(defun report-dups (xs)
  (delete-dups (cl-remove-if-not (lambda (x) (member x (cdr (member x xs)))) xs)))

Mas ambos sofrem ao testar cada elemento da lista, mesmo que já tenha sido testado. E então eles correm delete-dups.

Essa definição é direta e não sofre dessas ineficiências:

(defun report-dups (xs)
  (let ((ys  ()))
    (while xs
      (unless (member (car xs) ys) ; Don't check it if already known to be a dup.
        (when (member (car xs) (cdr xs)) (push (car xs) ys)))
      (setq xs  (cdr xs)))
    ys))

Também parece ser cerca de 6 vezes mais rápido que a solução de tabela de hash ( get-duplicates, acima).

Desenhou
fonte