5. Hashes

Introduction

;;;-----------------------------
(setf age (make-hash-table :test 'equal))

(setf (gethash "Nat" age) 24
      (gethash "Jules" age) 25
      (gethash "Josh" age) 17)
;;-----------------------------
(mapcar #'(lambda (l)
            (setf (gethash (car l) age) (cdr l)))
        '(("Nat" . 24)
          ("Jules" . 25)
          ("Josh" . 17)))
;;-----------------------------
(defparameter *food-color* (make-hash-table :test 'equal))

(mapcar #'(lambda (l)
            (setf (gethash (car l) *food-color*) (cdr l)))
        '(("Apple" . "red")
          ("Banana" . "yellow")
          ("Lemon" . "yellow")
          ("Carrot" . "orange")))
;;;-----------------------------
(mapcar #'(lambda (l)
            (setf (gethash (car l) *food-color*) (cdr l)))
        '((Apple . "red")
          (Banana . "yellow")
          (Lemon . "yellow")
          (Carrot . "orange")))
;;;-----------------------------

Adding an Element to a Hash

;;;-----------------------------
(setf (gethash key hash) value)
;;;-----------------------------
;; *FOOD-COLOR* defined per the introduction
(setf (gethash "Raspberry" *food-color*) "pink")

(format t "Known foods:~%~{~A~%~}"
        (loop for f being the hash-keys of *food-color*
           collect f))
;;Known foods:
;;Apple
;;Banana
;;Lemon
;;Carrot
;;;-----------------------------

Testing for the Presence of a Key in a Hash

;;;-----------------------------
;; does HASH have a value for KEY ?
(if (nth-value 1 (gethash key hash))
    (progn
      ;; it exists
      )
    (progn
      ;; it doesn't
      ))
;;;-----------------------------
;; *FOOD-COLOR* per the introduction
(dolist (name '("Banana" "Martini"))
  (format t "~A is a ~A.~%"
          name
          (if (nth-value 1 (gethash name *food-color*))
              "food" "drink")))

;;Banana is a food.
;;Martini is a drink.
;;;-----------------------------
(setf age (make-hash-table :test 'equal))
(setf (gethash "Toddler" age) 3)
(setf (gethash "Unborn" age) 0)
(setf (gethash "Phantasm" age) nil)
(dolist (thing '("Toddler" "Unborn" "Phantasm" "Relic"))
  (format t "~a: " thing)
  (multiple-value-bind (defined exists)
      (gethash thing age)
      (when exists
        (format t "Exists ")
        (when defined 
          (format t "Defined ")
          ;; 0 is "true" in CL, so explicitly mimic Perl
          (unless (zerop defined)
            (format t "True ")))))
  (format t "~%"))

;;Toddler: Exists Defined True 
;;Unborn: Exists Defined 
;;Phantasm: Exists 
;;Relic: 
;;;-----------------------------
;;; @@INCOMPLETE@@

Deleting from a Hash

;;;-----------------------------
;; remove KEY and its value from HASH
(remhash key hash)
;;;-----------------------------
;; *FOOD-COLOR* as per Introduction
(defun print-foods ()
  (let ((foods (hash-keys *food-color*))) ; HASH-KEYS defined in Appendix
    (format t "Keys: ~{~A~^ ~}~%Values: ~{~A~^ ~}~%" 
            foods
            (loop for food in foods
               collect (or (gethash food *food-color*)
                           "(undef)")))))

(format t "Initially~%")
(print-foods)

(format t "~%With Banana undef~%")
(setf (gethash "Banana" *food-color*) 

(format t "~%With Banana deleted~%")
(remhash "Banana" *food-color*)
(print-foods)

;; Initially
;; Keys: Apple Banana Lemon Carrot
;; Values: red yellow yellow orange
;;
;; With Banana undef
;; Keys: Apple Banana Lemon Carrot
;; Values: red (undef) yellow orange
;; 
;; With Banana deleted
;; Keys: Apple Lemon Carrot
;; Values: red yellow orange
;;;-----------------------------
(mapc #'(lambda (key) (remhash key *food-color*)) '("Banana" "Apple" "Cabbage"))
;;;-----------------------------
    

Traversing a Hash

;;;-----------------------------
(loop for key being the hash-keys of hash using (hash-value value)
     ;; do something with KEY and VALUE
     )
;;;-----------------------------
(maphash #'(lambda (key value)
             ;; do something with KEY and VALUE
             )
         hash)
;;;-----------------------------
;; *FOOD-COLOR* per the introduction
(loop for food being the hash-keys of *food-color* using (hash-value color)
     do (format t "~A is ~A.~%" food color))
;; Apple is red.
;; Banana is yellow.
;; Lemon is yellow.
;; Carrot is orange.
;;;-----------------------------
(maphash #'(lambda (food color)
             (format t "~A is ~A.~%" food color))
         *food-color*)
;; Apple is red.
;; Banana is yellow.
;; Lemon is yellow.
;; Carrot is orange.
;;;-----------------------------
;; No equivalent
;;;-----------------------------
(loop for food in (sort (hash-keys *food-color*) 'string-lessp)
   do (format t "~A is ~A~%" food (gethash food *food-color*)))
;; Apple is red
;; Banana is yellow
;; Carrot is orange
;; Lemon is yellow
;;;-----------------------------
;; Not sure what the following Perl is supposed to do:
;;while ( ($k,$v) = each %food_color ) {
;;    print "Processing $k\n";
;;    keys %food_color;               # goes back to the start of %food_color
;;}
;;;-----------------------------
(use-package :cl-ppcre)
(use-package :iterate)

;; The following handles the case that the Perl handles where there is
;; no filename (and it then opens '-'.  To do the same thing you'd do
;; something like: (countfrom *standard-input*) and this method would
;; automatically get triggered instead of the one requiring a
;; filename.
(defmethod countfrom ((stream stream))
  (let ((from (make-hash-table :test 'equal)))
    (with-open-stream (input stream)
      (iter (for line in-stream input using 'read-line)
            (register-groups-bind (person) ("^From: (.*)\\s" line)
              (incf (gethash person from 0)))))
    (loop for person in (sort (hash-keys from) 'string-lessp)
       do (format t "~A: ~A~%" person (gethash person from)))))

;; This method is a bit of a hack in that it shouldn't really assume
;; that the string designates a filename, but for the purposes of this
;; example that seems ok.  Note that it just calls OPEN directly
;; without WITH-OPEN-FILE because it knows that the STREAM version of
;; this method will always close it.
(defmethod countfrom ((filename string))
  (countfrom (open filename)))
;;;-----------------------------

Printing a Hash

Retrieving from a Hash in Insertion Order

Hashes with Multiple Values Per Key

Inverting a Hash

Sorting a Hash

Merging Hashes

Finding Common or Different Keys in Two Hashes

Hashing References

Presizing a Hash

Finding the Most Common Anything

Representing Relationships Between Data

Program: dutree