Skip to content

LISP - Vectors

Vectors are one-dimensional arrays and, along with lists, are a fundamental sequence type in Common Lisp. Unlike lists, which are optimized for sequential access and modification at the front, vectors provide fast, constant-time (O(1)) random access to their elements. This makes them ideal for collections where you need to look up elements by index.

There are several ways to create vectors, each suited to different needs.

The simplest way to create a vector with predefined elements is with the vector function. It takes any number of arguments and returns a new vector containing them.

(let ((v1 (vector 1 2 3 4 5))
(v2 (vector 'a "hello" t)))
(format t "Numeric vector: ~a~%" v1)
(format t "Mixed vector: ~a~%" v2))
Numeric vector: #(1 2 3 4 5)
Mixed vector: #(A "hello" T)

Lisp provides a literal syntax for vectors: #( ... ). This is convenient for embedding constant vector data directly in your code.

Warning: According to the standard, you should never modify a literal object. Doing so can lead to undefined behavior, as the Lisp system may store literals in read-only memory. For vectors you intend to modify, always use vector or make-array.

(defvar *my-constant-vector* #(A B C))
(write *my-constant-vector*)

make-array is the most general and powerful function for creating arrays, including vectors. For a vector, you provide a single dimension.

;; Create a vector of size 5, with each element initialized to 0.
(let ((zero-vec (make-array 5 :initial-element 0)))
(format t "Zero-initialized vector: ~a~%" zero-vec))
;; Create a vector from an existing list of contents
(let ((from-list (make-array 3 :initial-contents '(10 20 30))))
(format t "Vector from list: ~a~%" from-list))
Zero-initialized vector: #(0 0 0 0 0)
Vector from list: #(10 20 30)

A key feature of Lisp vectors is the fill pointer. A vector with a fill pointer acts like a dynamic array or list from other languages (e.g., C++ std::vector or Python list). It maintains a length independent of its total allocated capacity, allowing for efficient additions to the end.

You create one by passing the :fill-pointer argument to make-array.

  • vector-push: Adds an element to the end of the vector and increments the fill pointer. It returns the index where the element was added, or nil if the vector is full.
  • vector-pop: Removes and returns the last element (at the fill pointer) and decrements the fill pointer.
;; Create a vector with a capacity of 5 but an initial length of 0
(let ((dyn-vec (make-array 5 :fill-pointer 0)))
(format t "Initial: ~a (Length: ~a)~%" dyn-vec (length dyn-vec))
;; Push some elements
(vector-push 'a dyn-vec)
(vector-push 'b dyn-vec)
(vector-push 'c dyn-vec)
(format t "After 3 pushes: ~a (Length: ~a)~%" dyn-vec (length dyn-vec))
;; Pop an element
(let ((popped-item (vector-pop dyn-vec)))
(format t "Popped '~a'. Vector is now: ~a (Length: ~a)~%"
popped-item dyn-vec (length dyn-vec)))
;; Fill the rest of the capacity
(vector-push 'd dyn-vec)
(vector-push 'e dyn-vec)
(format t "Filled to capacity: ~a~%" dyn-vec)
;; This push will fail because the vector is full
(let ((result (vector-push 'f dyn-vec)))
(format t "Attempting to push past capacity returned: ~a~%" result)))
Initial: #() (Length: 0)
After 3 pushes: #(A B C) (Length: 3)
Popped 'C'. Vector is now: #(A B) (Length: 2)
Filled to capacity: #(A B D E)
Attempting to push past capacity returned: NIL

Pro Tip: For truly dynamic, resizable vectors, use make-array with both :fill-pointer and :adjustable t. This allows you to use vector-push-extend (a common utility, often from a library) which automatically resizes the vector’s capacity when it becomes full.