Найкращий спосіб отримати значення у вкладених списках асоцій?


11

Припустимо, у мене є такий список асоцій:

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

І я хочу значення bar. Я можу зробити це:

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

Але те, що мені дуже хотілося, - це те, що приймає кілька клавіш, наприклад

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

Чи існує така річ, можливо, в пакеті десь? Я впевнений, що міг би це написати, але відчуваю, що мій Google-фу просто не працює, і я не можу його знайти.


FWIW, я не бачу вкладених списків на цій сторінці. Я бачу лише звичайних, нестримованих алістів. І незрозуміло, яку поведінку ви шукаєте. Ти нічого не кажеш про поведінку assoc-multi-key. Імовірно, він шукає збіги обох своїх перших двох аргументів, але це дійсно все, що можна було припустити, з того, що ви сказали. І явно не може прийняти більше двох ключів, оскільки аллістний аргумент (імовірно x) є останнім, а не першим - що говорить про те, що він не надто корисний в цілому. Спробуйте насправді вказати, що ви шукаєте.
Дрю

Я також виявив, що оригінальне форматування setqв прикладі заплутане, тому я відредагував його, щоб використовувати загальну точку-позначення для списків асоцій.
паприка

А, добре. Отже, аліст має два рівні. Питання досі залишається незрозумілим - assoc-multi-keyзалишається невизначеним.
Дрю

1
Дрю: Суть у assoc-multi-keyтому, щоб знайти перший ключ у списку асоцій. Це має вирішити новий список асоцій, в якому ми шукаємо наступний ключ. І так далі. В основному це короткий порядок для перекопування значень з вкладених списків асоцій.
Абінгем

2
@Malabarba Можливо, ви могли б також згадати let-alist? наприклад (let-alist '((foo . ((bar . "llama") (baz . "monkey")))) .foo.bar)повернеться "llama". Я думаю, ви писали let-alistпісля того, як питання було задано, але це в дусі запитання і дуже варто згадати ІМО!
YoungFrog

Відповіді:


15

Ось варіант, який приймає точний синтаксис, який ви просили, але узагальнений, і його зрозуміти досить просто. Єдина відмінність полягає в тому, що ALISTпараметр повинен прийти першим (ви можете адаптувати його до останнього, якщо це важливо для вас).

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

Тоді ви можете зателефонувати за допомогою:

(assoc-recursive x 'foo 'bar)

2
Це більш-менш те, що я готував також. Я трохи здивований, що це не частина якоїсь створеної бібліотеки, як тире чи щось таке. Здається, вони виникають постійно під час роботи, наприклад, з даними json.
Абінгем

2

Ось більш загальне рішення:

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

Він може пройти будь-який "шлях" ключів. Це повернеться(bar . "llama")

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

тоді як це повернеться (baz . "monkey"):

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

3
Отримав перший відгук на цю відповідь. Хто-небудь готовий сказати мені, чому?
rekado

1
Я не згоден із заявою, оскільки працює ваш код (+1). Мої міркування полягають у тому, що відповідь @ Малабарби явно більш загальна / елегантна, ніж інші відповіді, які пропонуються, і тому інші відповіді отримали репутацію не тому, що вони не працюють, а тому, що вони не найкращі. (Це, як було сказано, я віддаю перевагу варіанту "відмовити за найкращим", а не за "альтернативу найкращим та відмову від інших".
Дан

1
Ці два питання були зняті з огляду на те, що тут є одна людина, яка не зовсім розуміє, як функціонують downvotes (і вирішує проігнорувати прохання інтерфейсу залишити коментар). Це прикро, але найкраще, що ми можемо зробити, - це підтримка.
Малабарба

0

Ось проста функція, яка працює з алістом, вкладеним всередину іншого списку:

(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
Будь ласка, люди, коли ви голосуєте, залиште коментар.
Малабарба

1
Хто бідував: я не ображаюся, але мені цікаво чому. @Malabara: тепер є мета-нитка щодо норм на "downvote + comment"? ; Мені буде цікаво з вами.
Дан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.