Skip to content

LISP - Removing elements from List

Common Lisp provides a rich set of functions for removing elements from lists. A critical distinction is made between non-destructive functions, which return a new list, and destructive functions, which may modify the original list. Understanding this difference is key to writing safe and predictable code.

Non-Destructive Removal: remove, remove-if, remove-if-not

Section titled “Non-Destructive Removal: remove, remove-if, remove-if-not”

Non-destructive functions are generally safer to use as they do not have side effects. They return a new list, leaving the original list untouched.

The remove function creates a new list with all occurrences of a specific item removed.

(defparameter *my-list* '(a b c b a))
;; Remove all 'b's. The original list is unaffected.
(defparameter *new-list* (remove 'b *my-list*))
(format t "Original List: ~s~%" *my-list*)
(format t "New List: ~s~%" *new-list*)
Original List: (A B C B A)
New List: (A C A)

You can limit the number of removed items with the :count keyword.

;; Remove only the first occurrence of 'b'
(print (remove 'b *my-list* :count 1))
(A C B A)

remove-if: Removing Elements that Match a Predicate

Section titled “remove-if: Removing Elements that Match a Predicate”

The remove-if function removes elements for which a given predicate (a function that returns true or false) returns true.

(defparameter *numbers* '(1 2 3 4 5 6))
;; Remove all even numbers using the `evenp` predicate.
(print (remove-if #'evenp *numbers*))
(1 3 5)

remove-if-not: Removing Elements that Don’t Match a Predicate

Section titled “remove-if-not: Removing Elements that Don’t Match a Predicate”

Conversely, remove-if-not removes all elements for which the predicate returns false (i.e., it keeps only the elements that satisfy the predicate).

(defparameter *numbers* '(1 2 3 4 5 6))
;; Keep only the even numbers.
(print (remove-if-not #'evenp *numbers*))
(2 4 6)

The removal functions can be made even more powerful with the :key and :test keyword arguments.

  • :key: Specifies a function to be called on each element before it is tested. This is useful for working with complex data structures.
  • :test: Specifies a function to use for comparison instead of the default eql.
;; A list of (name score) pairs
(defparameter *scores* '((alice 88) (bob 95) (charlie 72)))
;; Remove any entry where the name is 'bob'
(print (remove 'bob *scores* :key #'first))
((ALICE 88) (CHARLIE 72))
(defparameter *numbers* '(10 25 5 40 15))
;; Remove all numbers GREATER THAN 20.
;; The test #'> checks if element > item, i.e., element > 20
(print (remove 20 *numbers* :test #'>))
(10 5 15)

Use with caution! The delete family of functions (delete, delete-if, etc.) are permitted to modify the original list structure to remove the elements. This can be more memory-efficient but is dangerous if other parts of your code hold a reference to the original list.

;; NOTE: We use `defvar` here because the list itself will be modified.
(defvar *my-list* (list 'a 'b 'c 'b 'a))
(format t "List before delete: ~s~%" *my-list*)
;; `delete` returns the modified list, but also changes *my-list*
(delete 'b *my-list*)
(format t "List after delete: ~s~%" *my-list*)
List before delete: (A B C B A)
List after delete: (A C A)

Best Practice: When to Use Destructive Functions

Section titled “Best Practice: When to Use Destructive Functions”

Prefer non-destructive functions like remove by default. Only use destructive functions like delete in performance-critical sections of code where you are certain that no other part of the program holds a reference to the list being modified.