In this article, we explore the various hash table functions and macros provided by the ht.el library.
The ht.el hash table library for Emacs has been written by Wilfred Hughes. The latest tagged release is version 2.2 and the software is released under the GNU General Public License v3. The source code is available at https://github.com/Wilfred/ht.el. It provides a comprehensive list of hash table operations and a very consistent API. For example, any mutation function will always return nil.
Installation
The Milkypostman’s Emacs Lisp Package Archive (MELPA) and Marmalade repositories have ht.el available for installation. You can add the following command to your Emacs init.el configuration file:
(require ‘package) (add-to-list ‘package-archives ‘(“melpa” . “https://melpa.org/packages/”) t)
You can then run M-x package <RET> ht <RET> to install the ht.el library. If you are using Cask, then you simply add the following code to your Cask file:
(depends-on “ht”)
You will need the ht library in your Emacs environment before using the API functions.
(require ‘ht)
Usage
Let us now explore the various API functions provided by the ht.el library. The ht-create function will return a hash table that can be assigned to a hash table variable. You can also verify that the variable is a hash table using the type-of function as shown below:
(let ((greetings (ht-create))) (type-of greetings)) hash-table
You can add an item to the hash table using the ht-set! function, which takes the hash table, key and value as arguments. The entries in the hash table can be listed using the ht-items function as illustrated below:
(ht-set! hash-table key value) ;; Syntax (ht-items hash-table) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-items greetings)) ((“Adam” “Hello Adam!”))
The keys present in a hash table can be retrieved using the ht-keys function, while the values in a hash table can be obtained using the ht-values function, as shown in the following examples:
(ht-keys hash-table) ;; Syntax (ht-values hash-table) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-keys greetings)) (“Adam”) (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-values greetings)) (“Hello Adam!”)
The “ht-clear!” function can be used to clear all the items in a hash-table. For example:
(ht-clear! hash-table) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-clear! greetings) (ht-items greetings)) nil
An entire hash table can be copied to another hash table using the ht-copy API as shown below:
(ht-copy hash-table) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-items (ht-copy greetings))) ((“Adam” “Hello Adam!”))
The ht-merge function can combine two different hash tables into one. In the following example, the items in the english and numbers hash tables are merged together.
(ht-merge hash-table1 hash-table2) ;; Syntax (let ((english (ht-create)) (numbers (ht-create))) (ht-set! english “a” “A”) (ht-set! numbers “1” “One”) (ht-items (ht-merge english numbers))) ((“1” “One”) (“a” “A”))
You can make modifications to an existing hash table. For example, you can remove an item in the hash table using the ht-remove! function, which takes as input a hash table and a key as shown below:
(ht-remove hash-table key) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-set! greetings “Eve” “Hello Eve!”) (ht-remove! greetings “Eve”) (ht-items greetings)) ((“Adam” “Hello Adam!”))
You can do an in-place modification to an item in the hash table using the ht-update! function. An example is given below:
(ht-update! hash-table key value) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-update! greetings (ht (“Adam” “Howdy Adam!”))) (ht-items greetings)) ((“Adam” “Howdy Adam!”))
A number of predicate functions are available in ht.el that can be used to check for conditions in a hash table. The ht? function checks to see if the input argument is a hash table. It returns t if the argument is a hash table and nil otherwise.
(ht? hash-table) ;; Syntax (ht? nil) nil (let ((greetings (ht-create))) (ht? greetings)) t
You can verify if a key is present in a hash table using the ht-contains? API, which takes a hash table and key as arguments. It returns t if the item exists in the hash table. Otherwise, it simply returns nil.
(ht-contains? hash-table key) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-contains? greetings “Adam”)) t (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-contains? greetings “Eve”)) nil
The ht-empty? function can be used to check if the input hash-table is empty or not. A couple of examples are shown below:
(ht-empty? hash-table) ;; Syntax (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-empty? greetings)) nil (let ((greetings (ht-create))) (ht-empty? greetings)) t
The equality check can be used on a couple of hash tables to verify if they are the same, using the ht-equal? function as illustrated below:
(ht-equal? hash-table1 hash-table2) ;; Syntax (let ((english (ht-create)) (numbers (ht-create))) (ht-set! english “a” “A”) (ht-set! numbers “1” “One”) (ht-equal? english numbers)) nil
A few of the ht.el library functions accept a function as an argument and apply it to the items of the list. For example, the ht-map function takes a function with a key and value as arguments, and applies the function to each item in the hash table. For example:
(ht-map function hash-table) ;; Syntax (let ((numbers (ht-create))) (ht-set! numbers 1 “One”) (ht-map (lambda (x y) (* x 2)) numbers)) (2)
You can also use the ht-each API to iterate through each item in the hash-table. In the following example, the sum of all the values is calculated and finally printed in the output.
(ht-each function hash-table) ;; Syntax (let ((numbers (ht-create)) (sum 0)) (ht-set! numbers “A” 1) (ht-set! numbers “B” 2) (ht-set! numbers “C” 3) (ht-each (lambda (key value) (setq sum (+ sum value))) numbers) (print sum)) 6
The ht-select function can be used to match and pick a specific set of items in the list. For example:
(ht-select function hash-table) ;; Syntax (let ((numbers (ht-create))) (ht-set! numbers 1 “One”) (ht-set! numbers 2 “Two”) (ht-items (ht-select (lambda (x y) (= x 2)) numbers))) ((“2” “Two”))
You can also reject a set of values by passing a filter function to the ht-reject API, and retrieve those items from the hash table that do not match the predicate function. In the following example, key 2 is rejected and the item with key 1 is returned.
(ht-reject function hash-table) ;; Syntax (let ((numbers (ht-create))) (ht-set! numbers 1 “One”) (ht-set! numbers 2 “Two”) (ht-items (ht-reject (lambda (x y) (= x 2)) numbers))) ((“1” “One”))
If you want to mutate the existing hash table and remove the items that match a filter function, you can use the ht-reject! function as shown below:
(ht-reject! function hash-table) ;; Syntax (let ((numbers (ht-create))) (ht-set! numbers 1 “One”) (ht-set! numbers 2 “Two”) (ht-reject! (lambda (x y) (= x 2)) numbers) (ht-items numbers)) ((“1” “One”))
The ht-find function accepts a function and a hash table, and returns the items that satisfy the input function. For example:
(ht-find function hash-table) ;; Syntax (let ((numbers (ht-create))) (ht-set! numbers 1 “One”) (ht-set! numbers 2 “Two”) (ht-find (lambda (x y) (= x 2)) numbers)) (2 “Two”)
You can retrieve the items in the hash table using a specific set of keys with the ht-select-keys API, as illustrated below:
(ht-select-keys hash-table keys) ;; Syntax (let ((numbers (ht-create))) (ht-set! numbers 1 “One”) (ht-set! numbers 2 “Two”) (ht-items (ht-select-keys numbers ‘(1)))) ((1 “One”))
The following two examples are more comprehensive in using the hash table library functions. The say-hello function returns a greeting based on the name as shown below:
(defun say-hello (name) (let ((greetings (ht-create))) (ht-set! greetings “Adam” “Hello Adam!”) (ht-set! greetings “Eve” “Hello Eve!”) (ht-get greetings name “Hello stranger!”))) (say-hello “Adam”) “Hello Adam!” (say-hello “Eve”) “Hello Eve!” (say-hello “Bob”) “Hello stranger!”
The ht macro returns a hash table and we create nested hash tables in the following example:
(let ((alphabets (ht (“Greek” (ht (1 (ht (‘letter “α”) (‘name “alpha”))) (2 (ht (‘letter “β”) (‘name “beta”))))) (“English” (ht (1 (ht (‘letter “a”) (‘name “A”))) (2 (ht (‘letter “b”) (‘name “B”)))))))) (ht-get* alphabets “Greek” 1 ‘letter)) “α”
Testing
The ht.el library has built-in tests that you can execute to validate the API functions. You first need to clone the repository using the following commands:
$ git clone git@github.com:Wilfred/ht.el.git Cloning into ‘ht.el’... remote: Enumerating objects: 1, done. remote: Counting objects: 100% (1/1), done. Receiving objects: 100% (471/471), 74.58 KiB | 658.00 KiB/s, done. remote: Total 471 (delta 0), reused 1 (delta 0), pack-reused 470 Resolving deltas: 100% (247/247), done.
If you do not have Cask, install the same using the instructions provided in the README file at https://github.com/cask/cask.
You can then change the directory into the cloned ‘ht.el’ folder and run cask install. This will locally install the required dependencies for running the tests.
$ cd ht.el/ $ cask install Loading package information... Select coding system (default utf-8): done Package operations: 4 installs, 0 removals - Installing [ 1/4] dash (2.12.0)... done - Installing [ 2/4] ert-runner (latest)... done - Installing [ 3/4] cl-lib (latest)... already present - Installing [ 4/4] f (latest)... already present
A Makefile exists in the top-level directory and you can simply run ‘make’ to run the tests, as shown below:
$ make rm -f ht.elc make unit make[1]: Entering directory ‘/home/guest/ht.el’ cask exec ert-runner ......................................... Ran 41 tests in 0.016 seconds make[1]: Leaving directory ‘/home/guest/ht.el’ make compile make[1]: Entering directory ‘/home/guest/ht.el’ cask exec emacs -Q -batch -f batch-byte-compile ht.el make[1]: Leaving directory ‘/home/guest/ht.el’ make unit make[1]: Entering directory ‘/home/guest/ht.el’ cask exec ert-runner ......................................... Ran 41 tests in 0.015 seconds make[1]: Leaving directory ‘/home/guest/ht.el’ make clean-elc make[1]: Entering directory ‘/home/guest/ht.el’ rm -f ht.elc make[1]: Leaving directory ‘/home/guest/ht.el’
You are encouraged to read the ht.el README file from the GitHub repository at https://github.com/Wilfred/ht.el/blob/master/README.md for more information.