- ht 20161015.1945(in MELPA)
- The missing hash table library for Emacs
概要
あなたはEmacs Lispでハッシュテーブルを使っていますか?
Emacs LispはLispだけに、どうしてもリストに目がいってしまうでしょう。
実際にリストを扱う関数は豊富です。
key/valueペアを扱うにはalistやplistを使うのが普通でしょう。
ハッシュテーブルにはそれらにない特性があります。
ハッシュテーブルの持ち味はなんといっても
高速性
です。
リストは頭から順に探索しますが、
ハッシュテーブルはたとえ巨大なデータであっても一瞬でアクセスできます。
ところが、
Emacs Lispのハッシュテーブルは扱いにくいのが正直なところですよね。
make-hash-table で作成して、
puthash でkey/valueペアを設定して、
gethash でkeyに対応するvalueを得ます。
各ペアに対してループする maphash はmapと名乗ってるくせに
nilを返すという奇妙な仕様…。
これではどうしてもリストに流れてしまうのは仕方ないですね。
ht.el はこの状況をなんとかすべく、使いやすい関数群を用意します。
Rubyプログラマにはおなじみの関数名になっています。
インストール
パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。
(package-initialize) (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/") ("melpa" . "http://melpa.org/packages/") ("org" . "http://orgmode.org/elpa/")))
初めてhtを使う方は
以下のコマンドを実行します。
M-x package-install ht
アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。
M-x package-install package-utils (初めてアップグレードする場合のみ) M-x package-utils-upgrade-by-name ht
基本
使い方は簡単です。
ht マクロでハッシュテーブルを作成し、
ht-get と ht-set でアクセスします。
(setq test-table (ht ("fuga" "hoge") ("test" "table"))) ;; => #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8 data ;; ("fuga" "hoge" "test" "table")) (ht-get test-table "fuga") ; => "hoge" (ht-set test-table "foo" "bar") (ht-get test-table "foo") ; => "bar"
ht-p はハッシュテーブルであるかどうかを検査し、
ht-equal-p は同値性を検査します。
ht-keys と ht-values はそれぞれkeyのリスト、valueのリストを返します。
ht-contains-p はkeyがハッシュテーブルに含まれているかを検査します。
ht-get との違いはvalueがnilかどうかを区別することです。
ht-size はハッシュテーブルのサイズを返します。
ht->alist と ht->plist はそれぞれalist/plistへ変換します。
以後のサンプルではスペース省略のため ht->alist を使うことにします。
ht-clear はハッシュテーブルをクリアします。
(require 'ht) (setq test-table (ht ("foo" 1))) (ht-p test-table) ; => t (ht-equal-p test-table (ht ("foo" 1))) ; => t (ht-update test-table (ht ("bar" 2))) (ht-keys test-table) ; => ("bar" "foo") (ht-values test-table) ; => (2 1) (ht-contains-p test-table "foo") ; => t (ht-contains-p test-table "not-exist") ; => nil (ht-size test-table) ; => 2 (ht->alist test-table) ; => (("bar" . 2) ("foo" . 1)) (ht-items test-table) ; => (("bar" 2) ("foo" 1)) (ht->plist test-table) ; => ("bar" 2 "foo" 1) (ht-clear test-table) (ht->alist test-table) ; => nil
ハッシュテーブルをマージする
ht-merge は2つのハッシュテーブルをマージしたハッシュテーブルを返します。
(setq table1 (ht ("foo" 1))) (setq table2 (ht ("bar" 2))) (ht->alist (ht-merge table1 table2)) ; => (("bar" . 2) ("foo" . 1))
ハッシュテーブルから値を削除する
ht-remove はハッシュテーブルから値を削除します。
(setq test-table (ht ("foo" "bar"))) (ht-remove test-table "foo") (ht->alist test-table) ; => nil
ハッシュテーブルでループする
ht-each と ht-map は各要素でループします。
両者の違いは、ラムダ式のリストを返すかどうかです。
mapcとmapcarの違いと同じです。
また、アナフォリック版の ht-aeach と ht-amap があります。
それらはkeyとvalueというローカル変数が使えるお手軽マクロです。
;;; ht-each / ht-aeach (let ((total 0)) (ht-each (lambda (key value) (setq total (+ total value))) (ht ("foo" 1) ("bar" 2))) total) ; => 3 (let ((total 0)) (ht-aeach (setq total (+ total value)) (ht ("foo" 1) ("bar" 2))) total) ; => 3 ;;; ht-map / ht-amap (ht-map (lambda (key value) (cons key (* value 2))) (ht ("foo" 2) ("bar" 10))) ; => (("bar" . 20) ("foo" . 4)) (ht-amap (cons key (* value 2)) (ht ("foo" 2) ("bar" 10))) ; => (("bar" . 20) ("foo" . 4))
条件を満たす要素のみのハッシュテーブルを返す
ht-select は条件を満たす要素のみで構成された新しいハッシュテーブルを返します。
ht-reject はその逆で、条件を満たさないものです。
ht-delete-if は ht-reject の破壊的バージョンです。
;;; ht-select / ht-reject (ht->alist (ht-select (lambda (key value) (= (% value 2) 0)) (ht ("foo" 1) ("bar" 2) ("baz" 3) ("qux" 4)))) ;; => (("qux" . 4) ;; ("bar" . 2)) (ht->alist (ht-reject (lambda (key value) (= (% value 2) 0)) (ht ("foo" 1) ("bar" 2) ("baz" 3) ("qux" 4)))) ;;; => (("baz" . 3) ;;; ("foo" . 1)) ;;; ht-delete-if (let ((table (ht ("foo" 1) ("bar" 2) ("baz" 3) ("qux" 4)))) (ht-delete-if (lambda (key value) (= (% value 2) 0)) table) ; => nil (ht->alist table)) ; => (("baz" . 3) ("foo" . 1))
ht-find は条件を満たす最初の要素をリストで返します。
(ht-find (lambda (key value) (= (% value 2) 0)) (ht ("baz" 3) ("qux" 4))) ; => ("qux" 4)
結論
このようにht.elはとても便利です。
あなたもht.elでハッシュテーブルを使いこなしてみませんか?
本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。