Najlepszy sposób na uzyskanie wartości z zagnieżdżonych list assoc?

11

Załóżmy, że mam taką listę assoc:

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

I chcę wartość na bar. Mogę to zrobić:

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

Ale tak naprawdę chciałbym coś, co akceptuje wiele kluczy

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

Czy coś takiego istnieje, może gdzieś w paczce? Jestem pewien, że mógłbym to napisać, ale wydaje mi się, że mój Google-fu po prostu zawodzi i nie mogę go znaleźć.

abingham
źródło
FWIW, nie widzę żadnych zagnieżdżonych list na tej stronie. Widzę tylko zwykłych, nie odkrytych alistów. I nie jest jasne, jakiego zachowania szukasz. Nic nie mówisz o zachowaniu assoc-multi-key. Przypuszczalnie szuka dopasowania do obu pierwszych dwóch argumentów, ale tak naprawdę można przypuszczać na podstawie tego, co powiedziałeś. I najwyraźniej nie może zaakceptować więcej niż dwóch kluczy, ponieważ argument alist (przypuszczalnie x) jest ostatnim, a nie pierwszym - co sugeruje, że ogólnie nie jest zbyt użyteczny. Spróbuj właściwie określić, czego szukasz.
Drew
Znalazłem również oryginalne formatowanie setqformularza w tym przykładzie mylące, więc edytowałem go, aby użyć wspólnej notacji kropkowej dla list assoc.
papryka,
Ach, okej. Więc alist ma dwa poziomy. Pytanie wciąż jest niejasne - assoc-multi-keypozostaje nieokreślone.
Drew
1
Drew: Chodzi o assoc-multi-keysprawdzenie pierwszego klucza na liście assoc. To powinno rozwiązać nową listę assoc, w której sprawdzamy następny klucz. I tak dalej. Zasadniczo skrót do wykopywania wartości z zagnieżdżonych list assoc.
abingham
2
@Malabarba Być może mógłbyś let-alistteż wspomnieć ? np . (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar)wróci "llama". Wydaje mi się, że napisałeś let-alistpo zadaniu pytania, ale jest ono zgodne z duchem pytania i warto wspomnieć o IMO!
YoungFrog,

Odpowiedzi:

15

Oto opcja, która przyjmuje dokładnie tę składnię, o którą prosiłeś, ale w uogólniony sposób i jest dość prosta do zrozumienia. Jedyną różnicą jest to, że ALISTparametr musi być na pierwszym miejscu (możesz go dostosować, aby był ostatni, jeśli jest to dla Ciebie ważne).

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

Następnie możesz to nazwać za pomocą:

(assoc-recursive x 'foo 'bar)
Malabarba
źródło
2
To mniej więcej to, co ugotowałem. Jestem trochę zaskoczony, że nie jest to część jakiejś uznanej biblioteki, takiej jak dash czy coś takiego. Wydaje się, że pojawia się cały czas, gdy mamy do czynienia np. Z danymi JSON.
abingham
2

Oto bardziej ogólne rozwiązanie:

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

Może przyjmować dowolną „ścieżkę” kluczy. To wróci(bar . "llama")

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

mając na uwadze, że to zwróci (baz . "monkey"):

(assoc-multi-key '(foo bar baz)
    '((foo (bar (bozo . "llama") (baz . "monkey")))))
rekado
źródło
3
Mam pierwszy głos za odpowiedzią. Ktoś chce mi powiedzieć, dlaczego?
rekado
1
Nie zgadzam się z opinią negatywną, ponieważ Twój kod działa (+1). Spekuluję, że odpowiedź @ Malabarba jest wyraźnie bardziej ogólna / elegancka niż inne odpowiedzi w ofercie, a więc inne odpowiedzi otrzymały głosy negatywne nie dlatego, że nie działają, ale dlatego, że nie są najlepsze. (Biorąc to pod uwagę, wolę opcję „upvote the best” zamiast „upvote the best and downvote the other” alternatywa.)
Dan
1
Te dwa pytania zostały odrzucone, ponieważ jest tu jedna osoba, która nie do końca rozumie, jak działa downvotes (i postanawia zignorować prośbę interfejsu o pozostawienie komentarza). To niefortunne, ale najlepsze, co wszyscy możemy zrobić, to głosować pozytywnie.
Malabarba
0

Oto prosta funkcja, która działa z listą zagnieżdżoną w innej listce:

(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
źródło
3
Proszę, ludzie, kiedy głosujesz w dół, zostaw komentarz.
Malabarba
1
Ktokolwiek zlekceważył: nie jestem obrażony, ale jestem ciekawy, dlaczego. @Malabara: istnieje teraz meta wątek na temat norm dotyczących „downvote + comment”? ; Byłbym ciekawy twojego zdania.
Dan