5.8 Списки ассоциаций

Список ассоциаций (association list), или сокращенно alist, регистрирует отображение из ключей в значения. Это список ячеек конс, называемых ассоциациями: CAR каждой ячейки конс является ключом, а CDR — ассоциированным значением.7

Вот пример alist. Ключ pine ассоциирован со значением cones; ключ oak ассоциирован со acorns; а ключ maple ассоциирован со seeds.

((pine . cones)
 (oak . acorns)
 (maple . seeds))

И значения, и ключи в alist могут быть любыми объектами Lisp. Например, в следующем alist символ a ассоциирован с числом 1, а строка "b" ассоциирована с списком (2 3), который является CDR элемента alist:

((a . 1) ("b" 2 3))

Иногда лучше спроектировать alist таким образом, чтобы хранить ассоциированное значение в CAR CDR элемента. Вот пример такого alist:

((rose red) (lily white) (buttercup yellow))

Здесь мы рассматриваем red как значение, ассоциированное с rose. Одно преимущество такого alist в том, что вы можете хранить другую связанную информацию — даже список других элементов — в CDR CDR. Один недостаток в том, что вы не можете использовать rassq (см. ниже) для поиска элемента, содержащего заданное значение. Когда ни одно из этих соображений не является важным, выбор зависит от вкуса, при условии, что вы последовательны в отношении любого данного alist.

Тот же alist, показанный выше, можно рассматривать как имеющий ассоциированное значение в CDR элемента; значение, ассоциированное с rose, было бы списком (red).

Списки ассоциаций часто используются для записи информации, которую вы могли бы хранить в стеке, поскольку новые ассоциации легко добавляются в начало списка. Когда вы ищете в списке ассоциаций ассоциацию с заданным ключом, возвращается первый найденный, если их несколько.

В Emacs Lisp не является ошибкой, если элемент списка ассоциаций не является ячейкой конс. Функции поиска alist просто игнорируют такие элементы. Многие другие версии Lisp сигнализируют об ошибках в таких случаях.

Обратите внимание, что списки свойств похожи на списки ассоциаций по нескольким аспектам. Список свойств ведет себя как список ассоциаций, в котором каждый ключ может встречаться только один раз. См. Списки свойств для сравнения списков свойств и списков ассоциаций.

Функция: assoc key alist &optional testfn

Эта функция возвращает первую ассоциацию для key в alist, сравнивая key с элементами alist с использованием testfn, если это функция, или equal в противном случае (см. Предикаты равенства). Если testfn является функцией, она вызывается с двумя аргументами: CAR элемента из alist и key. Функция возвращает nil, если ни одна ассоциация в alist не имеет CAR, равный key, как проверяется testfn. Например:

(setq trees '((pine . cones) (oak . acorns) (maple . seeds)))
     ⇒ ((pine . cones) (oak . acorns) (maple . seeds))
(assoc 'oak trees)
     ⇒ (oak . acorns)
(cdr (assoc 'oak trees))
     ⇒ acorns
(assoc 'birch trees)
     ⇒ nil

Вот еще один пример, в котором ключи и значения не являются символами:

(setq needles-per-cluster
      '((2 "Austrian Pine" "Red Pine")
        (3 "Pitch Pine")
        (5 "White Pine"))

(cdr (assoc 3 needles-per-cluster))
     ⇒ ("Pitch Pine")
(cdr (assoc 2 needles-per-cluster))
     ⇒ ("Austrian Pine" "Red Pine")

Функция assoc-string очень похожа на assoc, за исключением того, что она игнорирует определенные различия между строками. См. Сравнение символов и строк.

Функция: rassoc value alist

Эта функция возвращает первую ассоциацию со значением value в alist. Она возвращает nil, если ни одна ассоциация в alist не имеет CDR, равный equal value.

rassoc похож на assoc, за исключением того, что он сравнивает CDR каждой ассоциации alist вместо CAR. Вы можете думать об этом как о обратном assoc, находящем ключ для заданного значения.

Функция: assq key alist

Эта функция похожа на assoc тем, что возвращает первую ассоциацию для key в alist, но она выполняет сравнение, используя eq. assq возвращает nil, если ни одна ассоциация в alist не имеет CAR, eq равный key. Эта функция используется чаще, чем assoc, поскольку eq быстрее, чем equal, и большинство alist используют символы в качестве ключей. См. Предикаты равенства.

(setq trees '((pine . cones) (oak . acorns) (maple . seeds)))
     ⇒ ((pine . cones) (oak . acorns) (maple . seeds))
(assq 'pine trees)
     ⇒ (pine . cones)

С другой стороны, assq обычно не полезен в alist, где ключи могут не быть символами:

(setq leaves
      '(("simple leaves" . oak)
        ("compound leaves" . horsechestnut)))

(assq "simple leaves" leaves)
     ⇒ Неопределенно; может быть nil или ("simple leaves" . oak).
(assoc "simple leaves" leaves)
     ⇒ ("simple leaves" . oak)
Функция: alist-get key alist &optional default remove testfn

Эта функция похожа на assq. Она находит первую ассоциацию (key . value) путем сравнения key с элементами alist и, если найдена, возвращает value этой ассоциации. Если ассоциация не найдена, функция возвращает default. Сравнение key с элементами alist использует функцию, указанную в testfn, по умолчанию eq.

Это обобщенная переменная (см. Обобщенные переменные), которая может использоваться для изменения значения с помощью setf. При использовании ее для установки значения, необязательный аргумент remove не-nil означает удаление ассоциации key из alist, если новое значение eql default.

Функция: rassq value alist

Эта функция возвращает первую ассоциацию со значением value в alist. Она возвращает nil, если ни одна ассоциация в alist не имеет CDR, eq равный value.

rassq похож на assq, за исключением того, что он сравнивает CDR каждой ассоциации alist вместо CAR. Вы можете думать об этом как о обратном assq, находящем ключ для заданного значения.

Например:

(setq trees '((pine . cones) (oak . acorns) (maple . seeds)))

(rassq 'acorns trees)
     ⇒ (oak . acorns)
(rassq 'spores trees)
     ⇒ nil

rassq не может искать значение, хранящееся в CAR CDR элемента:

(setq colors '((rose red) (lily white) (buttercup yellow)))

(rassq 'white colors)
     ⇒ nil

В этом случае CDR ассоциации (lily white) не является символом white, а скорее списком (white). Это становится более очевидно, если ассоциация записана в нотации точечной пары:

(lily white) ≡ (lily . (white))
Функция: assoc-default key alist &optional test default

Эта функция ищет в alist совпадение для key. Для каждого элемента alist она сравнивает элемент (если это атом) или CAR элемента (если это конс) с key, вызывая test с двумя аргументами: элементом или его CAR и key. Аргументы передаются в таком порядке, чтобы вы могли получить полезные результаты, используя string-match с alist, содержащим регулярные выражения (см. Поиск по регулярным выражениям). Если test опущен или равен nil, для сравнения используется equal.

Если элемент alist соответствует key по этому критерию, то assoc-default возвращает значение, основанное на этом элементе. Если элемент является консом, то значением является CDR элемента. В противном случае возвращается default.

Если ни один элемент alist не соответствует key, assoc-default возвращает nil.

Функция: copy-alist alist

Эта функция возвращает копию alist с глубиной двух уровней: она создает новую копию каждой ассоциации, чтобы вы могли изменять ассоциации нового alist, не меняя старого.

(setq needles-per-cluster
      '((2 . ("Austrian Pine" "Red Pine"))
        (3 . ("Pitch Pine"))
        (5 . ("White Pine"))))))
⇒
((2 "Austrian Pine" "Red Pine")
 (3 "Pitch Pine")
 (5 "White Pine"))

(setq copy (copy-alist needles-per-cluster))
⇒
((2 "Austrian Pine" "Red Pine")
 (3 "Pitch Pine")
 (5 "White Pine"))

(eq needles-per-cluster copy)
     ⇒ nil
(equal needles-per-cluster copy)
     ⇒ t
(eq (car needles-per-cluster) (car copy))
     ⇒ nil
(cdr (car (cdr needles-per-cluster)))
     ⇒ "Pitch Pine")
(eq (cdr (car (cdr needles-per-cluster)))
    (cdr (car (cdr copy))))
     ⇒ t

Этот пример показывает, как copy-alist позволяет изменять ассоциации одной копии, не затрагивая другую:

(setcdr (assq 3 copy) '("Martian Vacuum Pine"))
(cdr (assq 3 needles-per-cluster))
     ⇒ "Pitch Pine")
Функция: assq-delete-all key alist

Эта функция удаляет из alist все элементы, чей CAR равен eq key, подобно тому, как если бы вы использовали delq для удаления каждого такого элемента по отдельности. Она возвращает укороченный alist и часто изменяет исходную структуру списка alist. Для правильных результатов используйте возвращаемое значение assq-delete-all, а не смотрите на сохраненное значение alist.

(setq alist (list '(foo 1) '(bar 2) '(foo 3) '(lose 4)))
     ⇒ ((foo 1) (bar 2) (foo 3) (lose 4))
(assq-delete-all 'foo alist)
     ⇒ ((bar 2) (lose 4))
alist
     ⇒ ((foo 1) (bar 2) (lose 4))
Функция: assoc-delete-all key alist &optional test

Эта функция похожа на assq-delete-all, за исключением того, что она принимает необязательный аргумент test, предикатную функцию для сравнения ключей в alist. Если он опущен или равен nil, test по умолчанию равен equal. Как и assq-delete-all, эта функция часто изменяет исходную структуру списка alist.

Функция: rassq-delete-all value alist

Эта функция удаляет из alist все элементы, чей CDR равен eq value. Она возвращает укороченный alist и часто изменяет исходную структуру списка alist. rassq-delete-all похож на assq-delete-all, за исключением того, что он сравнивает CDR каждой ассоциации alist вместо CAR.

Макрос: let-alist alist body

Создает привязку для каждого символа, используемого в качестве ключа в списке ассоциаций alist, с префиксом точкой. Это может быть полезно при доступе к нескольким элементам в одном списке ассоциаций, и это лучше всего понять на простом примере:

(setq colors '((rose . red) (lily . white) (buttercup . yellow)))
(let-alist colors
  (if (eq .rose 'red)
      .lily))
     ⇒ white

Тело body проверяется во время компиляции, и привязываются только символы, которые появляются в body с символом ‘.’ в имени символа. Поиск ключей выполняется с помощью assq, а cdr возвращаемого значения этого assq присваивается в качестве значения для привязки.

Поддерживается вложенные списки ассоциаций:

(setq colors '((rose . red) (lily (belladonna . yellow) (brindisi . pink))))
(let-alist colors
  (if (eq .rose 'red)
      .lily.belladonna))
     ⇒ yellow

Вложенность let-alist друг в друга разрешена, но код во внутреннем let-alist не может получить доступ к переменным, привязанным внешним let-alist.


Примечания

(7)

Это использование термина “ключ” не связано с термином “последовательность ключей”; оно означает значение, используемое для поиска элемента в таблице. В этом случае таблицей является alist, а ассоциации alist — это элементы.