diff --git a/ht.el b/ht.el new file mode 100644 index 000000000..0d5d8b0da --- /dev/null +++ b/ht.el @@ -0,0 +1,280 @@ +;;; ht.el --- The missing hash table library for Emacs + +;; Copyright (C) 2013 Wilfred Hughes + +;; Author: Wilfred Hughes +;; Version: 2.0 +;; Keywords: hash table, hash map, hash + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The missing hash table utility library for Emacs. +;; +;; See documentation on https://github.com/Wilfred/ht.el + +;;; Code: + +(eval-when-compile (require 'cl)) ;; dolist + +(defmacro ht (&rest pairs) + "Create a hash table with the key-value pairs given. +Keys are compared with `equal'. + +\(fn (KEY-1 VALUE-1) (KEY-2 VALUE-2) ...)" + (let* ((table-symbol (make-symbol "ht-temp")) + (assignments + (mapcar + (lambda (pair) `(ht-set! ,table-symbol ,@pair)) + pairs))) + `(let ((,table-symbol (ht-create))) + ,@assignments + ,table-symbol))) + +(defun ht-create (&optional test) + "Create an empty hash table. + +TEST indicates the function used to compare the hash +keys. Default is `equal'. It can be `eq', `eql', `equal' or a +user-supplied test created via `define-hash-table-test'." + (make-hash-table :test (or test 'equal))) + +(defun ht<-alist (alist) + "Create a hash table with initial values according to ALIST." + (let ((h (ht-create))) + ;; the first key-value pair in an alist gets precedence, so we + ;; start from the end of the list: + (dolist (pair (reverse alist) h) + (let ((key (car pair)) + (value (cdr pair))) + (ht-set! h key value))))) + +(defalias 'ht-from-alist 'ht<-alist) + +;; based on the excellent -partition from dash.el, but we aim to be self-contained +(defun ht/group-pairs (list) + "Return a new list with the items in LIST grouped into pairs. +Errors if LIST doesn't contain an even number of elements." + (let ((result) + (sublist) + (len 0)) + + (while list + ;; take the head of LIST and push onto SUBLIST + (setq sublist (cons (car list) sublist)) + (setq list (cdr list)) + + (setq len (1+ len)) + + (when (= len 2) + ;; push this two-item list onto RESULT + (setq result (cons (nreverse sublist) result)) + (setq sublist nil) + (setq len 0))) + + (when sublist (error "Expected an even number of elements")) + (nreverse result))) + +(defun ht<-plist (plist) + "Create a hash table with initial values according to PLIST." + (let ((h (ht-create))) + (dolist (pair (ht/group-pairs plist) h) + (let ((key (car pair)) + (value (cadr pair))) + (ht-set! h key value))))) + +(defalias 'ht-from-plist 'ht<-plist) + +(defun ht-get (table key &optional default) + "Look up KEY in TABLE, and return the matching value. +If KEY isn't present, return DEFAULT (nil if not specified)." + (gethash key table default)) + +(defun ht-set! (table key value) + "Associate KEY in TABLE with VALUE." + (puthash key value table) + nil) + +(defalias 'ht-set 'ht-set!) + +(defun ht-update! (table from-table) + "Update TABLE according to every key-value pair in FROM-TABLE." + (maphash + (lambda (key value) (puthash key value table)) + from-table) + nil) + +(defalias 'ht-update 'ht-update!) + +(defun ht-merge (&rest tables) + "Crete a new tables that includes all the key-value pairs from TABLES. +If multiple have tables have the same key, the value in the last +table is used." + (let ((merged (ht-create))) + (mapc (lambda (table) (ht-update! merged table)) tables) + merged)) + +(defun ht-remove! (table key) + "Remove KEY from TABLE." + (remhash key table)) + +(defalias 'ht-remove 'ht-remove!) + +(defun ht-clear! (table) + "Remove all keys from TABLE." + (clrhash table) + nil) + +(defalias 'ht-clear 'ht-clear!) + +(defun ht-map (function table) + "Apply FUNCTION to each key-value pair of TABLE, and make a list of the results. +FUNCTION is called with two arguments, KEY and VALUE." + (let (results) + (maphash + (lambda (key value) + (push (funcall function key value) results)) + table) + results)) + +(defmacro ht-amap (form table) + "Anaphoric version of `ht-map'. +For every key-value pair in TABLE, evaluate FORM with the +variables KEY and VALUE bound." + `(ht-map (lambda (key value) ,form) ,table)) + +(defun ht-keys (table) + "Return a list of all the keys in TABLE." + (ht-amap key table)) + +(defun ht-values (table) + "Return a list of all the values in TABLE." + (ht-amap value table)) + +(defun ht-items (table) + "Return a list of two-element lists '(key value) from TABLE." + (ht-amap (list key value) table)) + +(defalias 'ht-each 'maphash + "Apply FUNCTION to each key-value pair of TABLE. +Returns nil, used for side-effects only.") + +(defmacro ht-aeach (form table) + "Anaphoric version of `ht-each'. +For every key-value pair in TABLE, evaluate FORM with the +variables key and value bound." + `(ht-each (lambda (key value) ,form) ,table)) + +(defun ht->plist (table) + "Return a flat list '(key1 value1 key2 value2...) from TABLE. + +Note that hash tables are unordered, so this cannot be an exact +inverse of `ht<-plist'. The following is not guaranteed: + +\(let ((data '(a b c d))) + (equalp data + (ht->plist (ht<-plist data))))" + (apply 'append (ht-items table))) + +(defalias 'ht-to-plist 'ht->plist) + +(defun ht-copy (table) + "Return a shallow copy of TABLE (keys and values are shared)." + (copy-hash-table table)) + +(defun ht->alist (table) + "Return a list of two-element lists '(key . value) from TABLE. + +Note that hash tables are unordered, so this cannot be an exact +inverse of `ht<-alist'. The following is not guaranteed: + +\(let ((data '((a . b) (c . d)))) + (equalp data + (ht->alist (ht<-alist data))))" + (ht-amap (cons key value) table)) + +(defalias 'ht-to-alist 'ht->alist) + +(defalias 'ht? 'hash-table-p) + +(defalias 'ht-p 'hash-table-p) + +(defun ht-contains? (table key) + "Return 't if TABLE contains KEY." + (not (eq (ht-get table key 'ht--not-found) 'ht--not-found))) + +(defalias 'ht-contains-p 'ht-contains?) + +(defun ht-size (table) + "Return the actual number of entries in TABLE." + (hash-table-count table)) + +(defun ht-empty? (table) + "Return true if the actual number of entries in TABLE is zero." + (zerop (ht-size table))) + +(defun ht-select (function table) + "Return a hash table containing all entries in TABLE for which +FUNCTION returns a truthy value. + +FUNCTION is called with two arguments, KEY and VALUE." + (let ((results (ht-create))) + (ht-each + (lambda (key value) + (when (funcall function key value) + (ht-set! results key value))) + table) + results)) + +(defun ht-reject (function table) + "Return a hash table containing all entries in TABLE for which +FUNCTION returns a falsy value. + +FUNCTION is called with two arguments, KEY and VALUE." + (let ((results (ht-create))) + (ht-each + (lambda (key value) + (unless (funcall function key value) + (ht-set! results key value))) + table) + results)) + +(defun ht-reject! (function table) + "Delete entries from TABLE for which FUNCTION returns a falsy value. + +FUNCTION is called with two arguments, KEY and VALUE." + (ht-each + (lambda (key value) + (when (funcall function key value) + (remhash key table))) + table) + nil) + +(defalias 'ht-delete-if 'ht-reject!) + +(defun ht-find (function table) + "Return (key, value) from TABLE for which FUNCTION returns a truthy value. +Return nil otherwise. + +FUNCTION is called with two arguments, KEY and VALUE." + (catch 'break + (ht-each + (lambda (key value) + (when (funcall function key value) + (throw 'break (list key value)))) + table))) + +(provide 'ht) +;;; ht.el ends here