ht.el (11117B)
1 ;;; ht.el --- The missing hash table library for Emacs -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2013 Wilfred Hughes 4 5 ;; Author: Wilfred Hughes <me@wilfred.me.uk> 6 ;; Version: 2.4 7 ;; Keywords: hash table, hash map, hash 8 ;; Package-Requires: ((dash "2.12.0")) 9 10 ;; This program is free software; you can redistribute it and/or modify 11 ;; it under the terms of the GNU General Public License as published by 12 ;; the Free Software Foundation, either version 3 of the License, or 13 ;; (at your option) any later version. 14 15 ;; This program is distributed in the hope that it will be useful, 16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; GNU General Public License for more details. 19 20 ;; You should have received a copy of the GNU General Public License 21 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 22 23 ;;; Commentary: 24 25 ;; The missing hash table library for Emacs. 26 ;; 27 ;; See documentation at https://github.com/Wilfred/ht.el 28 29 ;;; Code: 30 31 (require 'dash) 32 (require 'gv) 33 (eval-when-compile 34 (require 'inline)) 35 36 (defmacro ht (&rest pairs) 37 "Create a hash table with the key-value pairs given. 38 Keys are compared with `equal'. 39 40 \(fn (KEY-1 VALUE-1) (KEY-2 VALUE-2) ...)" 41 (let* ((table-symbol (make-symbol "ht-temp")) 42 (assignments 43 (mapcar 44 (lambda (pair) `(ht-set! ,table-symbol ,@pair)) 45 pairs))) 46 `(let ((,table-symbol (ht-create))) 47 ,@assignments 48 ,table-symbol))) 49 50 (define-inline ht-set! (table key value) 51 "Associate KEY in TABLE with VALUE." 52 (inline-quote 53 (prog1 nil 54 (puthash ,key ,value ,table)))) 55 56 (defalias 'ht-set 'ht-set!) 57 58 (define-inline ht-create (&optional test) 59 "Create an empty hash table. 60 61 TEST indicates the function used to compare the hash 62 keys. Default is `equal'. It can be `eq', `eql', `equal' or a 63 user-supplied test created via `define-hash-table-test'." 64 (declare (side-effect-free t)) 65 (inline-quote (make-hash-table :test (or ,test 'equal)))) 66 67 (defun ht<-alist (alist &optional test) 68 "Create a hash table with initial values according to ALIST. 69 70 TEST indicates the function used to compare the hash 71 keys. Default is `equal'. It can be `eq', `eql', `equal' or a 72 user-supplied test created via `define-hash-table-test'." 73 (declare (side-effect-free t)) 74 (let ((h (ht-create test))) 75 ;; the first key-value pair in an alist gets precedence, so we 76 ;; start from the end of the list: 77 (dolist (pair (reverse alist) h) 78 (let ((key (car pair)) 79 (value (cdr pair))) 80 (ht-set! h key value))))) 81 82 (defalias 'ht-from-alist 'ht<-alist) 83 84 (defun ht<-plist (plist &optional test) 85 "Create a hash table with initial values according to PLIST. 86 87 TEST indicates the function used to compare the hash 88 keys. Default is `equal'. It can be `eq', `eql', `equal' or a 89 user-supplied test created via `define-hash-table-test'." 90 (declare (side-effect-free t)) 91 (let ((h (ht-create test))) 92 (dolist (pair (nreverse (-partition 2 plist)) h) 93 (let ((key (car pair)) 94 (value (cadr pair))) 95 (ht-set! h key value))))) 96 97 (defalias 'ht-from-plist 'ht<-plist) 98 99 (define-inline ht-get (table key &optional default) 100 "Look up KEY in TABLE, and return the matching value. 101 If KEY isn't present, return DEFAULT (nil if not specified)." 102 (declare (side-effect-free t)) 103 (inline-quote 104 (gethash ,key ,table ,default))) 105 106 ;; Don't use `ht-set!' here, gv setter was assumed to return the value 107 ;; to be set. 108 (gv-define-setter ht-get (value table key) `(puthash ,key ,value ,table)) 109 110 (define-inline ht-get* (table &rest keys) 111 "Look up KEYS in nested hash tables, starting with TABLE. 112 The lookup for each key should return another hash table, except 113 for the final key, which may return any value." 114 (declare (side-effect-free t)) 115 (inline-letevals (table keys) 116 (inline-quote 117 (progn 118 (while ,keys 119 (setf ,table (ht-get ,table (pop ,keys)))) 120 ,table)))) 121 122 (put 'ht-get* 'compiler-macro 123 (lambda (_ table &rest keys) 124 (--reduce-from `(ht-get ,acc ,it) table keys))) 125 126 (defun ht-update! (table from-table) 127 "Update TABLE according to every key-value pair in FROM-TABLE." 128 (maphash 129 (lambda (key value) (puthash key value table)) 130 from-table) 131 nil) 132 133 (defalias 'ht-update 'ht-update!) 134 135 (define-inline ht-update-with! (table key updater &optional default) 136 "Update the value of KEY in TABLE with UPDATER. 137 If the value does not exist, do nothing, unless DEFAULT is 138 non-nil, in which case act as if the value is DEFAULT. 139 140 UPDATER receives one argument, the value, and its return value 141 becomes the new value of KEY." 142 (inline-quote 143 (let* ((not-found-symbol (make-symbol "ht--not-found")) 144 (v (gethash ,key ,table 145 (or ,default not-found-symbol)))) 146 (unless (eq v not-found-symbol) 147 (prog1 nil 148 (puthash ,key (funcall ,updater v) ,table)))))) 149 150 (defun ht-merge (&rest tables) 151 "Crete a new table that includes all the key-value pairs from TABLES. 152 If multiple tables have the same key, the value in the last 153 table is used." 154 (let ((merged (ht-create))) 155 (mapc (lambda (table) (ht-update! merged table)) tables) 156 merged)) 157 158 (define-inline ht-remove! (table key) 159 "Remove KEY from TABLE." 160 (inline-quote (remhash ,key ,table))) 161 162 (defalias 'ht-remove 'ht-remove!) 163 164 (define-inline ht-clear! (table) 165 "Remove all keys from TABLE." 166 (inline-quote 167 (prog1 nil 168 (clrhash ,table)))) 169 170 (defalias 'ht-clear 'ht-clear!) 171 172 (defun ht-map (function table) 173 "Apply FUNCTION to each key-value pair of TABLE, and make a list of the results. 174 FUNCTION is called with two arguments, KEY and VALUE." 175 (let (results) 176 (maphash 177 (lambda (key value) 178 (push (funcall function key value) results)) 179 table) 180 results)) 181 182 (defmacro ht-amap (form table) 183 "Anaphoric version of `ht-map'. 184 For every key-value pair in TABLE, evaluate FORM with the 185 variables KEY and VALUE bound. If you don't use both of 186 these variables, then use `ht-map' to avoid warnings." 187 `(ht-map (lambda (key value) ,form) ,table)) 188 189 (defun ht-keys (table) 190 "Return a list of all the keys in TABLE." 191 (declare (side-effect-free t)) 192 (ht-map (lambda (key _value) key) table)) 193 194 (defun ht-values (table) 195 "Return a list of all the values in TABLE." 196 (declare (side-effect-free t)) 197 (ht-map (lambda (_key value) value) table)) 198 199 (defun ht-items (table) 200 "Return a list of two-element lists \\='(key value) from TABLE." 201 (declare (side-effect-free t)) 202 (ht-amap (list key value) table)) 203 204 (defalias 'ht-each 'maphash 205 "Apply FUNCTION to each key-value pair of TABLE. 206 Returns nil, used for side-effects only.") 207 208 (defmacro ht-aeach (form table) 209 "Anaphoric version of `ht-each'. 210 For every key-value pair in TABLE, evaluate FORM with the 211 variables key and value bound." 212 `(ht-each (lambda (key value) ,form) ,table)) 213 214 (defun ht-select-keys (table keys) 215 "Return a copy of TABLE with only the specified KEYS." 216 (declare (side-effect-free t)) 217 (let ((not-found-symbol (make-symbol "ht--not-found")) 218 result) 219 (setq result (make-hash-table :test (hash-table-test table))) 220 (dolist (key keys result) 221 (if (not (equal (gethash key table not-found-symbol) not-found-symbol)) 222 (puthash key (gethash key table) result))))) 223 224 (defun ht->plist (table) 225 "Return a flat list \\='(key1 value1 key2 value2...) from TABLE. 226 227 Note that hash tables are unordered, so this cannot be an exact 228 inverse of `ht<-plist'. The following is not guaranteed: 229 230 \(let ((data \\='(a b c d))) 231 (equalp data 232 (ht->plist (ht<-plist data))))" 233 (declare (side-effect-free t)) 234 (apply 'append (ht-items table))) 235 236 (defalias 'ht-to-plist 'ht->plist) 237 238 (define-inline ht-copy (table) 239 "Return a shallow copy of TABLE (keys and values are shared)." 240 (declare (side-effect-free t)) 241 (inline-quote (copy-hash-table ,table))) 242 243 (defun ht->alist (table) 244 "Return a list of two-element lists \\='(key . value) from TABLE. 245 246 Note that hash tables are unordered, so this cannot be an exact 247 inverse of `ht<-alist'. The following is not guaranteed: 248 249 \(let ((data \\='((a . b) (c . d)))) 250 (equalp data 251 (ht->alist (ht<-alist data))))" 252 (declare (side-effect-free t)) 253 (ht-amap (cons key value) table)) 254 255 (defalias 'ht-to-alist 'ht->alist) 256 257 (defalias 'ht? 'hash-table-p) 258 259 (defalias 'ht-p 'hash-table-p) 260 261 (define-inline ht-contains? (table key) 262 "Return \\='t if TABLE contains KEY." 263 (declare (side-effect-free t)) 264 (inline-quote 265 (let ((not-found-symbol (make-symbol "ht--not-found"))) 266 (not (eq (ht-get ,table ,key not-found-symbol) not-found-symbol))))) 267 268 (defalias 'ht-contains-p 'ht-contains?) 269 270 (define-inline ht-size (table) 271 "Return the actual number of entries in TABLE." 272 (declare (side-effect-free t)) 273 (inline-quote 274 (hash-table-count ,table))) 275 276 (define-inline ht-empty? (table) 277 "Return true if the actual number of entries in TABLE is zero." 278 (declare (side-effect-free t)) 279 (inline-quote 280 (zerop (ht-size ,table)))) 281 282 (defalias 'ht-empty-p 'ht-empty?) 283 284 (defun ht-select (function table) 285 "Return a hash table containing all entries in TABLE for which 286 FUNCTION returns a truthy value. 287 288 FUNCTION is called with two arguments, KEY and VALUE." 289 (let ((results (ht-create))) 290 (ht-each 291 (lambda (key value) 292 (when (funcall function key value) 293 (ht-set! results key value))) 294 table) 295 results)) 296 297 (defun ht-reject (function table) 298 "Return a hash table containing all entries in TABLE for which 299 FUNCTION returns a falsy value. 300 301 FUNCTION is called with two arguments, KEY and VALUE." 302 (let ((results (ht-create))) 303 (ht-each 304 (lambda (key value) 305 (unless (funcall function key value) 306 (ht-set! results key value))) 307 table) 308 results)) 309 310 (defun ht-reject! (function table) 311 "Delete entries from TABLE for which FUNCTION returns non-nil. 312 313 FUNCTION is called with two arguments, KEY and VALUE." 314 (ht-each 315 (lambda (key value) 316 (when (funcall function key value) 317 (remhash key table))) 318 table) 319 nil) 320 321 (defalias 'ht-delete-if 'ht-reject!) 322 323 (defun ht-find (function table) 324 "Return (key, value) from TABLE for which FUNCTION returns a truthy value. 325 Return nil otherwise. 326 327 FUNCTION is called with two arguments, KEY and VALUE." 328 (catch 'break 329 (ht-each 330 (lambda (key value) 331 (when (funcall function key value) 332 (throw 'break (list key value)))) 333 table))) 334 335 (defun ht-equal? (table1 table2) 336 "Return t if TABLE1 and TABLE2 have the same keys and values. 337 Does not compare equality predicates." 338 (declare (side-effect-free t)) 339 (let ((keys1 (ht-keys table1)) 340 (keys2 (ht-keys table2)) 341 (sentinel (make-symbol "ht-sentinel"))) 342 (and (equal (length keys1) (length keys2)) 343 (--all? 344 (if (ht-p (ht-get table1 it)) 345 (ht-equal-p (ht-get table1 it) 346 (ht-get table2 it)) 347 (equal (ht-get table1 it) 348 (ht-get table2 it sentinel))) 349 keys1)))) 350 351 (defalias 'ht-equal-p 'ht-equal?) 352 353 (provide 'ht) 354 ;;; ht.el ends here