¿La mejor manera de recuperar valores en listas de asociaciones anidadas?

11

Supongamos que tengo una lista asociada como esta:

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

Y quiero el valor en bar. Puedo hacer esto:

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

Pero lo que realmente me gustaría es algo que acepte varias claves, como

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

¿Existe tal cosa, tal vez en un paquete en alguna parte? Estoy seguro de que podría escribirlo, pero siento que mi Google-fu está fallando y no puedo encontrarlo.

abingham
fuente
FWIW, no veo ninguna lista anidada en esta página. Solo veo alistas ordinarios, sin anotar. Y no está claro qué comportamiento está buscando. No dices nada sobre el comportamiento de assoc-multi-key. Presumiblemente busca coincidencias con sus dos primeros argumentos, pero eso es realmente todo lo que uno podría suponer, por lo que ha dicho. Y claramente no puede aceptar más de dos claves, ya que el argumento alist (presumiblemente x) es el último, no el primero, lo que sugiere que no es demasiado útil en general. Intenta especificar lo que estás buscando.
Dibujó el
También encontré el formato original del setqformulario en el ejemplo confuso, así que lo edité para usar la notación de puntos común para las listas de asociaciones.
pimentón
Ah ok. Entonces la lista tiene dos niveles. La pregunta aún no está clara, assoc-multi-keysigue sin especificarse.
Dibujó
1
Drew: El punto assoc-multi-keyes buscar la primera clave en la lista de asociación. Esto debería resolver una nueva lista de asociación en la que buscamos la siguiente clave. Etcétera. Básicamente, una abreviatura para extraer valores de listas de asociaciones anidadas.
abingham
2
@Malabarba ¿Quizás podrías mencionarlo let-alisttambién? Por ejemplo (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar), volveremos "llama". ¡Supongo que escribiste let-alistdespués de que se hizo la pregunta, pero está en el espíritu de la pregunta y vale la pena mencionar a IMO!
YoungFrog

Respuestas:

15

Aquí hay una opción que toma la sintaxis exacta que solicitó pero de forma generalizada, y es bastante simple de entender. La única diferencia es que el ALISTparámetro debe ser el primero (puede adaptarlo para el último, si eso es importante para usted).

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

Entonces puedes llamarlo con:

(assoc-recursive x 'foo 'bar)
Malabarba
fuente
2
Esto es más o menos lo que yo había cocinado también. Estoy un poco sorprendido de que esto no sea parte de una biblioteca establecida como dash o algo así. Parece aparecer todo el tiempo cuando se trata, por ejemplo, de datos json.
abingham
2

Aquí hay una solución más 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)))))

Puede tomar cualquier "camino" de claves. Esto volverá(bar . "llama")

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

mientras que esto devolverá (baz . "monkey"):

(assoc-multi-key '(foo bar baz)
    '((foo (bar (bozo . "llama") (baz . "monkey")))))
rekado
fuente
3
Obtuve mi primer voto negativo para esta respuesta. ¿Alguien dispuesto a decirme por qué?
rekado
1
No estoy de acuerdo con el voto negativo ya que su código funciona (+1). Mi especulación es que la respuesta de @ Malabarba es claramente más general / elegante que las otras respuestas que se ofrecen, por lo que las otras respuestas recibieron votos negativos no porque no funcionen, sino porque no son las mejores. (Dicho esto, prefiero la opción "votar a los mejores" en lugar de "votar a los mejores y votar a los demás")
Dan
1
Estas dos preguntas fueron rechazadas porque hay una persona aquí que no comprende muy bien cómo funcionan los votos negativos (y elige ignorar la solicitud de la interfaz de dejar un comentario). Es lamentable, pero lo mejor que todos podemos hacer es votar.
Malabarba
0

Aquí hay una función simple que funciona con una lista anidada dentro de otra lista:

(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
fuente
3
Por favor, la gente, cuando rechace votar, deje un comentario.
Malabarba
1
Quien votó en contra: no estoy ofendido, pero tengo curiosidad por qué. @Malabara: ¿ahora hay un hilo meta en las normas sobre "voto negativo + comentario"? ; Tendría curiosidad por tu opinión.
Dan