11

Is there a simpler way to remove the last element of a list than this?

(setq list (reverse (cdr (reverse list))))
1
  • 1
    If doing that, you would likely want to be using nreverse (destructive) rather than reverse (copying), for the sake of efficiency.
    – phils
    Commented May 22, 2019 at 4:16

3 Answers 3

18

Yes there is:

(setq list (butlast list))

That is a function from subr.el. (Loaded by default. No need to load anything.)

You can also cut a tail with N elements by

(setq list (butlast list N))


A word about phils' comment:

If it's safe to modify the original list structure, then nbutlast will be slightly more efficient (n.b. you still need to assign the result back to the variable).

The comment is right and useful but it has to be considered very cautious.

For an exmple the application of nbutlast is safe if the following three conditions are fulfilled:

  • if you are in a pure function (you have local non-leaking bindings)
  • you generate the list from its elements within the function
  • you want to split off the last element at the end of the function (i.e. in the last evaluated form before exiting the function)

Be aware that nbutlast modifies the argument list in the calls of the following functions!

(defun queue (el list)
 "Prepend el and remove last element."
 (nbutlast (cons el list)))

(defun foo (head list)
 "Prepend head and remove last element."
 (nbutlast (append head list)))

(setq list (list 2 3 4))

(queue 1 list)

list ;; -> (2 3)

(foo '(1) list)

list ;; -> (2)
2
  • 1
    If it's safe to modify the original list structure, then nbutlast will be slightly more efficient (n.b. you still need to assign the result back to the variable).
    – phils
    Commented May 22, 2019 at 4:09
  • Note that behind the scenes, these functions are using setcdr to modify the nthcdr where N is based on the length of the list.
    – phils
    Commented May 22, 2019 at 4:13
1

Another solution would be to use dash.el's slicing:

(setq list (-slice 0 -1 list))

I don't know if this is too much 'soy' for those hardcore lispers, but -slice really takes the hassle out of lists for me. ;)

0
0

The sequences library bundled with Emacs makes this easily generalized. Here I demonstrate a possible implementation in an IELM buffer.

ELISP> (defun seq-butlast-elt (seq)
         "Return a copy of SEQ without its final element."
         (seq-take seq (1- (seq-length seq))))
seq-butlast-elt
ELISP> (seq-butlast-elt '(1 2 3))
(1 2)

ELISP> (seq-butlast-elt '(1))
nil
ELISP> (seq-butlast-elt '())
nil
ELISP> (seq-butlast-elt ["oak" "cherry" "apple"])
["oak" "cherry"]

ELISP> (seq-butlast-elt ["apple"])
[]

ELISP> (seq-butlast-elt [])
[]

ELISP> (seq-butlast-elt "dogs")
"dog"
ELISP> (seq-butlast-elt "d")
""
ELISP> (seq-butlast-elt "")
""

https://www.gnu.org/software/emacs/manual/html_node/elisp/Sequence-Functions.html

This might not be the fastest possible implementation.

Your Answer

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

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