Skip to content

Adding Values to Hash Table

Hash tables are a fundamental and highly efficient data structure in Common Lisp for storing key-value pairs. They provide near constant-time performance for lookups, insertions, and deletions. This guide covers creating, manipulating, and effectively using hash tables in modern Lisp projects.

You create a hash table using make-hash-table. A crucial argument is :test, which specifies the function used to compare keys. The choice of :test is critical for correctness.

  • 'eq: Compares if two objects are the same in memory. Use for symbols or objects you know are identical.
  • 'eql (The default): Compares objects using eq, but also works for identical numbers and characters.
  • 'equal: Compares if two objects have a similar structure and content. Use this for string keys.
  • 'equalp: A more lenient version of equal, ignoring case in strings and comparing numeric types.
;; A hash table for symbol keys (default :test is 'eql)
(defvar *user-ids* (make-hash-table))
;; A hash table for string keys (best practice is :test 'equal)
(defvar *user-prefs* (make-hash-table :test 'equal))

The standard way to add or update a value in a hash table is by using setf with gethash. If the key already exists, its value is updated; otherwise, a new key-value pair is created.

;; gethash retrieves the value for a key. If the key doesn't exist, it returns nil.
;; setf modifies the place, associating the new value with the key.
(setf (gethash 'user-001 *user-ids*) 1024)
(setf (gethash "username" *user-prefs*) "alex")
(setf (gethash "theme" *user-prefs*) "dark")

gethash is used to retrieve a value. It returns two values: the found value (or nil if not found), and a boolean t or nil indicating whether the key was present. Checking the second return value is the only reliable way to distinguish between a stored value of nil and a key that does not exist.

;; Simple retrieval
(gethash 'user-001 *user-ids*) ;=> 1024, t
;; Retrieving a non-existent key
(gethash 'user-999 *user-ids*) ;=> NIL, NIL
;; Handling the two return values properly
(multiple-value-bind (value foundp) (gethash "theme" *user-prefs*)
(if foundp
(format t "Theme is: ~a~%" value)
(format t "No theme set.~%")))

Use remhash to remove a key-value pair from a hash table. It returns t if the key was found and removed, and nil otherwise.

(remhash 'user-001 *user-ids*) ;=> t

Let’s build a function that counts word frequencies in a piece of text, a classic use case for hash tables.

(defun word-frequency (text)
"Counts the frequency of each word in a text string."
(let ((counts (make-hash-table :test 'equalp)) ; 'equalp for case-insensitivity
(words (str:words (str:downcase text)))) ; Using the 'str' library
(dolist (word words)
;; (incf (gethash word counts 0)) is a concise way to increment
;; the count. If the key doesn't exist, (gethash ...) returns the
;; default value 0, which is then incremented to 1 and stored.
(incf (gethash word counts 0)))
counts))
;; Example Usage (requires ql:quickload "str")
(let* ((text "To be or not to be, that is the question.")
(freqs (word-frequency text)))
(maphash (lambda (key value)
(format t "~a: ~a~%" key value))
freqs))
QUESTION: 1
THE: 1
IS: 1
THAT: 1
BE: 2
NOT: 1
OR: 1
TO: 2
  • Wrong :test function: The most common error. Using the default 'eql for string keys will fail because two identical-looking strings are not necessarily eql. Always use 'equal or 'equalp for string keys.
  • Assuming gethash returns only one value: Forgetting to check the second return value (foundp) can lead to bugs when a key’s value is legitimately nil.