Elisp:如何删除具有字符串键的关联列表中的元素

18

现在这个很好地运行了:

(setq al '((a . "1") (b . "2")))
(assq-delete-all 'a al)

但是我在我的应用程序中使用字符串作为键:

(setq al '(("a" . "foo") ("b" . "bar")))

而且这个代码什么也没有做:

(assq-delete-all "a" al)

我认为这是因为字符串对象实例不同导致的(?)

那么我应该如何从一个关联列表中删除具有字符串键的元素呢?或者我应该放弃使用字符串作为键,改用符号,并在需要时将它们转换成字符串吗?


3
顺便提一下,即使assq-delete-all是一个破坏性操作,你仍然需要将其结果分配回变量中:(setq al (assq-delete-all 'a al))。如果列表变为空怎么办?al必须取值为nil:这将如何发生?或者,如果删除了头部的第一个元素,则指向al最初指向的头部cons的指针必须更新以跳过第二个单元格。 - Kaz
为了完整性,要按值(和/或键)删除,您也可以使用seq-removecl-remove(-if)。 这仅返回“过滤”列表,因此要更新值,您应该使用setq更新原始值(这总是建议的/更安全的方式,即使在使用“破坏性”操作如delete时也是如此;至少在Common-Lisp中是这样的,根据Paul Graham的说法; 他在他的一本或多本书中提到了这一点)。 - dalanicolai
4个回答

17

assq函数中的q传统上代表对象使用eq相等性。

换句话说,assq是一种eq风味的assoc

字符串不遵循eq相等性。两个在字符序列上等效的字符串可能并不是eq。Emacs Lisp中的assoc使用与字符串一起工作的equal相等性。

因此,在基于equal的关联列表中,您需要一个assoc-delete-all,但该函数不存在。

当我搜索assoc-delete-all时,我找到的所有内容都在这个邮件列表线程中:http://lists.gnu.org/archive/html/emacs-devel/2005-07/msg00169.html

自己动手实现吧。这非常简单:您沿着列表前进,并将所有car与给定键不匹配的条目收集到一个新列表中,其中使用equal进行比较。

一个有用的东西可能是查看Common Lisp兼容性库。http://www.gnu.org/software/emacs/manual/html_node/cl/index.html

那里有一些有用的函数,比如remove*,可以使用自定义谓词函数从列表中删除元素。使用它,您可以做到这样:

;; remove "a" from al, using equal as the test, applied to the car of each element
(setq al (remove* "a" al :test 'equal :key 'car))

破坏性变体是 delete*


17

如果您知道列表中只会有一个匹配条目,也可以使用以下形式:

(setq al (delq (assoc <string> al) al)

请注意,setq(你提供的代码样例中缺失了该语句)对于在列表上进行 `delete' 操作是非常重要的,否则当被删除的元素恰好是列表的第一个元素时,操作会失败。


8

Emacs 27+包含assoc-delete-all函数,它可以用于字符串键,并且还可以与任意测试函数一起使用。

(assoc-delete-all KEY ALIST &optional TEST)

Delete from ALIST all elements whose car is KEY.
Compare keys with TEST.  Defaults to ‘equal’.
Return the modified alist.
Elements of ALIST that are not conses are ignored.

e.g.:

(setf ALIST (assoc-delete-all KEY ALIST))

在早期版本的Emacs中,cl-delete 提供了一种替代方法:
(setf ALIST (cl-delete KEY ALIST :key #'car :test #'equal))

这句话的意思是从ALIST中删除car等于KEY的列表项。

注意,Kaz的答案已经提到了这个选项,但使用了旧的(require 'cl)名称,即delete*remove*,而现在(为了支持Emacs 24+)应该使用cl-deletecl-remove(它们会自动加载)。


3
如果使用emacs 25或更新版本,您可以使用alist-get(setf (alist-get "a" al t t 'equal) t)

1
正确,但在我看来非常难阅读/解释,以至于我几乎觉得这是一个缺陷。我只能假设它存在是为了支持动态情况,在这种情况下,您可能会删除或不删除值。如果我要使用它,我可能会将其编写为(setf (alist-get "a" al :remove :remove 'equal) :remove)--但我相当确定我永远不会为此目的使用它。 - phils

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接