Assoc dans une liste imbriquée

Nov 19 2020

J'ai la liste imbriquée suivante:

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

Comment puis-je entrer (chocolate . "edible")?

J'ai lu cette question et et ceci

Mais contrairement à q1, je ne connais pas le "chemin" vers la valeur, et contrairement à q2, je voudrais une implémentation Elisp. De plus, j'ai une liste plus grande qui peut avoir une "profondeur" de 2 à 5 (par profondeur, je veux dire des listes dans les listes)

Jusqu'à présent, c'est ce que j'ai pu cuisiner:

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

Il est évident que ce code ne fonctionne que tant que la valeur n'est pas une liste d'alistes comme (bar . ((..))

Comment puis-je accéder à une valeur dans une liste imbriquée avec Vanilla Elisp (sans émulation CL)? Ou devrais-je abandonner et installer l'API CL et essayer q2?

La syntaxe que je recherche est quelque chose comme (func key list)

ps: Je suis assez nouveau sur Emacs, donc je manque probablement une fonction pratique.

Réponses

3 Basil Nov 19 2020 at 22:55

J'ai la liste imbriquée suivante:

L'exemple ne montre pas une vraie liste, car son premier élément,, foon'est pas une cellule contre. Je l'appellerais personnellement un arbre. Des fonctions comme assoc-stringpeuvent gérer cela, d'autres peuvent ignorer ces éléments, mais en général, les fonctions de liste s'attendent à ce que chaque élément soit un contre avec une voiture et un cdr. See (info "(elisp) Lists")et ses sous-nœuds.

Jusqu'à présent, c'est ce que j'ai pu cuisiner:

Elisp ne gère pas la récursivité de manière très efficace, je vous recommande donc de l'éviter en général, si possible. Vous pourriez autrement atteindre des max-specpdl-sizelimites.

contrairement à q1, je ne connais pas le "chemin" vers la valeur

De plus, j'ai une liste plus grande qui peut avoir une "profondeur" de 2 à 5 (par profondeur, je veux dire des listes dans les listes)

Compte tenu de l'irrégularité de cette structure de données, je recommanderais d'aplatir la liste avant d'y rechercher des éléments. Cela devrait grandement simplifier la complexité du code au prix d'un peu de temps et d'espace. Dans Emacs 27:

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

Voici l'implémentation actuelle de flatten-tree, au cas où vous seriez sur une ancienne version d'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)))

Vous pouvez également effectuer une recherche itérative dans l'arborescence en profondeur d'abord. Il échange les problèmes de récursivité d'Elisp contre du code plus complexe. Voici un exemple de DFS sur un DOM HTML tiré 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))

Dans votre cas, la whileboucle se terminerait en outre à l'emplacement de la clé souhaitée.

Sinon, je recommanderais de structurer vos données de manière à ce qu'elles soient plus régulières. ;)

xuchunyang Nov 20 2020 at 00:30

Vous pouvez utiliser la macro intégrée let-alistpour accéder à la valeur d'une liste imbriquée, par exemple,

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

Et votre xn'est pas une liste, une liste est une liste de paires clé-valeur, c'est-à-dire ((key1 . val1) (key2 . val2) ...).