A melhor maneira de recuperar valores em listas associadas aninhadas?

11

Suponha que eu tenha uma lista assoc como esta:

(setq x '((foo . ((bar . "llama")
                  (baz . "monkey")))))

E eu quero o valor em bar. Eu posso fazer isso:

(assoc-default 'bar (assoc-default 'foo x))

Mas o que eu realmente gostaria é algo que aceite várias chaves, como

(assoc-multi-key 'foo 'bar x)

Existe algo assim, talvez em um pacote em algum lugar? Tenho certeza de que poderia escrever, mas sinto que meu Google-fu está falhando e não consigo encontrá-lo.

Abingham
fonte
FWIW, não vejo nenhuma lista aninhada nesta página. Só vejo alistas comuns e não aninhados. E não está claro qual comportamento você está procurando. Você não diz nada sobre o comportamento de assoc-multi-key. Presumivelmente, ele procura correspondências para os dois primeiros argumentos, mas isso é tudo o que se poderia supor, pelo que você disse. E claramente não pode aceitar mais de duas chaves, uma vez que o argumento alista (presumivelmente x) é o último, não o primeiro - o que sugere que não é muito útil em geral. Tente realmente especificar o que você está procurando.
Tirou
Também achei a formatação original do setqformulário no exemplo confusa, por isso editei-a para usar a notação de ponto comum para assoc-lists.
paprika
Ah ok. Portanto, o alist tem dois níveis. A questão ainda não está clara - assoc-multi-keypermanece não especificada.
Drew
11
Drew: O objetivo de assoc-multi-keyé procurar a primeira chave na lista assoc. Isso deve resolver para uma nova lista assoc na qual procuramos a próxima chave. E assim por diante. Basicamente, é uma abreviação para extrair valores de listas associadas aninhadas.
abingham
2
@ Malabarba Talvez você possa mencionar let-alisttambém? por exemplo (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar), retornará "llama". Eu acho que você escreveu let-alistdepois que a pergunta foi feita, mas está no espírito da pergunta e vale a pena mencionar a IMO!
YoungFrog

Respostas:

15

Aqui está uma opção que usa a sintaxe exata que você solicitou, mas de uma maneira generalizada, e é bastante simples de entender. A única diferença é que o ALISTparâmetro precisa vir primeiro (você pode adaptá-lo para vir em último, se isso for importante para você).

(defun assoc-recursive (alist &rest keys)
  "Recursively find KEYs in ALIST."
  (while keys
    (setq alist (cdr (assoc (pop keys) alist))))
  alist)

Então você pode chamá-lo com:

(assoc-recursive x 'foo 'bar)
Malabarba
fonte
2
Isso é mais ou menos o que eu havia preparado também. Estou um pouco surpreso que isso não faça parte de alguma biblioteca estabelecida, como dash ou algo assim. Parece surgir o tempo todo ao lidar com, por exemplo, dados json.
Abingham 7/11
2

Aqui está uma solução mais genérica:

(defun assoc-multi-key (path nested-alist)
   "Find element in nested alist by path."
   (if (equal nested-alist nil)
       (error "cannot lookup in empty list"))
   (let ((key (car path))
         (remainder (cdr path)))
     (if (equal remainder nil)
         (assoc key nested-alist)
       (assoc-multi-key remainder (assoc key nested-alist)))))

Pode pegar qualquer "caminho" de chaves. Isso retornará(bar . "llama")

(assoc-multi-key '(foo bar)
    '((foo (bar . "llama") (baz . "monkey"))))

considerando que isso retornará (baz . "monkey"):

(assoc-multi-key '(foo bar baz)
    '((foo (bar (bozo . "llama") (baz . "monkey")))))
rekado
fonte
3
Recebi meu primeiro voto negativo para esta resposta. Alguém quer me dizer por quê?
rekado
11
Não concordo com o voto negativo, pois seu código funciona (+1). Minha especulação é que a resposta de @ Malabarba é claramente mais geral / elegante do que as outras respostas oferecidas e, portanto, as outras respostas recebidas são negativas não porque não funcionam, mas porque não são as melhores. (Dito isto, eu prefiro o "upvote a melhor" opção em vez de "upvote o melhor e downvote os outros" alternativa.)
Dan
11
Essas duas perguntas foram rejeitadas porque há uma pessoa aqui que não entende muito bem como funcionam os votos negativos (e decide desconsiderar a solicitação da interface para deixar um comentário). É lamentável, mas o melhor que todos podemos fazer é votar.
Malabarba 7/11
0

Aqui está uma função simples que trabalha com um alist aninhado dentro de outro alist:

(defun assoc2 (outer inner alist)
  "`assoc', but for an assoc list inside an assoc list."
  (assoc inner (assoc outer alist)))

(setq alist2 '((puppies (tail . "waggly") (ears . "floppy"))
               (kitties (paws . "fuzzy")  (coat . "sleek"))))

(assoc2 'kitties 'coat alist2)       ;; => (coat . "sleek")
(cdr (assoc2 'kitties 'coat alist2)) ;; => "sleek"
Dan
fonte
3
Por favor, as pessoas, quando você votar, deixe um comentário.
Malabarba 7/11
11
Quem votou mal: não estou ofendido, mas estou curioso por quê. @ Malabara: agora existe uma meta thread sobre normas em " voto negativo + comentário"? ; Eu ficaria curioso em sua opinião.
Dan