Cara terbaik untuk mengambil nilai dalam daftar assoc-nested?

11

Misalkan saya punya daftar assoc seperti ini:

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

Dan saya ingin nilainya bar. Aku bisa melakukan ini:

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

Tapi yang saya benar-benar suka adalah sesuatu yang menerima banyak kunci

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

Apakah hal semacam itu ada, mungkin dalam suatu paket di suatu tempat? Saya yakin saya bisa menulisnya, tetapi saya merasa Google-fu saya gagal dan saya tidak dapat menemukannya.

abingham
sumber
FWIW, saya tidak melihat daftar bersarang di halaman ini. Saya hanya melihat alists biasa dan tidak teruji. Dan tidak jelas perilaku apa yang Anda cari. Anda tidak mengatakan apa - apa tentang perilaku assoc-multi-key. Agaknya ia mencari kecocokan dengan kedua argumen pertama, tapi hanya itu yang bisa diduga, dari apa yang Anda katakan. Dan jelas tidak dapat menerima lebih dari dua kunci, karena argumen alist (mungkin x) adalah yang terakhir, bukan yang pertama - yang menunjukkan bahwa itu tidak terlalu berguna secara umum. Cobalah sebutkan apa yang Anda cari.
Drew
Saya juga menemukan format asli setqformulir dalam contoh membingungkan, jadi saya mengeditnya untuk menggunakan notasi titik umum untuk daftar-assoc.
paprika
Ah, baiklah. Jadi alist memang memiliki dua level. Pertanyaannya masih belum jelas - assoc-multi-keytetap tidak ditentukan.
Drew
1
Drew: Intinya assoc-multi-keyadalah mencari kunci pertama dalam daftar asosiasi. Ini harus menyelesaikan ke daftar asosiasi baru di mana kita mencari kunci selanjutnya. Dan seterusnya. Pada dasarnya pekerjaan singkat untuk menggali nilai dari daftar assoc-nested.
abingham
2
@Malabarba Mungkin Anda bisa menyebutkannya let-alistjuga? misalnya (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar)akan kembali "llama". Saya kira Anda menulis let-alistsetelah pertanyaan diajukan, tetapi itu dalam semangat pertanyaan dan sangat layak disebut IMO!
YoungFrog

Jawaban:

15

Berikut adalah opsi yang menggunakan sintaksis persis yang Anda minta tetapi dengan cara umum, dan cukup mudah dimengerti. Satu-satunya perbedaan adalah bahwa ALISTparameter perlu diutamakan (Anda dapat menyesuaikannya untuk menjadi yang terakhir, jika itu penting bagi Anda).

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

Maka Anda dapat menyebutnya dengan:

(assoc-recursive x 'foo 'bar)
Malabarba
sumber
2
Ini kurang lebih apa yang telah saya masak juga. Saya agak terkejut bahwa ini bukan bagian dari beberapa perpustakaan yang mapan seperti tanda hubung atau sesuatu. Tampaknya muncul sepanjang waktu ketika berhadapan dengan misalnya data json.
abingham
2

Inilah solusi yang lebih umum:

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

Itu bisa mengambil "jalur" kunci apa saja. Ini akan kembali(bar . "llama")

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

sedangkan ini akan kembali (baz . "monkey"):

(assoc-multi-key '(foo bar baz)
    '((foo (bar (bozo . "llama") (baz . "monkey")))))
rekado
sumber
3
Dapatkan downvote pertama saya untuk jawaban ini. Adakah yang mau memberi tahu saya alasannya?
rekado
1
Saya tidak setuju dengan downvote karena kode Anda berfungsi (+1). Spekulasi saya adalah bahwa jawaban @ Malabarba jelas lebih umum / anggun daripada jawaban lain yang ditawarkan, dan jawaban lainnya menerima downvotes bukan karena mereka tidak berfungsi, tetapi karena mereka bukan yang terbaik. (Karena itu, saya lebih suka pilihan "pilih-pilih yang terbaik" daripada "pilih-pilih yang terbaik dan pilih-pilih yang lain").
Dan
1
Dua pertanyaan ini diturunkan karena ada satu orang di sini yang tidak begitu memahami cara kerja downvotes (dan memilih untuk mengabaikan permintaan antarmuka untuk memberikan komentar). Sangat disayangkan, tetapi yang terbaik yang bisa kita semua lakukan adalah menang.
Malabarba
0

Berikut adalah fungsi sederhana yang bekerja dengan daftar alist di dalam daftar alist lain:

(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
sumber
3
Tolong orang-orang, ketika Anda memilih, memberikan komentar.
Malabarba
1
Siapa pun yang kalah: Saya tidak tersinggung, tetapi saya ingin tahu mengapa. @Malabara: sekarang ada utas meta tentang norma pada "downvote + komentar"? ; Saya ingin tahu.
Dan