Emacs Lisp: how to avoid inserting a duplicate list item?
The function add-to-list
will check automatically before adding
(setq a '(1 2 3))
(add-to-list 'a 4)
(add-to-list 'a 3)
will result in a
equal to (4 1 2 3)
From Emacs 26 C-h f add-to-list
:
(add-to-list LIST-VAR ELEMENT &optional APPEND COMPARE-FN) Add ELEMENT to the value of LIST-VAR if it isn’t there yet. The test for presence of ELEMENT is done with ‘equal’, or with COMPARE-FN if that’s non-nil. If ELEMENT is added, it is added at the beginning of the list, unless the optional argument APPEND is non-nil, in which case ELEMENT is added at the end. The return value is the new value of LIST-VAR. This is handy to add some elements to configuration variables, but please do not abuse it in Elisp code, where you are usually better off using ‘push’ or ‘cl-pushnew’. If you want to use ‘add-to-list’ on a variable that is not defined until a certain package is loaded, you should put the call to ‘add-to-list’ into a hook function that will be run only after loading the package. ‘eval-after-load’ provides one way to do this. In some cases other hooks, such as major mode hooks, can do the job.
In addition to cobbal's answer about add-to-list
, there's also cl-pushnew
:
(require 'cl-lib)
(setq list-of-strings '("one" "two" "three"))
(cl-pushnew "two" list-of-strings :test #'string=)
⇒ ("one" "two" "three")
(cl-pushnew "zero" list-of-strings :test #'string=)
⇒ ("zero" "one" "two" "three")
The :test
#'string=
argument is needed in your case because, cl-pushnew
uses eql
to compare by default, and it doesn't treat two strings with the same content as equal. (equal
) would work too.
(eql "some-string" "some-string")
⇒ nil
(string= "some-string" "some-string")
⇒ t
(equal "some-string" "some-string")
⇒ t
From Emacs 26 C-h f cl-pushnew
:
cl-pushnew is a Lisp macro in ‘cl-lib.el’. (cl-pushnew X PLACE [KEYWORD VALUE]...) (cl-pushnew X PLACE): insert X at the head of the list if not already there. Like (push X PLACE), except that the list is unmodified if X is ‘eql’ to an element already on the list. Keywords supported: :test :test-not :key