Лучший способ получить значения во вложенных ассоциативных списках?

11

Предположим, у меня есть ассоциативный список, подобный этому:

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

И я хочу значение в bar. Я могу сделать это:

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

Но то, что я действительно хотел бы, это то, что принимает несколько ключей, как

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

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

abingham
источник
FWIW, я не вижу вложенных списков на этой странице. Я вижу только обычных, неопубликованных списков. И не ясно, какое поведение вы ищете. Вы ничего не говорите о поведении assoc-multi-key. Предположительно он ищет совпадения с обоими первыми двумя аргументами, но это действительно все, что можно предположить, исходя из того, что вы сказали. И он явно не может принять более двух ключей, поскольку аргумент alist (предположительно x) является последним, а не первым, что говорит о том, что он не слишком полезен в целом. Попробуйте на самом деле указать, что вы ищете.
Дрю
Я также посчитал, что оригинальное форматирование setqформы в примере сбивает с толку, поэтому я отредактировал его, чтобы использовать общую точечную нотацию для ассоциативных списков.
паприка
Ах хорошо. Таким образом, у alist есть два уровня. Вопрос до сих пор неясен - 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)
Malabarba
источник
2
Это более или менее то, что я приготовил. Я немного удивлен, что это не часть какой-то устоявшейся библиотеки, такой как dash или что-то в этом роде. Кажется, все время возникает, например, при работе с данными 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")))))
rekado
источник
3
Получил мой первый downvote за этот ответ. Кто-нибудь хочет сказать мне, почему?
Rekado
1
Я не согласен с понижением, поскольку ваш код работает (+1). Я предполагаю, что ответ @ Malabarba явно более общий / элегантный, чем другие предлагаемые ответы, и поэтому другие ответы получили отрицательные отзывы не потому, что они не работают, а потому, что они не самые лучшие. (Тем не менее, я предпочитаю вариант «поднять голосование за лучшее», а не «поднять голосование за лучшее и понизить мнение других».)
Дан
1
Эти два вопроса были отклонены, потому что здесь есть один человек, который не совсем понимает, как работают понижающие голоса (и предпочитает игнорировать запрос интерфейса оставить комментарий). Это прискорбно, но лучшее, что мы все можем сделать, - это повысить голос.
Малабарба
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"? ; Мне было бы любопытно на ваш взгляд.
Дан