Il modo migliore per recuperare valori in elenchi assoc nidificati?


11

Supponiamo di avere una lista di soci come questa:

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

E voglio il valore a bar. Posso farlo:

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

Ma quello che mi piacerebbe davvero è qualcosa che accetta più chiavi, come

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

Esiste una cosa del genere, forse in un pacchetto da qualche parte? Sono sicuro di poterlo scrivere, ma sento che il mio Google-Fu sta fallendo e non riesco a trovarlo.


FWIW, non vedo nessun elenco nidificato in questa pagina. Vedo solo elenchi ordinari e non previsti. E non è chiaro quale comportamento stai cercando. Non dici nulla sul comportamento di assoc-multi-key. Presumibilmente cerca corrispondenze con entrambi i suoi primi due argomenti, ma è davvero tutto ciò che si potrebbe supporre, da quello che hai detto. E chiaramente non può accettare più di due chiavi, poiché l'argomento di alist (presumibilmente x) è l'ultimo, non il primo - il che suggerisce che non sia troppo utile in generale. Prova a specificare cosa stai cercando.
Disegnò il

Ho anche trovato setqconfusa la formattazione originale del modulo nell'esempio, quindi l'ho modificata per usare la notazione comune per gli elenchi assoc.
paprika,

Ah ok. Quindi la lista ha due livelli. La domanda non è ancora chiara: assoc-multi-keyrimane non specificata.
Disegnò il

1
Drew: il punto assoc-multi-keyè cercare la prima chiave nell'elenco assoc. Questo dovrebbe risolversi in un nuovo elenco assoc in cui cerchiamo la chiave successiva. E così via. Fondamentalmente una scorciatoia per estrarre valori da elenchi assoc nidificati.
circa

2
@Malabarba Forse potresti let-alistanche menzionarlo ? ad es . (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar)tornerà "llama". Immagino tu abbia scritto let-alistdopo che la domanda è stata posta, ma è nello spirito della domanda e vale la pena menzionare l'IMO!
YoungFrog,

Risposte:


15

Ecco un'opzione che accetta l'esatta sintassi richiesta ma in modo generalizzato ed è abbastanza semplice da capire. L'unica differenza è che il ALISTparametro deve venire per primo (potresti adattarlo per diventare l'ultimo, se questo è importante per te).

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

Quindi puoi chiamarlo con:

(assoc-recursive x 'foo 'bar)

2
Questo è più o meno quello che avevo preparato anch'io. Sono un po 'sorpreso che questo non faccia parte di alcune librerie affermate come dash o qualcosa del genere. Sembra emergere continuamente quando si tratta, ad esempio, di dati json.
circa

2

Ecco una soluzione più generica:

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

Può richiedere qualsiasi "percorso" di chiavi. Questo tornerà(bar . "llama")

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

mentre ciò restituirà (baz . "monkey"):

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

3
Ho ottenuto il mio primo voto negativo per questa risposta. Qualcuno disposto a dirmi perché?
rekado,

1
Non sono d'accordo con il downvote poiché il tuo codice funziona (+1). La mia ipotesi è che la risposta di @Malabarba sia chiaramente più generale / elegante rispetto alle altre risposte offerte, e quindi le altre risposte hanno ricevuto i voti non perché non funzionano, ma perché non sono le migliori. (Detto questo, preferisco l'opzione "vota il migliore" piuttosto che "vota il migliore e declassa gli altri").
Dan

1
Queste due domande sono state sottovalutate perché c'è una persona qui che non capisce bene come funzionano i downvotes (e sceglie di ignorare la richiesta dell'interfaccia di lasciare un commento). È un peccato, ma il meglio che possiamo fare è migliorare.
Malabarba,

0

Ecco una semplice funzione che funziona con un elenco nidificato all'interno di un altro elenco:

(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
Per favore, la gente, quando voti in basso, lascia un commento.
Malabarba,

1
Chiunque abbia effettuato il downvoting: non sono offeso, ma sono curioso di sapere perché. @Malabara: ora c'è un meta thread sulle norme su "downvote + comment"? ; Sarei curioso di sapere cosa ne pensi.
Dan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.