I am trying to write a function that adds data into a hash table. The function takes a key and a value. If the given key already exists in the table, the given value is appended to the existing values associated with the key. If the key does not yet exist in the table, the value is placed in a list and added to the table. Here is the implementation:
(defparameter *ht* (make-hash-table))
(defun add-to-table (key value)
(multiple-value-bind (existing-values present-p) (gethash key *ht*)
(if present-p
(push value existing-values)
(setf (gethash key *ht*) (list value)))))
I tried to use the function like this:
(add-to-table 'company "Acme")
(add-to-table 'company "Ajax")
However, (length (gethash 'company *ht*))
returns 1
instead of 2
, even though I added two companies. Why is that?
I found that I could solve the problem by replacing (push value existing-values)
with (push value (gethash key *ht*))
. Why do I need to use another (gethash key *ht*)
when I have already obtained its value (existing-values
)? Isn't existing-values
supposed to be some kind of pointer to the list?
When you push on existing-values
, you only modify the local variable.
You can do this much simpler by using the place magic in a single form:
(defun add-to-table (key value)
(push value (gethash key *ht* nil)))
This sees the chain of gethash and list access as a place, so that the push works in both cases.
You can express this explicitly by first ensuring the existence of the hash-table entry:
(defun add-to-table (key value)
(unless (nth-value 1 (gethash key *ht*))
(setf (gethash key *ht*) ()))
(push value (gethash key *ht*)))
Also see ensure-gethash
in the alexandria
utility library for a more general construct.