Skip to content

Commit 81e1a91

Browse files
committed
iteration on hash-tables: move all to iteration, enhance with-hash-table-iterator
1 parent e5929ea commit 81e1a91

File tree

2 files changed

+107
-133
lines changed

2 files changed

+107
-133
lines changed

data-structures.md

Lines changed: 15 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,115 +1161,33 @@ NIL
11611161

11621162
<a name="traverse"></a>
11631163

1164-
### Traversing a Hash Table
1164+
### Traversing a Hash Table: `loop`, `maphash`, `with-hash-table-iterator`
11651165

11661166
If you want to perform an action on each entry (i.e., each key-value
11671167
pair) in a hash table, you have several options:
11681168

1169-
You can use
1170-
[`maphash`](http://www.lispworks.com/documentation/HyperSpec/Body/f_maphas.htm)
1171-
which iterates over all entries in the hash table. Its first argument
1172-
must be a function which accepts _two_ arguments, the key and the
1173-
value of each entry. Note that due to the nature of hash tables you
1174-
_can't_ control the order in which the entries are provided by
1175-
`maphash` (or other traversing constructs). `maphash` always returns
1176-
`nil`.
1169+
- of course, `loop`, but also
1170+
- `maphash`
1171+
- `with-hash-table-iterator`
1172+
- as well as alexandria, serapeum and other third-party libraries.
1173+
1174+
=> please see our [iteration page](/cl-cookbook/iteration.html#looping-over-a-hash-table).
11771175

11781176
~~~lisp
1179-
CL-USER> (defparameter *my-hash* (make-hash-table))
1180-
*MY-HASH*
1181-
CL-USER> (setf (gethash 'first-key *my-hash*) 'one)
1182-
ONE
1183-
CL-USER> (setf (gethash 'second-key *my-hash*) 'two)
1184-
TWO
1185-
CL-USER> (setf (gethash 'third-key *my-hash*) nil)
1186-
NIL
1187-
CL-USER> (setf (gethash nil *my-hash*) 'nil-value)
1188-
NIL-VALUE
1189-
CL-USER> (defun print-hash-entry (key value)
1190-
(format t "The value associated with the key ~S is ~S~%"
1191-
key value))
1192-
PRINT-HASH-ENTRY
1193-
CL-USER> (maphash #'print-hash-entry *my-hash*)
1194-
The value associated with the key FIRST-KEY is ONE
1195-
The value associated with the key SECOND-KEY is TWO
1196-
The value associated with the key THIRD-KEY is NIL
1197-
The value associated with the key NIL is NIL-VALUE
1198-
~~~
1199-
1200-
You can also use
1201-
[`with-hash-table-iterator`](http://www.lispworks.com/documentation/HyperSpec/Body/m_w_hash.htm),
1202-
a macro which turns (via
1203-
[`macrolet`](http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm))
1204-
its first argument into an iterator that on each invocation returns
1205-
three values per hash table entry - a generalized boolean that's true
1206-
if an entry is returned, the key of the entry, and the value of the
1207-
entry. If there are no more entries, only one value is returned -
1208-
`nil`.
1209-
1210-
~~~lisp
1211-
;;; same hash-table as above
1212-
CL-USER> (with-hash-table-iterator (my-iterator *my-hash*)
1213-
(loop
1214-
(multiple-value-bind (entry-p key value)
1215-
(my-iterator)
1216-
(if entry-p
1217-
(print-hash-entry key value)
1218-
(return)))))
1219-
The value associated with the key FIRST-KEY is ONE
1220-
The value associated with the key SECOND-KEY is TWO
1221-
The value associated with the key THIRD-KEY is NIL
1222-
The value associated with the key NIL is NIL-VALUE
1223-
NIL
1177+
(loop :for k :being :the :hash-key :of *my-hash-table* :collect k)
1178+
;; (B A)
12241179
~~~
12251180

1226-
Note the following caveat from the HyperSpec: "It is unspecified what
1227-
happens if any of the implicit interior state of an iteration is
1228-
returned outside the dynamic extent of the `with-hash-table-iterator`
1229-
form such as by returning some closure over the invocation form."
1230-
1231-
1232-
And there's always [`loop`](http://www.lispworks.com/documentation/HyperSpec/Body/06_a.htm):
1233-
12341181
~~~lisp
1235-
;;; same hash-table as above
1236-
CL-USER> (loop for key being the hash-keys of *my-hash*
1237-
do (print key))
1238-
FIRST-KEY
1239-
SECOND-KEY
1240-
THIRD-KEY
1241-
NIL
1242-
NIL
1243-
CL-USER> (loop for key being the hash-keys of *my-hash*
1244-
using (hash-value value)
1245-
do (format t "The value associated with the key ~S is ~S~%"
1246-
key value))
1247-
The value associated with the key FIRST-KEY is ONE
1248-
The value associated with the key SECOND-KEY is TWO
1249-
The value associated with the key THIRD-KEY is NIL
1250-
The value associated with the key NIL is NIL-VALUE
1251-
NIL
1252-
CL-USER> (loop for value being the hash-values of *my-hash*
1253-
do (print value))
1254-
ONE
1255-
TWO
1256-
NIL
1257-
NIL-VALUE
1258-
NIL
1259-
CL-USER> (loop for value being the hash-values of *my-hash*
1260-
using (hash-key key)
1261-
do (format t "~&~A -> ~A" key value))
1262-
FIRST-KEY -> ONE
1263-
SECOND-KEY -> TWO
1264-
THIRD-KEY -> NIL
1265-
NIL -> NIL-VALUE
1182+
(maphash (lambda (key val)
1183+
(format t "key: ~A value: ~A~%" key val))
1184+
*my-hash-table*)
1185+
;; =>
1186+
key: A value: 1
1187+
key: B value: 2
12661188
NIL
12671189
~~~
12681190

1269-
#### Traversing keys or values
1270-
1271-
To map over keys or values we can again rely on Alexandria with
1272-
`maphash-keys` and `maphash-values`.
12731191

12741192

12751193
<a name="count"></a>

iteration.md

Lines changed: 92 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -581,86 +581,141 @@ This can be slower than a specific iteration construct.
581581

582582
### Looping over a hash-table
583583

584-
We create a hash-table:
584+
Iterating over a hash-table is possible with `loop` and other
585+
built-ins, with `iterate` and other libraries.
586+
587+
Note that due to the nature of hash tables you
588+
_can't_ control the order in which the entries are provided.
589+
590+
Let's create a hash-table for our fowlling examples:
585591

586592
~~~lisp
587-
(defparameter h (make-hash-table))
588-
(setf (gethash 'a h) 1)
589-
(setf (gethash 'b h) 2)
593+
(defparameter *my-hash-table* (make-hash-table))
594+
(setf (gethash 'a *my-hash-table*) 1)
595+
(setf (gethash 'b *my-hash-table*) 2)
590596
~~~
591597

592-
#### Looping over keys and values
598+
#### `loop`-ing over keys and values
593599

594-
Looping over keys:
600+
To loop over keys, use this:
595601

596602
~~~lisp
597-
(loop for k being the hash-key of h collect k)
603+
(loop :for k :being :the :hash-key :of *my-hash-table* :collect k)
598604
;; (B A)
599605
~~~
600606

601-
Looping over values uses the same concept but with the `hash-value` keyword instead of `hash-key`:
607+
Looping over values uses the same concept but with the `:hash-value` keyword instead of `:hash-key`:
602608

603609
~~~lisp
604-
(loop for v being the hash-value of h collect v)
610+
(loop :for v :being :the :hash-value :of *my-hash-table* :collect v)
605611
;; (2 1)
606612
~~~
607613

608614
Looping over key-values pairs:
609615

610616
~~~lisp
611-
(loop for k being the hash-key
612-
using (hash-value v) of h
613-
collect (list k v))
617+
(loop :for k :being :the :hash-key
618+
:using (hash-value v) :of *my-hash-table*
619+
:collect (list k v))
614620
;; ((B 2) (A 1))
615621
~~~
616622

617-
#### iterate: `in-hashtable`
618623

619-
Use `in-hashtable`:
624+
#### maphash
625+
626+
The lambda function of `maphash` takes two arguments: the key and the
627+
value:
620628

621629
~~~lisp
622-
(iter (for (k v) in-hashtable h)
623-
(collect (list k v)))
624-
;; ((B 2) (A 1))
630+
(maphash (lambda (key val)
631+
(format t "key: ~A value: ~A~%" key val))
632+
*my-hash-table*)
633+
;; =>
634+
key: A value: 1
635+
key: B value: 2
636+
NIL
625637
~~~
626638

627-
#### for
628639

629-
the same with `for`:
640+
#### with-hash-table-iterator
641+
642+
You can also use
643+
[`with-hash-table-iterator`](http://www.lispworks.com/documentation/HyperSpec/Body/m_w_hash.htm),
644+
a macro which turns (via
645+
[`macrolet`](http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm))
646+
its first argument into an iterator that on each invocation returns
647+
three values per hash table entry:
648+
- a generalized boolean that's true if an entry is returned,
649+
- the key of the entry,
650+
- and the value of the entry.
651+
652+
If there are no more entries, only one value is returned, `nil`.
653+
654+
For example:
630655

631656
~~~lisp
632-
(for:for ((it over h))
633-
(print it))
657+
;;; same hash-table as above
658+
CL-USER> (with-hash-table-iterator (my-iterator *my-hash-table*)
659+
(loop
660+
(multiple-value-bind (entry-p key value)
661+
(my-iterator)
662+
(if entry-p
663+
(format t "The value associated with the key ~S is ~S~%" key value)
664+
(return)))))
634665
;; =>
635-
(A 1)
636-
(B 2)
666+
The value associated with the key A is 1
667+
The value associated with the key B is 2
637668
NIL
638669
~~~
639670

671+
Note the following caveat from the HyperSpec:
640672

641-
#### maphash
673+
> It is unspecified what happens if any of the implicit interior state
674+
> of an iteration is returned outside the dynamic extent of the
675+
> `with-hash-table-iterator` form such as by returning some closure
676+
> over the invocation form.
642677
643-
The lambda function of `maphash` takes two arguments: the key and the
644-
value:
678+
#### iterate: `in-hashtable`
679+
680+
Use `in-hashtable`:
645681

646682
~~~lisp
647-
(maphash (lambda (key val)
648-
(format t "key: ~A value: ~A~%" key val))
649-
h)
683+
(iter (for (k v) in-hashtable *my-hash-table*)
684+
(collect (list k v)))
685+
;; ((B 2) (A 1))
686+
~~~
687+
688+
#### Alexandria's `maphash-keys` and `maphash-values`
689+
690+
To map over keys or values (and only keys or only values) we can again
691+
rely on Alexandria with `maphash-keys` and `maphash-values`.
692+
693+
#### Serapeum's `do-hash-table`
694+
695+
The [Serapeum library](https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#hash-tables) has a do-like macro called `do-hash-table`.
696+
697+
(do-hash-table (key value table &optional return) &body body)
698+
699+
700+
#### for
701+
702+
With the `for` library, use the `over` keyword:
703+
704+
~~~lisp
705+
(for:for ((it over *my-hash-table*))
706+
(print it))
650707
;; =>
651-
key: A value: 1
652-
key: B value: 2
708+
(A 1)
709+
(B 2)
653710
NIL
654711
~~~
655712

656-
See also [with-hash-table-iterator](http://www.lispworks.com/documentation/HyperSpec/Body/m_w_hash.htm).
657-
658-
#### dohash
713+
#### trivial-do:dohash
659714

660715
Only because we like this topic, we introduce another library, [trivial-do](https://github.com/yitzchak/trivial-do/). It has the `dohash` macro, that ressembles `dolist`:
661716

662717
~~~lisp
663-
(dohash (key value h)
718+
(dohash (key value *my-hash-table*)
664719
(format t "key: ~A, value: ~A~%" key value))
665720
;; =>
666721
key: A value: 1
@@ -669,8 +724,9 @@ NIL
669724
~~~
670725

671726
#### Series
727+
672728
~~~lisp
673-
(iterate (((k v) (scan-hash h)))
729+
(iterate (((k v) (scan-hash *my-hash-table*)))
674730
(format t "~&~a ~a~%" k v))
675731
;; =>
676732
A 1

0 commit comments

Comments
 (0)