Assoc em uma lista aninhada
Eu tenho a seguinte lista aninhada:
(setq x '(foo . ((bar . ((chocolate . "edible") (gold . "inedible")))
(jar . "glass"))))
Como posso obter a entrada (chocolate . "edible")?
Eu li esta pergunta e esta
Mas, ao contrário de q1, não sei o "caminho" para o valor e, ao contrário de q2, gostaria de uma implementação Elisp. Além disso, tenho uma lista maior, que pode ter uma "profundidade" de 2 a 5 (por profundidade, quero dizer listas em listas)
Até agora, isso é o que eu fui capaz de preparar:
(defun assoc-recur (key list)
(if (listp (cdr list))
(assoc key (cdr list))
(assoc-reccur key (cdr list))))
É óbvio que este código só funciona enquanto o valor não for uma lista de listas como (bar . ((..))
Como posso acessar um valor em um alist aninhado com vanilla Elisp (sem emulação CL)? Ou devo desistir e instalar a API CL e tentar q2?
A sintaxe que procuro é algo como (func key list)
ps: Eu sou muito novo no Emacs, então provavelmente estou perdendo uma função conveniente.
Respostas
Eu tenho a seguinte lista aninhada:
O exemplo não mostra uma lista real, porque seu primeiro elemento,, foonão é uma célula cons. Eu pessoalmente chamaria de árvore. Funções como assoc-stringpodem lidar com isso, outras podem ignorar tais elementos, mas em geral funções alist esperam que cada elemento seja um contras com um carro e um cdr. Veja (info "(elisp) Lists")e seus subnós.
Até agora, isso é o que eu fui capaz de preparar:
Elisp não lida com recursão de forma muito eficiente, então eu recomendo evitá-la em geral, se possível. Caso contrário, você pode atingir os max-specpdl-sizelimites.
ao contrário de q1, não sei o "caminho" para o valor
Além disso, tenho uma lista maior, que pode ter uma "profundidade" de 2 a 5 (por profundidade, quero dizer listas em listas)
Dada a irregularidade dessa estrutura de dados, eu recomendo nivelar a lista primeiro, antes de pesquisar as coisas nela. Isso deve simplificar muito a complexidade do código ao custo de algum tempo e espaço. No Emacs 27:
(setq x '(foo
(bar (chocolate . "edible")
(gold . "inedible"))
(jar . "glass")))
(cadr (memq 'chocolate (flatten-tree x))) ; => "edible"
Esta é a implementação atual do flatten-tree, caso você esteja em uma versão mais antiga do 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)))
Como alternativa, você pode realizar uma pesquisa iterativa em árvore em profundidade. Ele troca os problemas de recursão do Elisp por um código mais complexo. Aqui está um exemplo de DFS em um HTML DOM obtido 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))
No seu caso, o whileloop também terminaria no local da chave desejada.
Como alternativa, recomendo estruturar seus dados de forma que sejam mais regulares. ;)
Você pode usar a macro let-alistembutida para acessar o valor de uma lista aninhada, por exemplo,
(let-alist
'((foo . ((bar . ((chocolate . "edible") (gold . "inedible")))
(jar . "glass"))))
.foo.bar.chocolate)
;; => "edible"
E seu xnão é um alist, alist é uma lista de pares de valores-chave, ou seja ((key1 . val1) (key2 . val2) ...),.