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.