Assoc en una lista anidada

Nov 19 2020

Tengo la siguiente lista anidada:

(setq x '(foo . ((bar . ((chocolate . "edible") (gold . "inedible")))
                 (jar . "glass"))))

¿Cómo puedo entrar (chocolate . "edible")?

Leí esta pregunta y esta

Pero a diferencia de q1, no conozco la "ruta" al valor y, a diferencia de q2, me gustaría una implementación de Elisp. Además, tengo una lista más grande que puede tener una "profundidad" de 2 a 5 (por profundidad me refiero a alistas en alistas)

Hasta ahora esto es lo que pude cocinar:

(defun assoc-recur (key list)
  (if (listp (cdr list))
      (assoc key (cdr list))
    (assoc-reccur key (cdr list))))

Es obvio que este código solo funciona siempre que el valor no sea una lista de listas como (bar . ((..))

¿Cómo puedo acceder a un valor en una lista anidada con vanilla Elisp (sin emulación CL)? ¿O debería renunciar e instalar la API CL y probar q2?

La sintaxis que estoy buscando es algo como (func key list)

PD: Soy bastante nuevo en Emacs, así que probablemente me esté perdiendo una función conveniente.

Respuestas

3 Basil Nov 19 2020 at 22:55

Tengo la siguiente lista anidada:

El ejemplo no muestra una lista real, porque su primer elemento, foono es una celda de cons. Yo personalmente lo llamaría árbol. Funciones como assoc-stringpueden manejar esto, otras pueden ignorar tales elementos, pero en general, las funciones de lista esperan que cada elemento sea una desventaja con un automóvil y un cdr. Ver (info "(elisp) Lists")y sus subnodos.

Hasta ahora esto es lo que pude cocinar:

Elisp no maneja la recursividad de manera muy eficiente, por lo que recomendaría evitarla en general, si es posible. De lo contrario, puede alcanzar los max-specpdl-sizelímites.

a diferencia de q1, no conozco la "ruta" al valor

Además, tengo una lista más grande que puede tener una "profundidad" de 2 a 5 (por profundidad me refiero a alistas en alistas)

Dada la irregularidad de esta estructura de datos, recomendaría aplanar la lista antes de buscar cosas en ella. Esto debería simplificar enormemente la complejidad del código a costa de algo de tiempo y espacio. En Emacs 27:

(setq x '(foo
          (bar (chocolate . "edible")
               (gold . "inedible"))
          (jar . "glass")))
(cadr (memq 'chocolate (flatten-tree x))) ; => "edible"

Aquí está la implementación actual de flatten-tree, en caso de que esté en una versión anterior de Emacs:

(defun flatten-tree (tree)
  "Return a \"flattened\" copy of TREE.
In other words, return a list of the non-nil terminal nodes, or
leaves, of the tree of cons cells rooted at TREE.  Leaves in the
returned list are in the same order as in TREE.

\(flatten-tree \\='(1 (2 . 3) nil (4 5 (6)) 7))
=> (1 2 3 4 5 6 7)"
  (let (elems)
    (while (consp tree)
      (let ((elem (pop tree)))
        (while (consp elem)
          (push (cdr elem) tree)
          (setq elem (car elem)))
        (if elem (push elem elems))))
    (if tree (push tree elems))
    (nreverse elems)))

Alternativamente, puede realizar una búsqueda iterativa de árbol en profundidad. Cambia los problemas de recursividad de Elisp por un código más complejo. Aquí hay un ejemplo de DFS en un DOM HTML tomado dehttps://github.com/abo-abo/swiper/pull/1593#issuecomment-392587760 :

(defun counsel--firefox-bookmarks-libxml ()
  "Parse current buffer contents as Firefox HTML bookmarks.
Return list of propertized string candidates for
`counsel-firefox-bookmarks'.
Note: This function requires libxml2 support."
  ;; Perform iterative pre-order depth-first search instead of using
  ;; `dom.el' because the latter is new to Emacs 25 and uses recursion.
  (let ((stack (cddr (libxml-parse-html-region (point-min) (point-max))))
        cands)
    (while (let ((node (pop stack)))
             (if (eq (car-safe node) 'a)
                 (let* ((text (cl-caddr node))
                        (attrs (cadr node))
                        (href (cdr (assq 'href attrs)))
                        (tags (cdr (assq 'tags attrs))))
                   (unless (zerop (length href))
                     (push (counsel--firefox-bookmarks-cand href text tags)
                           cands)))
               (dolist (child (nreverse (cddr node)))
                 (when (consp child)
                   (push child stack))))
             stack))
    cands))

En su caso, el whileciclo terminaría adicionalmente en la ubicación de la clave deseada.

Alternativamente, recomendaría estructurar sus datos de manera que sean más regulares. ;)

xuchunyang Nov 20 2020 at 00:30

Puede usar la macro incorporada let-alistpara acceder al valor de una lista anidada, por ejemplo,

(let-alist
    '((foo . ((bar . ((chocolate . "edible") (gold . "inedible")))
              (jar . "glass"))))
  .foo.bar.chocolate)
;; => "edible"

Y su xno es una alista, a-lista es una lista de pares clave-valor, es decir, ((key1 . val1) (key2 . val2) ...).