8

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
6

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)
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)))
  • Thank you for the alternative answer which helps teach us how to cl-subseq from the cl-lib library. – lawlist Jan 3 '17 at 21:48
  • You beat me with the nthcdr-way;-). Did only see it on the second glance. Deleted my answer... – Tobias Jan 4 '17 at 18:11
3

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)
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
    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 Mar 17 at 14:37
3

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
    Neat trick. Using (cons nil nil) is a bit cheaper than gensym since you don't actually need a symbol here. – npostavs Mar 17 at 14:20
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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