2

I have the following nested alist:

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

How can get entry (chocolate . "edible")?

I read this question & and this

But unlike q1, I don't know the "path" to the value, and unlike, q2, I would like an Elisp implementation. Also, I have a larger a list which can have a "depth" of 2 - 5 (by depth I mean alists in alists)

So far this is what I was able to cook up:

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

It's obvious that this code only works as long as the value is isn't a list of alists like (bar . ((..))

How can can I access a value in a nested alist with vanilla Elisp (without CL emulation)? Or should I give up and install the CL api and try q2?

The syntax I am looking for is something like (func key list)

ps: I am quite new to Emacs so I am probably missing out on a convenient function.

3

I have the following nested alist:

The example does not show a real alist, because its first element, foo, is not a cons cell. I'd personally call it a tree. Functions like assoc-string may handle this, others may ignore such elements, but in general alist functions expect every element to be a cons with a car and a cdr. See (info "(elisp) Lists") and its subnodes.

So far this is what I was able to cook up:

Elisp doesn't handle recursion very efficiently, so I'd recommend avoiding it in general, if possible. You may otherwise hit max-specpdl-size limits.

unlike q1, I don't know the "path" to the value

Also, I have a larger a list which can have a "depth" of 2 - 5 (by depth I mean alists in alists)

Given the irregularity of this data structure, I'd recommend flattening the list first before looking things up in it. This should greatly simplify the code's complexity at the cost of some time and space. In Emacs 27:

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

Here's the current implementation of flatten-tree, in case you're on an older version of 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)))

Alternatively, you could perform an iterative depth-first tree search. It trades Elisp's recursion issues for more complex code. Here's an example of DFS on a HTML DOM taken from https://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))

In your case the while loop would additionally terminate on location of the desired key.

Alternatively, I'd recommend structuring your data such that it is more regular. ;)

2
  • +1 for all of it, but particularly for the last recommendation :-) – NickD Nov 19 '20 at 18:48
  • Thank you. I was thinking of doing something similar to flatten-tree but didn't know how to implement it – Benjamin Philip Nov 20 '20 at 3:03
0

You can use the builtin macro let-alist to access value from nested alist, e.g.,

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

And your x is not an alist, alist is a list of key-value pairs, i.e., ((key1 . val1) (key2 . val2) ...).

2
  • Using let-alist implies you know the path to the nested car, but the OP explicitly says the path is not known ahead of time. – Basil Nov 19 '20 at 18:31
  • @Basil Oh, I didn't read the question carefully. – xuchunyang Nov 20 '20 at 4:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.