Elisp: How to delete an element from an association list with string key
Asked Answered
D

4

18

Now this works just fine:

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

But I'm using strings as keys in my app:

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

And this fails to do anything:

(assq-delete-all "a" al)

I think that's because the string object instance is different (?)

So how should I delete an element with a string key from an association list? Or should I give up and use symbols as keys instead, and convert them to strings when needed?

Daphie answered 21/3, 2012 at 20:34 Comment(2)
By the way, you need to assign the result of assq-delete-all back to the variable even though it is a destructive operation: (setq al (assq-delete-all 'a al)). What if the list becomes empty? The al must take on the value nil: how will that happen? Or what if you delete the first element, the head cons to which al initially points? al must be updated to skip to the second cell.Karlise
Just for completeness, to remove by value (and/or key), you can also use either seq-remove or cl-remove(-if). This only returns the 'filtered' list, so to update the value you should update the original value using setq (which is always recommended/safer, also when using 'destructive' operations like delete; at least it is like that in Common-Lisp according to Paul Graham; he mentions this in one, or multiple, of his books).Katheleenkatherin
M
17

If you know there can only be a single matching entry in your list, you can also use the following form:

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

Notice that the setq (which was missing from your sample code) is very important for `delete' operations on lists, otherwise the operation fails when the deleted element happens to be the first on the list.

Manikin answered 21/3, 2012 at 21:33 Comment(0)
K
17

The q in assq traditionally means eq equality is used for the objects.

In other words, assq is an eq flavored assoc.

Strings don't follow eq equality. Two strings which are equivalent character sequences might not be eq. The assoc in Emacs Lisp uses equal equality which works with strings.

So what you need here is an assoc-delete-all for your equal-based association list, but that function doesn't exist.

All I can find when I search for assoc-delete-all is this mailing list thread: http://lists.gnu.org/archive/html/emacs-devel/2005-07/msg00169.html

Roll your own. It's fairly trivial: you march down the list, and collect all those entries into a new list whose car does not match the given key under equal.

One useful thing to look at might be the Common Lisp compatibility library. http://www.gnu.org/software/emacs/manual/html_node/cl/index.html

There are some useful functions there, like remove*, with which you can delete from a list with a custom predicate function for testing the elements. With that you can do something like this:

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

The destructive variant is delete*.

Karlise answered 21/3, 2012 at 20:47 Comment(0)
M
17

If you know there can only be a single matching entry in your list, you can also use the following form:

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

Notice that the setq (which was missing from your sample code) is very important for `delete' operations on lists, otherwise the operation fails when the deleted element happens to be the first on the list.

Manikin answered 21/3, 2012 at 21:33 Comment(0)
B
8

Emacs 27+ includes assoc-delete-all which will work for string keys, and can also be used with arbitrary test functions.

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

In earlier versions of Emacs, cl-delete provides an alternative:

(setf ALIST (cl-delete KEY ALIST :key #'car :test #'equal))

Which equivalently says to delete items from ALIST where the car of the list item is equal to KEY.

n.b. The answer by Kaz mentions this latter option already, but using the older (require 'cl) names of delete* and remove*, whereas you would now (for supporting Emacs 24+) use cl-delete or cl-remove (which are auto-loaded).

Brachium answered 9/2, 2019 at 3:30 Comment(0)
D
3

If using emacs 25 or newer you can use alist-get

(setf (alist-get "a" al t t 'equal) t)

Dip answered 8/2, 2019 at 23:10 Comment(1)
Correct, but so very hard to read/interpret IMO that I almost feel that's a mis-feature. I can only presume it's there to support dynamic situations where you may or may not be removing a value. I would probably end up writing it as (setf (alist-get "a" al :remove :remove 'equal) :remove) if I was going to use this -- but I'm fairly sure I'd just never use it for this purpose.Brachium

© 2022 - 2024 — McMap. All rights reserved.