Le meilleur moyen de récupérer des valeurs dans des listes d'associations imbriquées?


11

Supposons que j'ai une liste d'associations comme celle-ci:

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

Et je veux la valeur à bar. Je peux le faire:

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

Mais ce que j'aimerais vraiment, c'est quelque chose qui accepte plusieurs clés, comme

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

Une telle chose existe-t-elle, peut-être dans un paquet quelque part? Je suis sûr que je pourrais l'écrire, mais j'ai l'impression que mon Google-fu échoue et que je ne le trouve pas.


FWIW, je ne vois aucune liste imbriquée sur cette page. Je ne vois que des listes ordinaires, non imbriquées. Et le comportement que vous recherchez n'est pas clair. Vous ne dites rien du comportement de assoc-multi-key. Vraisemblablement, il recherche des correspondances avec ses deux premiers arguments, mais c'est vraiment tout ce que l'on peut supposer, d'après ce que vous avez dit. Et il ne peut clairement pas accepter plus de deux clés, puisque l'argument alist (vraisemblablement x) est le dernier, pas le premier - ce qui suggère qu'il n'est pas trop utile en général. Essayez de spécifier ce que vous recherchez.
Drew

J'ai également trouvé la mise en forme originale du setqformulaire dans l'exemple confus, donc je l'ai édité pour utiliser la notation par points commune pour les listes d'associations.
paprika

Ah ok. Donc, l'aliste a deux niveaux. La question n'est toujours pas claire - assoc-multi-keyreste non spécifiée.
Drew

1
Drew: Le but assoc-multi-keyest de rechercher la première clé de la liste d'associations. Cela devrait résoudre une nouvelle liste d'associations dans laquelle nous recherchons la clé suivante. Et ainsi de suite. Fondamentalement, un raccourci pour creuser des valeurs dans des listes d'associations imbriquées.
abingham

2
@Malabarba Peut-être pourriez-vous let-alistaussi le mentionner ? par exemple (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar)reviendra "llama". Je suppose que vous avez écrit let-alistaprès que la question a été posée, mais c'est dans l'esprit de la question et ça vaut vraiment la peine de mentionner IMO!
YoungFrog

Réponses:


15

Voici une option qui prend la syntaxe exacte que vous avez demandée mais de manière généralisée et qui est assez simple à comprendre. La seule différence est que le ALISTparamètre doit venir en premier (vous pouvez l'adapter pour qu'il arrive en dernier, si cela est important pour vous).

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

Ensuite, vous pouvez l'appeler avec:

(assoc-recursive x 'foo 'bar)

2
C'est plus ou moins ce que j'avais préparé. Je suis un peu surpris que cela ne fasse pas partie d'une bibliothèque établie comme dash ou quelque chose. Il semble apparaître tout le temps lorsqu'il s'agit par exemple de données json.
abingham

2

Voici une solution plus générique:

(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)))))

Il peut prendre n'importe quel "chemin" de clés. Cela reviendra(bar . "llama")

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

alors que cela reviendra (baz . "monkey"):

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

3
J'ai eu mon premier downvote pour cette réponse. Quelqu'un veut-il me dire pourquoi?
rekado

1
Je ne suis pas d'accord avec le downvote car votre code fonctionne (+1). Ma spéculation est que la réponse de @ Malabarba est clairement plus générale / élégante que les autres réponses proposées, et donc les autres réponses ont reçu des votes négatifs non pas parce qu'elles ne fonctionnent pas, mais parce qu'elles ne sont pas les meilleures. (Cela étant dit, je préfère l'option "voter positivement le meilleur" plutôt que l'option "voter positivement le meilleur et voter contre les autres".)
Dan

1
Ces deux questions ont été rejetées parce qu'il y a une personne ici qui ne comprend pas très bien comment fonctionnent les votes descendants (et choisit de ne pas tenir compte de la demande de l'interface de laisser un commentaire). C'est malheureux, mais le mieux que nous puissions tous faire est de voter.
Malabarba du

0

Voici une fonction simple qui fonctionne avec une liste imbriquée dans une autre liste:

(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"

3
S'il vous plaît les gens, lorsque vous votez, laissez un commentaire.
Malabarba

1
Quiconque a voté contre: Je ne suis pas offensé, mais je suis curieux de savoir pourquoi. @Malabara: il y a maintenant un méta-fil sur les normes sur "downvote + comment"? ; Je serais curieux de savoir ce que vous en pensez.
Dan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.