config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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