4

Given '(a b c d), how would I remove b and c to end up with '(a d)? Is there a "right" way to remove multiple, distinct elements from a list?

Obviously, one could do (delete 'c (delete 'b '(a b c d))). But this doesn't generalize or scale well1.

Other possibilities include:

  • Create a predicate function and map it over the list with something like delete-if-not.

  • Perform a set difference (if that's even different from the list mapping)

I am getting lost in all the different implementations which accomplish, so it seems, the same thing (i.e. delete, dash, seq, cl-delete, etc.).

I would prefer to only use whatever ships with vanilla Emacs (26.3), unless there is a compelling reason to introduce an external dependency.


1 I understand the distinction between remove and delete in that one preserves the original list while the other does not. It's my impression that the two are more or less interchangeable otherwise.

4
  • Would it not be more generic to ask how to delete elements from one list from another list rather than using "b" and "c"? I think that's what you intend to convey.
    – RichieHH
    Feb 2, 2020 at 20:24
  • "this doesn't generalize or scale well" -- when you talk about scaling, are you significantly concerned about performance? For smallish lists all the typical approaches will be perfectly serviceable, but you might see dramatic performance benefits when processing large lists if you used hashing to reduce the algorithmic complexity. irreal.org/blog/?p=8621 demonstrates this for a very similar problem.
    – phils
    Feb 3, 2020 at 3:49
  • @phils by generalize I meant delete doesn't accept a list and by scale I meant that to delete more items, it would require a process of (delete (delete (delete ...). I wasn't concerning myself with performance yet. However, your reference is noted and much appreciated! Feb 3, 2020 at 22:06
  • 2
    Note that you should never pass a quoted constant list, such as '(a b c d), directly to a destructive function such as delete, as the constant will then be modified globally and the same code may behave differently each time it's run. Instead, either make a copy of the constant list first, e.g. (copy-sequence '(a b c d)), create a new list each time, e.g. (list 'a 'b 'c 'd), or pass the constant to non-destructive functions such as remove.
    – Basil
    Feb 4, 2020 at 10:39

5 Answers 5

5

What about seq-remove from seq.el, which shipped with Emacs 25?

(require 'seq)

(let ((l '(a b c d)))
  (seq-remove (lambda (x) (memq x '(b c)))
              l))  ; (a d)

Or, if you just have a list of elements to exclude, seq-difference from the same package?

(let ((l '(a b c d)))
  (seq-difference l '(b c)))  ; (a d)

See (info "(elisp) Sequence Functions") for more information.

0
3

See cl-set difference.

(cl-set-difference '(a b c d e f) '(b d))

It might help.

https://www.gnu.org/software/emacs/manual/html_node/cl/Lists-as-Sets.html

2

You can also use cl-remove-if or cl-delete-if, with a predicate such as

(lambda (x) (memq x '(b c))

Or whatever other test you want (e.g. member). There are also functions cl-remove-if-not and cl-delete-if-not.

1

We can reduce over the list of elements you want to remove, and each time through, remove that element from the seq.

ELISP> (cl-reduce (lambda (seq elt) (delete elt seq))
                  '(b c)
                  :initial-value '(a b c d))
(a d)

Using a helper function from dash, which reduces the code somewhat:

ELISP> (cl-reduce (-flip #'delete)
                  '(b c)
                  :initial-value '(a b c d))
(a d)
1

Withouth loading any library

(setq mylist '(a b c d))
(let ((shorter (copy-sequence mylist)))
  (dolist (elt '(b c) shorter)
    (setq shorter (delete elt shorter))))

returns

(a d)
2
  • Instead of calling remove, which makes a copy of its list argument, in a loop, it would be better to initialise shorter to (copy-sequence mylist) and call delete in a loop. But even delete has to traverse the entire list each time, so it's not necessarily the fastest in every scenario. FWIW, since Emacs 27, seq-remove is autoloaded so one needn't explicitly load the library to use it.
    – Basil
    Nov 30, 2020 at 17:44
  • Thanks! I needed copy-sequence but I didn't know about it. I came up with this answer while trying to append a shortened ido-ignore-buffers to another list. I used remove because I noticed that by using delete ido-ignore-buffers itself was modified. With copy-sequence I can use delete and avoid the unnecessary duplication. Regarding the efficiency, I know pretty much nothing about it honestly. For my simple task I just didn't want to load any library (whether explicitly or not). Dec 1, 2020 at 10:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.