¿Informar duplicados en una lista?

7

P: ¿cómo obtengo solo los elementos duplicados en una lista?

delete-dups(y cl-delete-duplicates) elimina todos los elementos duplicados de una lista:

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

Quiero lo contrario: ¿hay una función que devuelva solo los duplicados en una lista?

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

Respuestas:

6

Creo que la forma más fácil es usar tablas 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
fuente
Fwiw hay una versión destacada de defun que acepta argumentos de palabras clave.
YoungFrog
6

Usando guión:

(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)))

Un conjunto de pruebas rápidas:

(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"))))

Actualmente parece devolver los elementos en el orden de la primera aparición de cada duplicado, pero no veo nada en -group-byeso que lo garantice, así que no creo que se pueda confiar en eso. Posiblemente podría ser más eficiente, usando tablas hash, pero esto funciona.

zck
fuente
3

Aquí hay una versión sin 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
fuente
0

Invertir delete-dupsusando ... delete-dups(y seq):

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

Esto es similar a la definición de @ caseneuve.

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

Pero ambos sufren de probar cada elemento de la lista, incluso si ya ha sido probado. Y luego corren delete-dups.

Esta definición es sencilla y no sufre de esas ineficiencias:

(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))

También parece ser aproximadamente 6 veces más rápido que la solución de tabla hash ( get-duplicatesarriba).

Dibujó
fuente