12

Q:  How to remove / delete nth element of a list.

CAVEAT:  Do not remove all occurrences / members matching nth element -- e.g., eq or equal.

EXAMPLE:  Remove the 17th element of:

'(a b c d e f g h i j k l m n o p q r s t u v w x y z)

nth ELEMENT -- CHEAT SHEET / LEGEND:

element 0:   a
element 1:   b
element 2:   c
element 3:   d
element 4:   e
element 5:   f
element 6:   g
element 7:   h
element 8:   i
element 9:   j
element 10:  k
element 11:  l
element 12:  m
element 13:  n
element 14:  o
element 15:  p
element 16:  q
element 17:  r
element 18:  s
element 19:  t
element 20:  u
element 21:  v
element 22:  w
element 23:  x
element 24:  y
element 25:  z

7 Answers 7

9

Well, here's a destructive version I'd be happy with:

(defun remove-nth-element (nth list)
  (if (zerop nth) (cdr list)
    (let ((last (nthcdr (1- nth) list)))
      (setcdr last (cddr last))
      list)))

(remove-nth-element 0 (list 1 2 3 4 5))
(2 3 4 5)

(remove-nth-element 1 (list 1 2 3 4 5))
(1 3 4 5)

(remove-nth-element 2 (list 1 2 3 4 5))
(1 2 4 5)

(remove-nth-element 3 (list 1 2 3 4 5))
(1 2 3 5)

(remove-nth-element 4 (list 1 2 3 4 5))
(1 2 3 4)

(remove-nth-element 5 (list 1 2 3 4 5))
(1 2 3 4 5)
4

Here is another non-destructive version which uses cl-loop:

(defun remove-nth-element (nth list)
  "Return a copy of a LIST, with NTH element removed."
  (loop for i in list
        for idx from 0
        unless (= idx nth)
        collect i))
     ⇒ remove-nth-element

(remove-nth-element 0 (list 1 2 3 4 5))
     ⇒ (2 3 4 5)

(remove-nth-element 1 (list 1 2 3 4 5))
     ⇒ (1 3 4 5)

(remove-nth-element 2 (list 1 2 3 4 5))
     ⇒ (1 2 4 5)

(remove-nth-element 3 (list 1 2 3 4 5))
     ⇒ (1 2 3 5)

(remove-nth-element 4 (list 1 2 3 4 5))
     ⇒ (1 2 3 4)

(remove-nth-element 5 (list 1 2 3 4 5))
     ⇒ (1 2 3 4 5)
4

setcar returns NEWCAR, which is an uninterned symbol G<N> deleted with delq:

(let ((l '(1 2 3 4 5 6 7)))
  (delq (setcar (nthcdr 5 l) (gensym)) l)) ;⇒ (1 2 3 4 5 7)

You may also use nil for example as NEWCAR when there are no nil values in the list.

1
  • 1
    Neat trick. Using (cons nil nil) is a bit cheaper than gensym since you don't actually need a symbol here.
    – npostavs
    Commented Mar 17, 2019 at 14:20
3

Here's a simple function to remove the nth element from a list:

(defun remove-nth (n list)
  "Remove the nth element of a list."
  (if (> n (length list))
      list
    (append (cl-subseq list 0 n)
            (cl-subseq list (1+ n)))))

(setq test-list '(a b c d e))
(remove-nth 3 test-list)                ; => (a b c e)

Two notes: it requires cl-lib, and it's not terribly efficient, as it walks through the list a few times. The latter is probably only noticeable for long lists.

Here are destructive and non-destructive versions that don't require cl-lib (again, not terribly efficient):

(defun delete-nth (n list)
  "Delete the nth element of a list (destructive)."
  (if (>= n (length list))
      list
    (setf (nthcdr n list) (nthcdr (1+ n) list))))

(defun remove-nth (n list)
  "Remove the nth element of a list."
  (if (>= n (length list))
      list
    (let ((list (copy-tree list)))
      (setf (nthcdr n list) (nthcdr (1+ n) list))
      list)))
2
  • Thank you for the alternative answer which helps teach us how to cl-subseq from the cl-lib library.
    – lawlist
    Commented Jan 3, 2017 at 21:48
  • You beat me with the nthcdr-way;-). Did only see it on the second glance. Deleted my answer...
    – Tobias
    Commented Jan 4, 2017 at 18:11
3

Here's an answer using just recursion. First we check to see if the list is empty, in which case we return the empty list. Then we check to see if we are removing the 0th element of the list, in which case all we want is the cdr of the list. If we haven't hit one of those base cases, we recur by removing the n-1th element of the cdr of the list, and then cons the car of the original list onto the result from the recursive call.

Should be very efficient. Runs in linear time.

(defun remove-nth-element (nth list)
  (cond
   ((equal nil list) list)
   ((zerop nth) (cdr list))
   (t (cons (car list)
            (remove-nth-element (- nth 1)
                                (cdr list))))))

;; Examples:
(remove-nth-element 5 '(0 1 2 3 4 5 6 7))
  ;; '(0 1 2 3 4 6 7)
(remove-nth-element 9 '(0 1 2 3 4 5 6 7))
  ;; '(0 1 2 3 4 5 6 7)
(remove-nth-element 0 '(0 1 2 3 4 5 6 7))
  ;; '(1 2 3 4 5 6 7)
1
  • 1
    This makes n recursive calls (hence uses linear stack space), so I doubt "very efficient" is a good description. And it runs the danger of stack overflow for big n.
    – npostavs
    Commented Mar 17, 2019 at 14:37
2

Surprised to see cl-delete/remove-if wasn't mentioned:

(require 'cl-lib)
(defun delete-nth (nth list) ; Destructive version.
  (cl-delete-if (lambda (_) t) list :start nth :end (1+ nth)))
(defun remove-nth (nth list)
  (cl-remove-if (lambda (_) t) list :start nth :end (1+ nth)))

This is linear time, and should be reasonably efficient, though I expect marginally slower than than wvxvw's answer as it goes through some more generic codepaths.

1

I was not happy with the accepted answer because it doesn't seem to be destructive for nth = 0. I came up with the following:

Non destructive version:

(defun remove-nth-element (num lst)
  (append (seq-take lst num) (seq-drop lst (1+ num))))

seq.el functions are new in emacs 25.1. In older versions you may need to

(require 'seq)

Destructive version, even for nth = 0:

(defun delete-nth-element (num lst)
  (if (zerop num)
      (setf (car lst) (cadr lst)
            (cdr lst) (cddr lst))
    (pop (nthcdr num lst))))
2
  • When manipulating Lisp strings "destructive" usually means "is allowed to mess up the input list as it sees fit". It doesn't force the input list to be modified. You seem to want an operation that's performed "in-place" (i.e. it does not return the answer but instead just modifies its argument), but delete-nth-element cannot work "in-place" in the case where num is 0 and lst is a list of a single element.
    – Stefan
    Commented Apr 11, 2020 at 22:43
  • @Stefan: Thank you for your observation. Indeed delete-nth-element doesn't just delete a single element but rather replaces it with nil, ending up with (nil) instead of expected (). +1.
    – inamist
    Commented Apr 13, 2020 at 2:25

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.