如何确保关联列表在Emacs中维护唯一键

dgt*_*zed 4 emacs elisp

考虑到键添加到关联列表的频率auto-mode-alist,我认为有一些习惯方法用于维护具有唯一键的关联列表,但尚未遇到它.

假设我执行以下操作:

(setq alist '())
(add-to-list 'alist '(a . 1))
(add-to-list 'alist '(a . 2))
(add-to-list 'alist '(b . 3))
Run Code Online (Sandbox Code Playgroud)

运行之后,alist包含((b . 3) (a . 2) (a . 1)).我看到add-to-list可以选择compare-fn,所以我认为有一些方法我可以传递得到((b . 3) (a . 1))结果.我也知道我可以使用哈希表,但很奇怪如何用关联列表惯用它.

Dan*_*Dan 7

没有要求关联列表具有唯一键,如您的示例所示,也没有任何特殊理由期望它们具有唯一键 - 在基础上,它只是列表列表car,对嵌套列表的s 没有限制.

我认为通过将新对推送到列表前面来覆盖初始键/值对的键没有限制这一事实是不恰当的.alists的一些核心功能隐含地沿着这些方向工作.例如,这里是docstring assoc:

Return non-nil if KEY is `equal' to the car of an element of LIST.
The value is actually the first element of LIST whose car equals KEY.
Run Code Online (Sandbox Code Playgroud)

因此,它只返回第一个元素,而不管列表后面有多少其他元素具有相同的键.这可能是一个非常有用的功能.

更新. 如果你真的想要防止add-to-list对先前的键/值对进行阴影处理(尽管你这样做会对语言有所帮助),你可以定义以下函数并将其传递给compare-fn参数add-to-list(注意它没有错误 -检查):

(defun key-used-p (elt1 elt2)
  "Helper function for add-to-list: returns non-nil if key is
already in use in an association list."
  (eq (car elt1) (car elt2)))

(setq alist '((a . 1) (b . 1) (c . 1)))        ; original list
(add-to-list 'alist '(a . 2) nil #'key-used-p) ; adds nothing because a in use
(add-to-list 'alist '(d . 2) nil #'key-used-p) ; adds (d . 2)
Run Code Online (Sandbox Code Playgroud)

  • 另一种说法是,这是alist(同样,对于plist)的优点之一:较旧的条目被较新的条目遮蔽.IOW,问题的快速回答是*不要打扰*(或*为什么要打扰?*). (3认同)

Mir*_*lov 6

不要担心具有重复键的项目的alist.通常当您使用alist时,您可以访问带有的项目assoc,这将返回列表中的第一个匹配项目并忽略其余项目.因此,对待alist的惯用方法是,每次要使用旧键替换alist中的项时,只需添加一个新的点对并忽略旧的.所以无论你有多少重复,assoc都会忽略它们.您可以通过alist API工作并忽略实现细节,可以这么说.Alist的结构列表是低级别且不相关的.

许多有用的Common Lisp函数都是通过CL库实现的,前缀为cl-.一个这样的功能是cl-remove-duplicates,由于关键字参数,它非常通用.(如果你想要一个Common Lisp解决方案,只需使用remove-duplicates).因此,如果由于某种原因您想要一个具有唯一键的alist,请删除所有重复的项目,但新添加的:

(cl-remove-duplicates my-list
                      :key #'car
                      :from-end t)
Run Code Online (Sandbox Code Playgroud)

提供car密钥等同于编写自定义测试功能,仅比较每2个元素的汽车::test (lambda (a b) (equal (car a) (car b)).它为什么有效?cl-remove-duplicates已经使用eql它作为它的测试函数,正如它在手册中所说,一个关键函数就像一个过滤器,通过该过滤器可以看到元素的功能,因此它不会比较元素本身,而是首先通过关键函数放置元素.

每次看起来方便和优雅时都应该使用CL库,因为它与Emacs一起提供,但我们假设您不能出于某种原因使用它.然后是dash.el第三方列表操作库.它有一个功能-distinct,可以删除列表中的多个匹配项.但是它逐字逐句地使用列表的元素,它不理解列表!或者是吗?您可以通过将其分配给-compare-fn变量来提供自定义比较功能,并-distinct使用它而不是直接比较它equal.只是暂时提供一个按键比较的功能let:

(let ((-compare-fn (lambda (a b)
                     (equal (car a) (car b)))))
  (-distinct my-list))
Run Code Online (Sandbox Code Playgroud)