Der beste Weg, um Werte in verschachtelten Assoc-Listen abzurufen?

11

Angenommen, ich habe eine Assoc-Liste wie diese:

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

Und ich möchte den Wert bei bar. Ich kann dies tun:

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

Aber was ich wirklich möchte, ist etwas, das mehrere Schlüssel akzeptiert, wie z

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

Existiert so etwas vielleicht in einem Paket irgendwo? Ich bin mir sicher, dass ich es schreiben könnte, aber ich habe das Gefühl, dass mein Google-Fu einfach ausfällt und ich es nicht finden kann.

Abingham
quelle
FWIW, ich sehe keine verschachtelten Listen auf dieser Seite. Ich sehe nur gewöhnliche, nicht verschachtelte Alisten. Und es ist nicht klar, nach welchem ​​Verhalten Sie suchen. Sie sagen nichts über das Verhalten von assoc-multi-key. Vermutlich sucht es nach Übereinstimmungen mit den beiden ersten Argumenten, aber das ist wirklich alles, was man von dem, was Sie gesagt haben, annehmen könnte. Und es kann eindeutig nicht mehr als zwei Schlüssel akzeptieren, da das alistische Argument (vermutlich x) das letzte und nicht das erste ist - was darauf hindeutet, dass es im Allgemeinen nicht allzu nützlich ist. Versuchen Sie tatsächlich anzugeben, wonach Sie suchen.
Drew
Ich fand auch die ursprüngliche Formatierung des setqFormulars im Beispiel verwirrend, also habe ich es bearbeitet, um die allgemeine Punktnotation für Assoc-Listen zu verwenden.
Paprika
Ah, OK. Der Alist hat also zwei Ebenen. Die Frage ist noch unklar - assoc-multi-keybleibt unbestimmt.
Drew
1
Drew: Es assoc-multi-keygeht darum, den ersten Schlüssel in der Assoc-Liste nachzuschlagen. Dies sollte sich in eine neue Assoc-Liste auflösen, in der wir den nächsten Schlüssel nachschlagen. Und so weiter. Grundsätzlich eine Abkürzung zum Ausgraben von Werten aus verschachtelten Assoc-Listen.
Abingham
2
@ Malabarba Vielleicht könntest du auch erwähnen let-alist? zB (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar)wird zurückkehren "llama". Ich denke, Sie haben geschrieben, let-alistnachdem die Frage gestellt wurde, aber es ist im Geiste der Frage und sehr erwähnenswert, IMO!
YoungFrog

Antworten:

14

Hier ist eine Option, die genau die Syntax verwendet, nach der Sie gefragt haben, aber verallgemeinert und recht einfach zu verstehen ist. Der einzige Unterschied besteht darin, dass der ALISTParameter an erster Stelle stehen muss (Sie können ihn an die letzte Stelle anpassen, wenn dies für Sie wichtig ist).

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

Dann können Sie es nennen mit:

(assoc-recursive x 'foo 'bar)
Malabarba
quelle
2
Das war mehr oder weniger das, was ich mir auch ausgedacht hatte. Ich bin ein bisschen überrascht, dass dies nicht Teil einer etablierten Bibliothek wie Dash oder so ist. Es scheint die ganze Zeit aufzutauchen, wenn es um zB JSON-Daten geht.
Abingham
2

Hier ist eine allgemeinere Lösung:

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

Es kann einen beliebigen "Pfad" von Schlüsseln nehmen. Dies wird zurückkehren(bar . "llama")

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

während dies zurückkehren wird (baz . "monkey"):

(assoc-multi-key '(foo bar baz)
    '((foo (bar (bozo . "llama") (baz . "monkey")))))
rekado
quelle
3
Ich habe meine erste Ablehnung für diese Antwort erhalten. Möchte mir jemand sagen warum?
rekado
1
Ich bin mit der Ablehnung nicht einverstanden, da Ihr Code funktioniert (+1). Meine Spekulation ist, dass die Antwort von @ Malabarba eindeutig allgemeiner / eleganter ist als die anderen angebotenen Antworten, und daher erhielten die anderen Antworten negative Stimmen, nicht weil sie nicht funktionieren, sondern weil sie nicht die besten sind. (Davon abgesehen bevorzuge ich die Option "Upvote the Best" anstelle von "Upvote the Best und Downvote the Other".)
Dan
1
Diese beiden Fragen wurden abgelehnt, weil es hier eine Person gibt, die nicht ganz versteht, wie Abstimmungen funktionieren (und die Aufforderung der Benutzeroberfläche, einen Kommentar zu hinterlassen, ignoriert). Es ist unglücklich, aber das Beste, was wir alle tun können, ist eine positive Abstimmung.
Malabarba
0

Hier ist eine einfache Funktion, die mit einem Alist funktioniert, der in einem anderen Alist verschachtelt ist:

(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
quelle
3
Bitte Leute, wenn Sie abstimmen, hinterlassen Sie einen Kommentar.
Malabarba
1
Wer auch immer herabgestimmt hat: Ich bin nicht beleidigt, aber ich bin neugierig warum. @ Malabara: Gibt es jetzt einen Meta-Thread zu Normen zu "Downvote + Kommentar"? ;; Ich wäre neugierig auf deine Einstellung.
Dan