Common Lisp: shorthand to initialize a hash table with many entries
Asked Answered
O

4

10

I'm looking for a possibly non-verbose portable way to initialize a hash table in Common Lisp. E.g. something that works for constant hash tables, but also to pre-load variable hashes. In CLISP I am using:

(defconstant +my-map+ #S(HASH-TABLE :TEST FASTHASH-EQ
  (key1 . "value1")
  ...
  (keyN . "valueN")
))

but unfortunately this format only works on CLISP.

Obscene answered 22/5, 2012 at 16:19 Comment(3)
github.com/vseloved/rutils/blob/master/core/readtable.lisp#L10Innervate
Note that e.g. aiai.ed.ac.uk/~jeff/lisp/cl-pitfalls warns against using hash tables as values for defconstant forms.Viceregal
Thanks to all. It seems this fundamental feature is lacking in the standard and must be added in some way. Rather than introducing new syntax, mimicking Perl or PHP, what do you think about writing a macro that wraps make-hash-table and adds the option :initial-contents, the same option supported in the standard by, e.g., make-array? I think this won't probably be very efficient, since content would be specified by an alist that has to be traversed, but it is at least more consistent with Lisp syntax.Westsouthwest
S
9

One can programmatically construct a hash table at read time:

(defvar *ht* #.(let ((ht (make-hash-table)))
                 (loop for (key . value) in
                       '((a . 1) (b . 2) (c . 3))
                       do (setf (gethash key ht) value))
                 ht))

(describe *ht*)

#. is used for read time evaluation. The compiler then will dump the hash table to the FASL file.

This can then be compiled:

Using SBCL:

* (compile-file "/tmp/test.lisp")

; compiling file "/private/tmp/test.lisp" (written 24 MAY 2012 10:08:49 PM):
; compiling (DEFVAR *HT* ...)
; compiling (DESCRIBE *HT*)

; /tmp/test.fasl written
; compilation finished in 0:00:00.360
#P"/private/tmp/test.fasl"
NIL
NIL
* (load *)

#<HASH-TABLE :TEST EQL :COUNT 3 {100299EA43}>
  [hash-table]

Occupancy: 0.2
Rehash-threshold: 1.0
Rehash-size: 1.5
Size: 16
Synchronized: no
T
* *ht*

#<HASH-TABLE :TEST EQL :COUNT 3 {100299EA43}>

Creating a hash table as a function:

(defun create-hashtable (alist
                         &key (test 'eql)
                         &aux (ht (make-hash-table :test test)))
  (loop for (key . value) in alist
        do (setf (gethash key ht) value))
  ht)
Stockroom answered 24/5, 2012 at 20:24 Comment(4)
Thanks a lot Reiner! The only drawback is that is it a bit verbose, but that can be helped using a macro. I am a Lisp beginner and not very good at macros. Anyway, here's mine: (defmacro ini-hash-table (pairs) (let ((hash (make-hash-table :test 'equal))) (loop for (key value) on ,pairs by #'cddr do (setf (gethash key hash) value)) hash))` Then I do: (defvar *ht* #.(ini-hash-table '(a 1 b 2 c 3)))Westsouthwest
@AntonioBonifati: when i doubt write a function, not a macro. There is no reason it should be macro, is it?Stockroom
Yes, thanks, I know, a good reason for that is that macros are harder to write and debug. But if I write a function in this case, I cannot call it with #. At least in ECL it tells me it is undefined. I think this is the effect of #. that is any user defined function is not available at read-time. I wonder if a macro wrapper can be written that allows one to fully configure make-hash-table while adding an :initial-contents keyword parameter. That would "add this feature to the standard" :)Westsouthwest
@Antonio Bonifati. If you define the function in the same file, then you can make it available to the compiler by using an eval-when clause.Stockroom
G
2

Alexandria has the alist-hash-table function, which you may find useful.

Generic answered 23/5, 2012 at 5:41 Comment(0)
P
1

The Serapeum library has dict:

(dict :a 1 :b 2 :c 3)
#<HASH-TABLE :TEST EQUAL :COUNT 3 {1008906D13}>

You can pretty print hash tables:

CL-USER> (toggle-pretty-print-hash-table)
T
CL-USER> (dict :a 1 :b 2 :c 3)
(dict
  :A 1
  :B 2
  :C 3
 ) 

which is a representation that can be read back in.

We can also use pretty-print-hash-table directly.

Serapeum is a high quality library.

ps: my CIEL meta-package makes dict available by default.

Paleethnology answered 2/9, 2021 at 9:49 Comment(0)
T
0

Wow, a question from 9 years ago getting another answer. Coincidentally make-hash came out 2 months after this question was asked and is generally useful for this kind of thing.

Temperamental answered 3/9, 2021 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.