XHT: The extensive hash table library (Emacs package)
Below you find the latest version of (1) the package's README and (2) its main source file.
You may also want to read:
- Embracing Emacs Lisp hash tables: introducing XHT
- So you want to destructure some hash tables, huh?
- Dot-bind any key–value thing in Emacs Lisp
For the git repository and issue tracker, see the project's page on sr.ht.
For more packages, see Software.
README.org
Meta
Compact tree-structure of functions
If you're seeing this as README.org
(instead of html), you can:
- put point at the beginning of the
Functions
orCommands
heading - maybe
C-l
(recenter-top-bottom
) to put it near the top of the screen - and then press
C-c C-k
(outline-show-branches
).
You'll then have a compact tree-structured view of all the functions and commands.
Commands for accessing this very README
These commands give quick local access to this README.org
:
Integration with Helpful and Help buffer
This README.org
file has more than 200 named source blocks with usage examples for this library's functions. You can integrate them into Helpful or Help buffers with another package of mine called Democratize, which can also do the same for a few other libraries, such as dash, s, f, and native Emacs libraries. The package also has overview commands that offer alternative ways to visualize all of xht's functions.
Overview
My intention with this library is that:
- dealing with hash tables in Emacs Lisp be pleasant,
- hash tables become your go-to choice for most key–value operations in the language, and
- for almost everything hash-table–related that you might want to do in Emacs Lisp, this library will have functions for it — or at least close enough that you can do it by composing a few of them.
Let me know if I succeeded.
Background
Emacs Lisp hash tables are fast. In fact, faster than alists and plists beyond a couple dozen items (see, for example, the benchmarks in Adam Porter's emacs-package-dev-handbook), and barely slower than plists when below that.
After learning the above I started using Wilfred Hughes's ht library. (I already used his suggest and helpful packages — I recommend both.) I found ht
to be a good step above the language's native bare-bones one. Hash tables became my usual choice of data structure when dealing with key–value pairs in Elisp.
After some time, I decided to write down a few extra functions that I thought would be useful to complement the ones from it. These “few” functions soon evolved into a full-fledged, systematically-structured library containing more than 200 functions for doing all sorts of things with hash tables: this one.
Installation
How to install
See my page Software for the most up-to-date instructions on how to download and install any of my Emacs packages.
Having downloaded and installed the package and its dependencies, adapt the configurations below to your init.el
file.
If you want to turn on the default keybindings, you can use this:
(use-package xht :commands (global-xht-fontify-mode global-xht-do-mode xht-fontify-mode xht-do-mode xht-see-readme) :config (global-xht-fontify-mode) (global-xht-do-mode))
You can choose your own keybindings instead. Here's an example that picks four of the functions. Shown below are the default keybindings from xht-do-mode-map
, which you could then change to something else.
(use-package xht :commands (global-xht-fontify-mode global-xht-do-mode xht-fontify-mode xht-do-mode) :bind (("C-x x d" . xht-do) ("C-x x c" . xht-do-convert-dwim) ("C-x x x" . xht-do-h*-ify-dwim-to-point) ("C-x x b" . xht-do-h*-ify-dwim-to-new-buffer)) :config (global-xht-fontify-mode))
I use the third one above particularly often, which is why I chose a third ?x
instead of ?i
.
See the variable xht-do-mode-map
for all the keybindings.
To have all the packages' functions available, you can use :demand t
, which is equivalent to require
:
(use-package xht :demand t :bind (("C-x x d" . xht-do) ("C-x x c" . xht-do-convert-dwim) ("C-x x x" . xht-do-h*-ify-dwim-to-point) ("C-x x b" . xht-do-h*-ify-dwim-to-new-buffer)) :config (global-xht-fontify-mode) (global-xht-do-mode))
or (with require
and no keybindings):
(require 'xht) (global-xht-fontify-mode) (global-xht-do-mode)
To use the library in a project
Add xht
to the comment header of the project's Elisp file:
;; Package-Requires: ((xht "1.0.5"))
Also add this to the beginning of the ;;; Code:
section of the project's Elisp file:
(require 'xht)
Other summaries of callables
Here's an overview of this package's...
Functions (non-interactive)
Function | Summary |
---|---|
h-new | Create empty hash table. |
h-empty-clone | Create empty hash table with some or all properties from TABLE. |
h-clr | Create empty hash table whose properties match those of TABLE. |
h-clr! | Remove all keys from TABLE. |
h* | Create a hash table with the key–value PAIRS. |
h-s* | Create a hash table of size SIZE with the key–value PAIRS. |
h-t* | Create a hash table of function TEST with the key–value PAIRS. |
h-st* | Create a hash table of SIZE and TEST with the key–value PAIRS. |
h-reduce-r | Return a one-key nested table with the KEYS-VALUE sequence. |
h--zip-vectors-with | Anaphoric version of ‘h-zip-vectors-with’. |
h-zip-vectors-with | Create a hash table with KEYS and VALUES modified by functions. |
h-zip-vectors | Create a hash table with KEYS and VALUES. Both must be vectors. |
h--zip-lists-with | Anaphoric version of ‘h-zip-lists-with’. |
h-zip-lists-with | Create a hash table with KEYS and VALUES modified by functions. |
h-zip-lists | Create a hash table with KEYS and VALUES. Both must be lists. |
h-let | Let-bind dotted symbols to their value in TABLE and execute BODY. |
h-let-it | Let-bind dotted symbols to their value in THING and execute BODY. |
h-htbl-form | Hash TABLE object as itself. |
h-htbl-cm-str | Hash TABLE object as a string in compact representation. |
h-htbl-pp-str | Hash TABLE object as a string in pretty-printed representation. |
h-ht-form | Restore canonical (ht…) form that creates hash TABLE. |
h-ht-cm-str | Restore as string canonical (ht…) form that creates hash TABLE. |
h-ht-pp-str | The pretty-print string of (ht…) form that creates hash TABLE. |
h-h*-form | Restore canonical (h*…) or (h-st*…) form that creates hash TABLE. |
h-h*-cm-str | Restore as string (h*…) or (h-st*…) form that creates hash TABLE. |
h-h*-pp-str | The pretty-print string of (h*…) form that creates hash TABLE. |
h-lambdify | Convert an HT-LIKE object into a lambda that creates the table. |
h-format | Format as FORMAT hash table–like object HT-LIKE. |
h-vmap | Apply FUN to each key–value pair of TABLE and return vector. |
h--vmap | Anaphoric version of ‘h-vmap’. |
h--vunzip-with | Anaphoric version of ‘h-vunzip-with’. |
h-vunzip-with | Return a list of modified vectors of KEYS and VALUES from TABLE. |
h-vunzip | Return a list of the vectors of KEYS and VALUES from TABLE. |
h-vitems | Return a vector of two-element lists '(key value) from TABLE. |
h-vkeys | Return a vector of all the keys in TABLE. |
h-vvalues | Return a vector of all the values in TABLE. |
h-vrandom | Return a vector of N pseudo-randomly chosen items from hash TABLE. |
h-lmap | Apply FUN to each key–value pair of TABLE; list the results. |
h--lmap | Anaphoric version of ‘h-lmap’. |
h-lkeep | Apply FUN to each key–value pair of TABLE; list non-nil results. |
h--lkeep | Anaphoric version of ‘h-lkeep’. |
h--lunzip-with | Anaphoric version of ‘h-lunzip-with’. |
h-lunzip-with | Return a list of modified lists of KEYS and VALUES from TABLE. |
h-lunzip | Return a list of the lists of KEYS and VALUES from TABLE. |
h-litems | Return a list of two-element lists '(key value) from TABLE. |
h-lkeys | Return a list of all the keys in TABLE. |
h-lvalues | Return a list of all the values in TABLE. |
h-lrandom | Return a list of N pseudo-randomly chosen items from hash TABLE. |
h-hmap | Return a table from applying KEY-FUN, VAL-FUN to each pair of TABLE. |
h--hmap | Anaphoric version of ‘h-hmap’. |
h-hmap! | Update TABLE by applying KEY-FUN, VAL-FUN to its pairs. |
h--hmap! | Anaphoric version of ‘h-hmap!’. |
h-hmap* | Return a table from applying KEY-FUN, VAL-FUN to each pair of TABLE. |
h--hmap* | Anaphoric version of ‘h-hmap*’. |
h-hmap*! | Update TABLE by applying KEY-FUN, VAL-FUN to each of its pairs. |
h--hmap*! | Anaphoric version of ‘h-hmap*!’. |
h-each | Apply function FUN to each key–value pair of TABLE. |
h--each | Anaphoric version of ‘h-each’. |
h-pop | Return the very first pair from TABLE. |
h-pop! | Return the very first pair from TABLE and remove it. |
h-pop-random | Return a pseudo-random pair from TABLE. |
h-pop-random! | Return a pseudo-random pair from TABLE and remove it. |
h-first | Return the first pair from TABLE for which FUN returns non-nil. |
h--first | Anaphoric version of ‘h-first’. |
h-first! | Remove from TABLE the first pair for which FUN returns non-nil. |
h--first! | Anaphoric version of ‘h-first!’. |
h-put! | Associate KEY in TABLE with VALUE. |
h-put | Return a table that is TABLE with KEY–VALUE applied. |
h-put*! | Set VALUE to KEYS sequence in hash table TABLE. |
h-put* | Return a table that is TABLE with KEYS–VALUE applied. |
h-put-add! | Add VALUE to the current value of KEY in hash table TABLE. |
h-put-add | Return a table that is TABLE with VALUE added to KEY's current. |
h-put-add*! | Add VALUE to the current KEYS sequence's value in hash table TABLE. |
h-put-add* | Return a table that is TABLE with VALUE added to KEYS' current. |
h-rem! | Remove KEY from hash table TABLE. |
h-rem | Return a table that is TABLE with KEY removed. |
h-rem*! | Remove key at end of KEYS sequence from hash table TABLE. |
h-rem* | Return a table that is TABLE with key at end of KEYS removed. |
h-sel-keys | Return a copy of TABLE with only the pairs specified by KEYS. |
h-sel-keys! | Update TABLE to contain only the pairs specified by KEYS. |
h-sel | Return a table with pairs from TABLE for which FUN returns non-nil. |
h--sel | Anaphoric version of ‘h-sel’. |
h-sel! | Update TABLE by keeping only pairs for which FUN returns non-nil. |
h--sel! | Anaphoric version of ‘h-sel!’. |
h-sel* | Return a table with pairs from TABLE for which FUN returns non-nil. |
h--sel* | Anaphoric version of ‘h-sel*’. |
h-sel*! | Update TABLE by keeping only pairs for which FUN returns non-nil. |
h--sel*! | Anaphoric version of ‘h-sel*!’. |
h-rej-keys | Return a copy of TABLE with all but the pairs specified by KEYS. |
h-rej-keys! | Update TABLE by removing the pairs specified by KEYS. |
h-rej | Return a table with pairs from TABLE for which FUN returns nil. |
h--rej | Anaphoric version of ‘h-rej’. |
h-rej! | Update TABLE by keeping only pairs for which FUN returns nil. |
h--rej! | Anaphoric version of ‘h-rej!’. |
h-rej* | Return a table with pairs from TABLE for which FUN returns nil. |
h--rej* | Anaphoric version of ‘h-rej*’. |
h-rej*! | Update TABLE by keeping only pairs for which FUN returns nil. |
h--rej*! | Anaphoric version of ‘h-rej*!’. |
h-reverse | Create a hash table identical to TABLE with key order reversed. |
h-reverse! | Reverse the key order of hash table TABLE. |
h-reverse* | Create a hash table identical to TABLE with key order reversed. |
h-reverse*! | Reverse the key order of hash table TABLE. |
h-put-num-with* | Return copy of TABLE with FUN applied to numeric value of KEYS. |
h-put-num-with*! | Apply FUN to the numeric value of KEYS in possibly nested hash TABLE. |
h-put-inc* | Return a copy of TABLE with 1 added to the value of KEYS. |
h-put-inc*! | Add 1 to the value of KEYS in possibly nested hash TABLE. |
h-put-dec* | Return a copy of TABLE with 1 subtracted from the value of KEYS. |
h-put-dec*! | Subtract 1 from the value of KEYS in possibly nested hash TABLE. |
h-has-key? | Return t if TABLE has KEY. |
h-has-key*? | Whether possibly-nested TABLE has KEY at some level. |
h-has-value? | Return t if TABLE has some key whose value is VALUE. |
h-has-value*? | Whether possibly-nested TABLE has at some level key–VALUE pair. |
h-has-pair? | Return t if TABLE has KEY and its value is VALUE. |
h-has-pair*? | Whether possibly-nested TABLE has at some level KEY–VALUE pair. |
h-has-keys? | Return t if TABLE has all KEYS. |
h-has-values? | Return t if TABLE has all VALUES. |
h-has-pairs? | Return t if TABLE has all PAIRS. |
h-mix! | Update TABLE according to every key–value pair in FROM-TABLES. |
h-mix | Return a table that has all key–value pairs from the tables. |
h-mix*! | Update TABLE by adding every key–value pair in FROM-TABLES. |
h-mix* | Return a table that has all key–value pairs from the tables. |
h-dif! | Update TABLE by removing all matching key–value pairs in FROM-TABLES. |
h-dif | Return a table of all key–value pairs in TABLE not in FROM-TABLES. |
h-dif*! | Update TABLE by removing all matching key–value pairs in FROM-TABLES. |
h-dif* | Return a table of all key–value pairs in TABLE not in FROM-TABLES. |
h-cmn! | Update TABLE by keeping only the intersection with FROM-TABLES. |
h-cmn | Return a table of all key–value pairs in TABLE also in FROM-TABLES. |
h-cmn*! | Update TABLE by keeping only the intersection with FROM-TABLES. |
h-cmn* | Return a table of all key–value pairs in TABLE also in FROM-TABLES. |
h-kvl= | Return t only if all KEY–VALUE LINES have same KEY–VALUE pairs. |
h-pr== | One of the ways to see if all hash tables TABLES are equivalent. |
h-pr= | One of the ways to see if all hash tables TABLES are equivalent. |
h== | One of the ways to see if all hash tables TABLES are equivalent. |
h= | One of the ways to see if all hash tables TABLES are equivalent. |
h_= | One of the ways to see if all hash tables TABLES are equivalent. |
h~= | One of the ways to see if all hash tables TABLES are equivalent. |
h-props= | Whether all hash tables TABLES have the same properties. |
h-alist= | Return t only if all ALISTs have same KEY–VALUE pairs. |
h-plist= | Return t only if all PLISTs have same KEY–VALUE pairs. |
h-json= | Return t only if all JSONs have same KEY–VALUE pairs. |
h-lol= | Return t only if all LISTS OF LISTS have same KEY–VALUE pairs. |
h-orgtbl= | Return t only if all ORG TABLES have same KEY–VALUE pairs. |
h-tsv= | Return t only if all TSVs have same KEY–VALUE pairs. |
h-csv= | Return t only if all CSVs have same KEY–VALUE pairs. |
h-ssv= | Return t only if all SSVs have same KEY–VALUE pairs. |
h-type | Detect object OBJ's type-for-conversion. Return a keyword. |
h-type= | Are all OBJECTS of same type? |
h-it= | Are all OBJECTS, all of same type, reducible to each other? |
h-it~ | Are all OBJECTS equivalent according to XHT tests? |
h-clone* | Create a deep copy of possibly-nested hash table TABLE. |
h<-ht | Given hash table TABLE, return it or a clone, depending on params. |
h-2d<-1d | Make 2D the 1D hash table TABLE by adding header fields F1, F2. |
h-1d<-2d | Make 1D (KVL-like) the 2D hash table TABLE2D. |
h<-vector | Convert vector VECTOR to a hash table using VECTOR's indices as keys. |
h->vector | Convert hash table TABLE to vector. |
h<-list | Convert LIST to a hash table using LIST's indices as keys. |
h->list | Convert hash table TABLE to list. |
h<-lines | Convert string STR to a hash table using line numbers as keys. |
h->lines | Convert hash table TABLE whose keys are numbers to string. |
h<-kvl | Convert key–value lines KVL to a hash table. |
h->kvl | Convert hash table TABLE to SEP-separated key–value lines. |
h<-cons-pair | Convert CONS-PAIR to hash table. |
h->cons-pair | Convert hash table TABLE to cons pair. |
h<-alist | Convert ALIST to hash table. |
h<-alist* | Convert possibly nested ALIST to hash table. |
h->alist | Convert hash table TABLE to alist. |
h->alist* | Convert hash table TABLE to alist, maybe recursing. |
h<-plist | Convert PLIST to hash table. |
h<-plist* | Convert possibly nested PLIST to hash table. |
h->plist | Convert hash table TABLE to plist. |
h->plist* | Convert hash table TABLE to plist, maybe recursing. |
h<-json* | Convert possibly nested JSON to hash table. |
h->json* | Convert hash table TABLE to JSON, maybe recursing. |
h<-lol | Create a hash table with initial values according to list of lists LOL. |
h->lol | Do the opposite of ‘h<-lol’, which see. Return list of lists. |
h<-orgtbl | Create a hash table with initial values according to org table ORGTBL. |
h->orgtbl | Do the opposite of ‘h<-orgtbl’, which see. Return org table as string. |
h<-tsv | Create a hash table with initial values according to TSV. |
h->tsv | Do the opposite of ‘h<-tsv’, which see. Return tsv. |
h<-ssv | Create a hash table with initial values according to SSV. |
h->ssv | Do the opposite of ‘h<-ssv’, which see. Return ssv. |
h->csv | Do the opposite of ‘h<-csv’, which see. Return csv. |
h<-it | Convert THING to hash table. Try to guess what it is. |
h-kvl? | Could string STR be a KVL? |
h-alist? | Non-nil if and only if LIST is a non-nil alist with simple keys. |
h-plist? | Non-nil if and only if LIST is a non-nil plist with non-nil atom keys. |
h-lol? | Return t if OBJ is a list of lists. |
h-orgtbl? | Could string STR be an Org Table? |
h-json? | Could string STR be a JSON object? |
h-tsv? | Could string STR be a TSV? |
h-csv? | Could string STR be a CSV? |
h-ssv? | Could string STR be an SSV? |
h-prop | Return the value of property PROP of hash table TABLE. |
h-props | Return a hash table containing properties of hash table TABLE. |
h-dim | Determine dimension of hash table TABLE. |
h-1d? | Return t if OBJ is a 1D hash table, nil otherwise. |
h-2d? | Return t if OBJ is a 2D hash table, nil otherwise. |
h-nested? | Return t if OBJ is a nested hash table, nil otherwise. |
h-it-dim | Determine dimension of object OBJ. |
h-it-empty? | Return t if OBJ is empty, nil otherwise. |
h-it-1d? | Return t if OBJ is of dimension 1, nil otherwise. |
h-it-2d? | Return t if OBJ is of dimension 2, nil otherwise. |
h-it-nested? | Return t if OBJ is nested, nil otherwise. |
h-write! | Write OBJ to file FILENAME. |
h-write-ht-like! | Write hash table HT-LIKE to file FILENAME formatted as FORMAT. |
h-write-htbl-cm! | Write hash table HT-LIKE to file FILENAME formatted as 'htbl-cm. |
h-write-htbl-pp! | Write hash table HT-LIKE to file FILENAME formatted as 'htbl-pp. |
h-write-h*-cm! | Write hash table HT-LIKE to file FILENAME formatted as 'h*-cm. |
h-write-ht-cm! | Write hash table HT-LIKE to file FILENAME formatted as 'ht-cm. |
h-write-h*-pp! | Write hash table HT-LIKE to file FILENAME formatted as 'h*-pp. |
h-write-ht-pp! | Write hash table HT-LIKE to file FILENAME formatted as 'ht-pp. |
h-read | Deal with object OBJ, expected to be of type TYPE. |
h-read-ht-like | Deal with object OBJ, expected to be ht-like. |
h-read-htbl | Deal with object OBJ, expected to be a hash table. |
h-read-s | Deal with object OBJ, expected to be a string. |
h-read-kvl | Deal with object OBJ, expected to be a KVL. |
h-read-tsv | Deal with object OBJ, expected to be a TSV. |
h-read-csv | Deal with object OBJ, expected to be a CSV. |
h-read-ssv | Deal with object OBJ, expected to be a SSV. |
h-read-json | Deal with object OBJ, expected to be JSON. |
h-read-lines | Deal with object OBJ, expected to be lines of text. |
h-read-orgtbl | Deal with object OBJ, expected to be an org table. |
h-read-lol | Deal with object OBJ, expected to be a list of lists. |
h-read-alist | Deal with object OBJ, expected to be an association list. |
h-read-plist | Deal with object OBJ, expected to be a property list. |
h-read-list | Deal with object OBJ, expected to be a list. |
h-read-cons-pair | Deal with object OBJ, expected to be a cons pair. |
h-read-vector | Deal with object OBJ, expected to be a vector. |
h-as-string | Return OBJ as a string. |
h-as-keyword | Return OBJ as a keyword. |
h-as-symbol | Return OBJ as an interned symbol. |
h-as-number | Return OBJ as a number, if convertible. |
h-2d-new | Return a 2D hash table from KEYS and HEADER. |
h-2d-header | Return the header of 2D hash table TABLE2d. |
h-2d-idcol | The ID column of 2D hash table TABLE2D, as list. It's ID + keys. |
h-2d-id | Find the ID-like element of the header of 2D hash table TABLE2D. |
h-2d-row | List row from 2D hash table TABLE2D given KEY and HEADER. |
h-2d-col | List column from 2D hash table TABLE2D given COL and IDCOL. |
h-2d-hmap | Return a 2D table from applying functions to each triad of TABLE2D. |
h--2d-hmap | Anaphoric version of ‘h-2d-hmap’. |
h-2d-hmap! | Update TABLE2D by applying functions to each of its triads. |
h--2d-hmap! | Anaphoric version of ‘h-2d-hmap!’. |
h-2d-sel-cols | Return a table with only selected named columns COLS of TABLE2D. |
h-2d-sel-cols! | Remove all but named columns COLS from 2D hash table TABLE2D. |
h-2d-rej-cols | Return a table without the named columns COLS of TABLE2D. |
h-2d-rej-cols! | Remove named columns COLS from 2D hash table TABLE2D. |
h-2d-transpose | Return a hash table that is TABLE2D transposed. |
h-2d-transpose! | Transpose TABLE2D. |
h-as-set<-vector | Create a hash table as set from VECTOR. |
h-as-set<-list | Create a hash table as set from LIST. |
h-as-set<-lines | Create a hash table as set from the lines of string STR. |
h-as-set<-words | Create a hash table as set from the words of string STR. |
h-count<-vector | Create a hash table with the elements of VECTOR and their count. |
h-count<-list | Create a hash table with the elements of LIST and their count. |
h-count<-lines | Create a hash table with the lines of string STR and their count. |
h-count<-words | Create a hash table with the words of string STR and their count. |
h-count-put | Create a hash table with objects OBJS with the counts as values. |
h-count-put! | Add counts of objects OBJS to the counts of hash table TABLE. |
Commands
Function | Summary |
---|---|
xht-do | The main command for you to do all sorts of hash table things. |
xht-do-convert-dwim | Convert region, thing at point or last-sexp — try to guess it. |
xht-do-h*-ify-dwim-to-clipboard | First h*-ify region, thing at point or last-sexp; send it to clipboard. |
xht-do-h*-ify-dwim-to-file | First h*-ify region, thing at point or last-sexp; write to file. |
xht-do-h*-ify-dwim-to-replace | First h*-ify region, thing at point or last-sexp; replace source. |
xht-do-h*-ify-dwim-to-new-buffer | First h*-ify region, thing at point or last-sexp; insert at new buffer. |
xht-do-h*-ify-dwim-to-point | First h*-ify region, thing at point or last-sexp; insert at point. |
xht-do-h*-ify-selected-region-to-point | First h*-ify selected region; then insert results at point. |
xht-do-h*-ify-thing-at-point-to-point | First h*-ify thing at point; then insert results at point. |
xht-do-h*-ify-last-sexp-to-point | First h*-ify last-sexp; then insert results at point. |
xht-do-mode | Enable keys for ‘xht-do’ actions. |
global-xht-do-mode | Toggle Xht-Do mode in all buffers. |
xht-fontify-mode | Toggle fontification of XHT/HT special variables. |
global-xht-fontify-mode | Toggle Xht-Fontify mode in all buffers. |
xht-see-readme | Open xht's README.org file. |
xht-see-function-in-readme | See FUNCTION in xht's README.org. |
xht-see-news | See the News in xht's README.org file. |
Aliases
This function… | is an alias to… |
---|---|
h-get | ht-get |
h-get* | ht-get* |
h-empty? | ht-empty? |
h? | hash-table-p |
h-size | hash-table-count |
h-copy | copy-hash-table |
h-has-key-p | h-has-key? |
h-has-key*-p | h-has-key*? |
h-has-keys-p | h-has-keys? |
h-has-value-p | h-has-value? |
h-has-value*-p | h-has-value*? |
h-has-values-p | h-has-values? |
h-has-pair-p | h-has-pair? |
h-has-pair*-p | h-has-pair*? |
h-has-pairs-p | h-has-pairs? |
h-1d-p | h-1d? |
h-2d-p | h-2d? |
h-nested-p | h-nested? |
h-it-1d-p | h-it-1d? |
h-it-2d-p | h-it-2d? |
h-it-nested-p | h-it-nested? |
h-csv-p | h-csv? |
h-ssv-p | h-ssv? |
h-tsv-p | h-tsv? |
h-kvl-p | h-kvl? |
h-lol-p | h-lol? |
h-json-p | h-json? |
h-alist-p | h-alist? |
h-plist-p | h-plist? |
h-orgtbl-p | h-orgtbl? |
h-empty-p | ht-empty-p |
h-p | hash-table-p |
h-random | h-lrandom |
h-unzip-with | h-lunzip-with |
h--unzip-with | h--lunzip-with |
h-unzip | h-lunzip |
h-items | h-litems |
h-keys | h-lkeys |
h-values | h-lvalues |
h-write-h*! | h-write-h*-pp! |
h-write-ht! | h-write-ht-pp! |
h-write-htbl! | h-write-htbl-pp! |
h-read-h | h-read-ht-like |
h-2d-keys | h-lkeys |
h-1d->2d | h-2d<-1d |
h-2d->1d | h-1d<-2d |
h<-csv | xht<--csv |
And if you're already familiar with ht
's functions, there's still another summary table in the Appendix.
Enough summaries.
Non-interactive functions and commands are described in detail below, accompanied by about 2061 examples.
You'll note that each of the examples have an equality operator. Here's what they mean:
;; symbol function '(( => . equal ) ; regular equality (Hp==> . h-pr== ) ; equality for hash tables ├> Functions ( Hp=> . h-pr= ) ; equality for hash tables │ provided ( H==> . h== ) ; equality for hash tables │ by this ( H=> . h= ) ; equality for hash tables │ very ( H_=> . h_= ) ; equality for hash tables │ package ( H~=> . h~= ) ; equality for hash tables │ ( O=> . h-orgtbl=) ; equality for org table strings │ ( A=> . h-alist= ) ; equality for association lists │ ( P=> . h-plist= ) ; equality for property lists │ ( L=> . h-lol= ) ; equality for lists of lists │ ( T=> . h-tsv= ) ; equality for TSVs │ ( C=> . h-csv= ) ; equality for CSVs │ ( S=> . h-ssv= ) ; equality for SSVs │ ( J=> . h-json= ) ; equality for JSONs │ ( K=> . h-kvl= ) ; equality for key–value lines │ ( ~> . exemplify-ert-approx=)) ; equality for floats
Some simple examples, so that you get their meaning:
(+ 20 10 10 2) => 42 (cons :a '(1)) => '(:a 1) '(:a 1 :b 2) P=> '(:b 2 :a 1) '((a . 1) (b . 2)) A=> '((b . 2) (a . 1)) '((a . 1) (b . 2)) A=> '((b . 2) (a . 1) (b . 5)) (ht (:a 1) (:b 2)) H=> (ht (:b 2) (:a 1)) (ht (:a 1) (:b 2)) H=> (h* :a 1 :b 2) (h* :b 2 :a 1) H=> (h* :a 1 :b 2)
So let's see the functions and commands.
Naming conventions
This library adopts the following naming conventions:
These functions | look like |
---|---|
Regular | h-foo |
Anaphoric | h--foo |
Interactive | xht-foo |
Private | xht--foo |
and
These variables | look like |
---|---|
Regular | h-foo |
Customizable | xht-foo |
Private | xht--foo |
The two tables above represent almost all of the more than 500
symbols defined by xht
. These also happen to be the same familiar
conventions that have long been adopted by dash
.
There are some functions that convert from and to hash tables:
These functions | look like |
---|---|
Regular (conversion) | h->foo, h<-foo |
Private (conversion) | xht-->foo, xht<--foo |
This intuitive use of '->' and '<-' to represent conversion will
certainly look familiar to those who have used ht
.
That's pretty much it.
The only symbols that don't perfectly match the tables above are:
- The commands global-xht-do-mode and global-xht-fontify-mode.
- Half a dozen regular functions:
Now, complementing the previous conventions, you'll notice that
some suffixes are consistently employed:
- ?
- Functions that have an ? as suffix are predicates
(they "ask" a yes-or-no question and return t or nil).
They have a corresponding -p alias. - =
- Functions that have an = as suffix are equality predicates
and therefore check whether two or more things can be
considered equivalent according to some standard. - *
- Functions that act on nested hash tables have an * as
suffix, and vice-versa. (With the notable exception of h*,
whose * has a slightly different, but hopefully obvious
enough, connotation.) - !
Functions that destructively modify the table that is
passed as argument have an ! as suffix, and vice-versa.
There are no !-less aliases for destructive functions:
wherever the ! is dropped, it means it's side-effect free:
the value of the result of the operations is returned, and
TABLE remains unmodified. If you find it happening
otherwise, it's most likely unintentional and a bug — please
report.
Note that this differs from ht's naming convention, in which
ht-clear
,ht-remove
,ht-set
, andht-update
remain as
aliases to, respectively,ht-clear!
,ht-remove!
,
ht-set!
, andht-update!
— all of which destructive
functions. (Butht-reject
is not an alias toht-reject!
:
the former is non-destructive, the latter is destructive.)
On the other hand, xht follows this convention consistently.
For example:
Non-destructive Destructive h-rej h-rej! h-sel h-sel! h-put h-put! h-mix h-mix! h-mix* h-mix*! etc.
The library itself may be called either XHT or xht. Both are fine.
Mixed casing is better avoided. Yes, you'll see it titleized in the
docstrings of global-xht-do-mode and global-xht-fontify-mode —
but only because these docstrings happen to be automatically
generated by define-globalized-minor-mode
, which calls
easy-mmode-pretty-mode-name
, which titleizes names.
Functions
General hash table operations
Creation
Functions that create a hash table.
Empty one
Functions that create an empty hash table.
(&optional sz ts we rs rt)
Create empty hash table.
Arguments passed correspond to, respectively, size, test, weakness,
rehash-size, rehash-threshold. When nil, they match that of TABLE.
SZ is the initial assigned size. When nil, default to 65.
TS is the function used to compare the hash keys.
It can be eq
, eql
, equal
or a user-supplied test created
via define-hash-table-test
. When nil, default to equal
.
The other three default to Emacs' defaults:
- WE defaults to nil.
- RS defaults to 1.5.
- RT defaults to 0.8125.
This is similar to ht-create
. The differences are:
- size as a first optional argument, test as second;
- possibility of passing other parameters accepted
bymake-hash-table
, which see.
;; note: remember that H=> (that is, #'h=) ;; doesn't check for :size or :test (h-new) H=> (ht-create) (h-new 20) H=> (ht-create) (h-new nil 'eq) H=> (ht-create) (-> (h-new) (h-prop 'size)) => 65 (-> (h-new) (h-prop 'test)) => 'equal (-> (h-new 20) (h-prop 'size)) => 20 (-> (h-new 20) (h-prop 'test)) => 'equal (-> (h-new nil 'eq) (h-prop 'size)) => 65 (-> (h-new nil 'eq) (h-prop 'test)) => 'eq (-> (h-new 20 'eq) (h-prop 'size)) => 20 (-> (h-new 20 'eq) (h-prop 'test)) => 'eq ;; Note about how =make-hash-table= now deals with rehash-size. ;; When the fractional part of rehash-size is a sum of powers of 2 (such as ;; 1.5, 1.625, 1.75, 2.0625), it shows up exactly. But values such as 1.4 ;; are set by =make-hash-table= to 1.4000000059604645. (-> (h-new nil nil nil) (h-prop 'rehash-size)) => 1.5 (-> (h-new nil nil nil 1.75) (h-prop 'rehash-size)) => 1.75 (-> (h-new nil nil 'key) (h-prop 'weakness)) => 'key
(table &optional sz ts we rs rt)
Create empty hash table with some or all properties from TABLE.
Arguments passed correspond to, respectively, size, test, weakness,
rehash-size, rehash-threshold. When nil, they match that of TABLE.
This is a non-destructive function.
See also: h-clr and h-clr!.
(-> (h-st* 10 'eq :a 1) (h-empty-clone 5) (h-prop 'size)) => 5 (-> (make-hash-table :rehash-size 1.2 :test 'eq) (h-put :a 1) (h-put :b 2) (h-empty-clone nil nil nil 1.3)) Hp==> (make-hash-table :rehash-size 1.3 :test 'eq) ;;;; The below should be identical to using h-clr (-> (h-st* 10 'eq :a 1) h-empty-clone (h-prop 'test)) => 'eq (-> (h-st* 10 'eq :a 1) h-empty-clone (h-prop 'size)) => 10 (-> (make-hash-table :rehash-size 1.2 :test 'eq) (h-put :a 1) (h-put :b 2) h-empty-clone) Hp==> (make-hash-table :rehash-size 1.2 :test 'eq) (let* ((tbl (ht (:a 1) (:b 2)))) (h-empty-clone tbl)) H=> (ht) (let* ((tbl (make-hash-table :size 10 :test 'eq)) res) (h-put! tbl :a 1) (setq res (h-empty-clone tbl)) (list 'tbl "has properties" :test (h-prop tbl 'test) :size (h-prop tbl 'size) :data (ht->plist tbl) 'res "has properties" :test (h-prop res 'test) :size (h-prop res 'size) :data (ht->plist res))) => (list 'tbl "has properties" :test 'eq :size 10 :data '(:a 1) 'res "has properties" :test 'eq :size 10 :data nil)
(table)
Create empty hash table whose properties match those of TABLE.
It preserves all of the properties of the original table — except
data, which is empty. It's therefore particularly useful as a basis
for other hash table functions (such as h-hmap*) that perform
non-destructive operations — for which a fresh empty table must be
created, and for which the use of h-new or ht-create
would
result in a loss of these properties.
This is a non-destructive function.
Its destructive counterpart is h-clr!.
(-> (h-st* 10 'eq :a 1) h-clr (h-prop 'test)) => 'eq (-> (h-st* 10 'eq :a 1) h-clr (h-prop 'size)) => 10 (-> (make-hash-table :rehash-size 1.2 :test 'eq) (h-put :a 1) (h-put :b 2) h-clr) Hp==> (make-hash-table :rehash-size 1.2 :test 'eq) (let* ((tbl (ht (:a 1) (:b 2)))) (h-clr tbl)) H=> (ht) (let* ((tbl (make-hash-table :size 10 :test 'eq)) res) (h-put! tbl :a 1) (setq res (h-clr tbl)) (list 'tbl "has properties" :test (h-prop tbl 'test) :size (h-prop tbl 'size) :data (ht->plist tbl) 'res "has properties" :test (h-prop res 'test) :size (h-prop res 'size) :data (ht->plist res))) => (list 'tbl "has properties" :test 'eq :size 10 :data '(:a 1) 'res "has properties" :test 'eq :size 10 :data nil)
(table)
Remove all keys from TABLE.
This is equivalent to current ht-clear!
.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-clr.
(let* ((tbl (ht (:a 1) (:b 2)))) (h-clr! tbl) tbl) H=> (ht) (let* ((tbl (make-hash-table :size 10 :test 'eq))) (h-put! tbl :a 1) (h-clr! tbl) (list 'tbl "has properties" :test (h-prop tbl 'test) :size (h-prop tbl 'size) :data (ht->plist tbl))) => `(tbl "has properties" :test eq :size 10 :data nil)
From keys and values
Functions to create a hash table from keys and values.
(key-1 value-1 key-2 value-2 ...)
Create a hash table with the key–value PAIRS.
Keys are compared with equal
.
This function is similar to ht
, with the difference that no
parentheses are used around each key–value pair: so its syntax is
identical to that used to create a plist, but replacing list
with
h*.
Also, caution: with plists and alists, repeated items that come
first have priority, whereas when passing pairs to ht
or h*.
it's the opposite — the repeated item's last value wins.
Compare:
(plist-get '(:a 1 :b 2 :a 5) :a) => 1 (ht-get (h* :a 1 :b 2 :a 5) :a) => 5 (ht-get (ht (:a 1) (:b 2) (:a 5)) :a) => 5
Another difference is that, instead of 65, its initial nominal size
defaults to the result of xht--init-size
, which determines
initial nominal size as a function of the number of pairs.
Currently, xht--init-size
uses xht--init-size-exact
, which
returns the exact number of pairs.
(h* :a 1 :b 2 :c 3 :d 4 :e 5) H=> (ht (:a 1)(:b 2)(:c 3)(:d 4)(:e 5)) (h* :a 1 :b 2) H=> (ht (:a 1) (:b 2)) (h* 'a 1 'b 2) H=> (ht ('a 1) ('b 2)) (h* "a"1 "b"2) H=> (ht ("a"1) ("b"2)) (h* :a 1 :b (h* :c 3)) H=> (ht (:a 1) (:b (ht (:c 3)))) (h* :a 1) H=> (ht (:a 1)) (h*) H=> (ht) (h* :a 1 :b) !!> wrong-number-of-arguments (h* :a) !!> wrong-number-of-arguments
(size key-1 value-1 key-2 value-2 ...)
Create a hash table of size SIZE with the key–value PAIRS.
This is exactly like h* (which see), with the difference that its
nominal size is explicitly passed as first argument instead of being
inferred.
When SIZE is nil, it defaults to h*'s default.
(-> (h-s* 20 :b 2) (h-prop 'size)) => 20 (-> (h-s* 20 :b 2) (h-prop 'size)) => 20 (-> (h-s* nil :b 2) (h-prop 'size)) => 1 (-> (h-s* nil :a 1 :b 2) (h-prop 'size)) => 2 (-> (h-s* nil :a 1 :b 2) (h-prop 'size)) => 2 (h-s* nil :a 1 :b 2) H=> (h* :a 1 :b 2) (h-s* 20 :a 1 :b 2 :c 3) H=> (ht (:a 1)(:b 2)(:c 3)) (h-s* 20 :a 1 :b 2) H=> (ht (:a 1) (:b 2)) (h-s* 20 'a 1 'b 2) H=> (ht ('a 1) ('b 2)) (h-s* 20 "a"1 "b"2) H=> (ht ("a"1) ("b"2)) (h-s* 20 :a 1 :b (h* :c 3)) H=> (ht (:a 1) (:b (ht (:c 3)))) (h-s* 20 :a 1) H=> (ht (:a 1)) (h-s* 20 ) H=> (ht) (h-s* 20 :a 1 :b) !!> wrong-number-of-arguments (h-s* 20 :a) !!> wrong-number-of-arguments (h-s* 20 :a 1 :b) !!> wrong-number-of-arguments (h-s* 20 :a) !!> wrong-number-of-arguments (h-s* :a 1 :b) !!> error
(test key-1 value-1 key-2 value-2 ...)
Create a hash table of function TEST with the key–value PAIRS.
This is exactly like h* (which see), with the difference that its
test function is explicitly passed as first argument instead of
being inferred.
When TEST is nil, it defaults to h*'s default, which is h-new's
default: 'equal.
(-> (h-t* 'eq :b 2) (h-prop 'test)) => 'eq (-> (h-t* nil :b 2) (h-prop 'test)) => 'equal (-> (h-t* 'eq :b 2) (h-prop 'test)) => 'eq (-> (h-t* nil :a 1 :b 2) (h-prop 'test)) => 'equal (h-t* nil :a 1 :b 2) H=> (h* :a 1 :b 2) (h-t* 'eq :a 1 :b 2 :c 3) H=> (ht (:a 1)(:b 2)(:c 3)) (h-t* 'eq :a 1 :b 2) H=> (ht (:a 1) (:b 2)) (h-t* 'eq 'a 1 'b 2) H=> (ht ('a 1) ('b 2)) (h-t* 'eq "a"1 "b"2) H=> (ht ("a"1) ("b"2)) (h-t* 'eq :a 1 :b (h* :c 3)) H=> (ht (:a 1) (:b (ht (:c 3)))) (h-t* 'eq :a 1) H=> (ht (:a 1)) (h-t* 'eq ) H=> (ht) (h-t* 'eq :a 1 :b) !!> wrong-number-of-arguments (h-t* 'eq :a) !!> wrong-number-of-arguments (h-t* nil :a 1 :b) !!> wrong-number-of-arguments (h-t* nil :a) !!> wrong-number-of-arguments (h-t* :a 1 :b) !!> error
(size test key-1 value-1 key-2 value-2 ...)
Create a hash table of SIZE and TEST with the key–value PAIRS.
This is exactly like h* (which see), with the difference that its
nominal size and its test function are explicitly passed as
first arguments instead of being inferred.
When SIZE is nil, it defaults to h*'s default.
When TEST is nil, it defaults to h*'s default, which is h-new's
default: 'equal.
(-> (h-st* 20 'eq :b 2) (h-prop 'test)) => 'eq (-> (h-st* 20 'eq :b 2) (h-prop 'size)) => 20 (-> (h-st* 20 nil :b 2) (h-prop 'test)) => 'equal (-> (h-st* 20 nil :b 2) (h-prop 'size)) => 20 (-> (h-st* nil 'eq :b 2) (h-prop 'test)) => 'eq (-> (h-st* nil 'eq :b 2) (h-prop 'size)) => 1 (-> (h-st* nil 'eq :a 1 :b 2) (h-prop 'size)) => 2 (-> (h-st* nil nil :a 1 :b 2) (h-prop 'test)) => 'equal (-> (h-st* nil nil :a 1 :b 2) (h-prop 'size)) => 2 (h-st* nil nil :a 1 :b 2) H=> (h* :a 1 :b 2) (h-st* 20 'eq :a 1 :b 2 :c 3) H=> (ht (:a 1)(:b 2)(:c 3)) (h-st* 20 'eq :a 1 :b 2) H=> (ht (:a 1) (:b 2)) (h-st* 20 'eq 'a 1 'b 2) H=> (ht ('a 1) ('b 2)) (h-st* 20 'eq "a"1 "b"2) H=> (ht ("a"1) ("b"2)) (h-st* 20 'eq :a 1 :b (h* :c 3)) H=> (ht (:a 1) (:b (ht (:c 3)))) (h-st* 20 'eq :a 1) H=> (ht (:a 1)) (h-st* 20 'eq ) H=> (ht) (h-st* 20 'eq :a 1 :b) !!> wrong-number-of-arguments (h-st* 20 'eq :a) !!> wrong-number-of-arguments (h-st* 20 nil :a 1 :b) !!> wrong-number-of-arguments (h-st* 20 nil :a) !!> wrong-number-of-arguments (h-st* 20 :a 1 :b) !!> error
(&rest keys-value)
Return a one-key nested table with the KEYS-VALUE sequence.
So, for example, (h-reduce-r :a :b :c 2) is equivalent
to (h* :a (h* :b (h* :c 2))), which is the same
as (ht (:a (ht (:b (ht (:c 2)))))).
(h-reduce-r :a :b :c 2) Hp=> (h* :a (h* :b (h* :c 2))) (h-reduce-r :a :b :c 2) H=> (ht (:a (ht (:b (ht (:c 2)))))) (h-reduce-r :a :b 2) H=> (ht (:a (ht (:b 2)))) (h-reduce-r :a 2) H=> (ht (:a 2)) (h-reduce-r :a) !!> wrong-number-of-arguments (h-reduce-r) H=> (ht) ;; Both nominal and actual size are 1 (-> (h-reduce-r :a :b :c 2) (h-prop 'size)) => 1 (-> (h-reduce-r :a :b :c 2) ht-size) => 1
(key-form val-form keys values &optional size test)
Anaphoric version of h-zip-vectors-with.
The form KEY-FORM modifies each key.
The form VAL-FORM modifies each value.
Each element of KEYS is bound to the symbol key.
Each element of VALUES is bound to the symbol value.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
The (roughly) inverse of this function is h--vunzip-with.
(h--zip-vectors-with (format "%s-%s" key value) key [a b] [1 2]) Hp=> (h* "a-1" 'a "b-2" 'b) (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) [a b] [1 2]) Hp=> (h* "a" "1" "b" "2") (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) [a b] [1]) Hp=> (h* "a" "1" "b" nil) (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) [a b c] [1 2]) Hp=> (h* "a" "1" "b" "2" "c" nil) (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) [a b] [nil nil]) Hp=> (h* "a" nil "b" nil) (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) [a b] []) Hp=> (h* "a" nil "b" nil) (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) [] []) Hp=> (h-new 1) (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) []) !!> error (h--zip-vectors-with (symbol-name key) (when value (number-to-string value))) !!> error (h--zip-vectors-with (symbol-name key) (when value (number-to-string value)) 3) !!> error
(key-fun val-fun keys values &optional size test)
Create a hash table with KEYS and VALUES modified by functions.
The function KEY-FUN modifies each key.
The function VAL-FUN modifies each value.
Both functions take two variables: key and value, respectively.
Each KEY and VALUE is taken one by one from their corresponding
positions at the vectors KEYS and VALUES.
If KEYS is nil, return empty hash table.
If KEYS is shorter than VALUES, ignore additional elements of VALUES.
If VALUES is shorter than KEYS, complete VALUES with nils.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
The anaphoric version of this function is h--zip-vectors-with.
The (roughly) inverse of this function is h-vunzip-with.
(h-zip-vectors-with (lambda (key value) (format "%s-%s" key value)) (lambda (key _value) key) [a b] [1 2]) Hp=> (h* "a-1" 'a "b-2" 'b) (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) [a b] [1 2]) Hp=> (h* "a" "1" "b" "2") (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) [a b] [1]) Hp=> (h* "a" "1" "b" nil) (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) [a b c] [1 2]) Hp=> (h* "a" "1" "b" "2" "c" nil) (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) [a b] [nil nil]) Hp=> (h* "a" nil "b" nil) (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) [a b] []) Hp=> (h* "a" nil "b" nil) (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) [] []) Hp=> (h-new 1) (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) []) !!> error (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value)))) !!> error (h-zip-vectors-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) 3) !!> error
(keys values &optional size test)
Create a hash table with KEYS and VALUES. Both must be vectors.
This is just like h-zip-vectors-with, but without modifying any of
the keys and values before putting them on the table.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
The (roughly) inverse of this function is h-vunzip.
(h-zip-vectors [:a :b] [1]) Hp=> (h* :a 1 :b nil) (h-zip-vectors [:a :b] [1 2]) Hp=> (h* :a 1 :b 2) (h-zip-vectors [:a :b :c] [1 2]) Hp=> (h* :a 1 :b 2 :c nil) (h-zip-vectors [:a :b] [nil nil]) Hp=> (h* :a nil :b nil) (h-zip-vectors [:a :b] []) Hp=> (h* :a nil :b nil) (h-zip-vectors [] []) Hp=> (h-new 1) (h-zip-vectors []) !!> error (h-zip-vectors) !!> error (h-zip-vectors 3) !!> error
(key-form val-form keys values &optional size test)
Anaphoric version of h-zip-lists-with.
The form KEY-FORM modifies each key.
The form VAL-FORM modifies each value.
Each element of KEYS is bound to the symbol key.
Each element of VALUES is bound to the symbol value.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
The (roughly) inverse of this function is h--lunzip-with.
(h--zip-lists-with (format "%s" key) (when value (format "%s" value)) '(a b c d) '(1 2 3) 5 'equal) Hp=> (h-s* 5 "a" "1" "b" "2" "c" "3" "d" nil) (h--zip-lists-with (format "%s-%s" key value) key '(a b) '(1 2)) Hp=> (h* "a-1" 'a "b-2" 'b) (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '(a b) '(1 2)) Hp=> (h* "a" "1" "b" "2") (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '(a b) '(1)) Hp=> (h* "a" "1" "b" nil) (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '(a b c) '(1 2)) Hp=> (h* "a" "1" "b" "2" "c" nil) (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '(a b) '(nil nil)) Hp=> (h* "a" nil "b" nil) (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '(a b) '()) Hp=> (h* "a" nil "b" nil) (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '() '()) Hp=> (h-new 1) (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) '()) !!> error (h--zip-lists-with (symbol-name key) (when value (number-to-string value))) !!> error (h--zip-lists-with (symbol-name key) (when value (number-to-string value)) 3) !!> error
(key-fun val-fun keys values &optional size test)
Create a hash table with KEYS and VALUES modified by functions.
The function KEY-FUN modifies each key.
The function VAL-FUN modifies each value.
Both functions take two variables: key and value, respectively.
Each KEY and VALUE is taken one by one from their corresponding
positions at the lists KEYS and VALUES.
If KEYS is nil, return empty hash table.
If KEYS is shorter than VALUES, ignore additional elements of VALUES.
If VALUES is shorter than KEYS, complete VALUES with nils.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
The anaphoric version of this function is h--zip-lists-with.
The (roughly) inverse of this function is h-lunzip-with.
(h-zip-lists-with (lambda (key value) (format "%s-%s" key value)) (lambda (key _value) key) '(a b) '(1 2)) Hp=> (h* "a-1" 'a "b-2" 'b) (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '(a b) '(1 2)) Hp=> (h* "a" "1" "b" "2") (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '(a b) '(1)) Hp=> (h* "a" "1" "b" nil) (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '(a b c) '(1 2)) Hp=> (h* "a" "1" "b" "2" "c" nil) (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '(a b) '(nil nil)) Hp=> (h* "a" nil "b" nil) (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '(a b) '()) Hp=> (h* "a" nil "b" nil) (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '() '()) Hp=> (h-new 1) (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '()) !!> error (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value)))) !!> error (h-zip-lists-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) 3) !!> error
(keys values &optional size test)
Create a hash table with KEYS and VALUES. Both must be lists.
This is just like h-zip-lists-with, but without modifying any of
the keys and values before putting them on the table.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
The (roughly) inverse of this function is h-lunzip.
(h-zip-lists '(:a :b) '(1)) Hp=> (h* :a 1 :b nil) (h-zip-lists '(:a :b) '(1 2)) Hp=> (h* :a 1 :b 2) (h-zip-lists '(:a :b :c) '(1 2)) Hp=> (h* :a 1 :b 2 :c nil) (h-zip-lists '(:a :b) '(nil nil)) Hp=> (h* :a nil :b nil) (h-zip-lists '(:a :b) '()) Hp=> (h* :a nil :b nil) (h-zip-lists '() '()) Hp=> (h-new 1) (h-zip-lists '()) !!> error (h-zip-lists) !!> error (h-zip-lists 3) !!> error
Binding
Functions to bind the values of a hash table to variables.
The main and the helper functions of h-let were inspired by, and
adapted from, let-alist
.
Dot-binding (h-let and h-let-it)
(table &rest body)
Let-bind dotted symbols to their value in TABLE and execute BODY.
Dotted symbol is any symbol starting with a dot. Only those present
in BODY are let-bound, and this search is done at compile time.
For instance, the following code:
(h-let table (if (and .title .body) .body .site .site.contents))
essentially expands to:
(let ((.title (h-get 'title table)) (.body (h-get 'body table)) (.site (h-get 'site table)) (.site.contents (h-get 'contents (h-get 'site table)))) (if (and .title .body) .body .site .site.contents))
That is still somewhat simplified, however. In fact, this line:
(h-get 'title table)
looks more like:
(or (h-get "title" table) (h-get 'title table) (h-get :title table))
so that your query should work regardless of whether the key is a
string, a symbol, or a keyword. (Or even number.)
Note that if you mix these types in the keys and two keys happen to
share the same word (such as having both 'title and :title as keys
of the same table), results will be unpredictable. But this case
should be as rare as it's frequent the need for type autodetection
in a given one-typed-key hash table.
If you nest h-let invocations, the inner one can't access the
variables of the outer one. You can, however, access nested hash
tables by using dots inside the symbol, as showed in the example
above.
This function was inspired by, and adapted from, let-alist
.
See also: h-let-it.
(let ((my-htbl (h* 'id 1234 'payload (h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is..."))) (my-alist '((id . 1234) (payload (url . "https://example.com") (title . "Example Domain") (content . "This domain is..."))))) ;; These three plists are the same: (h-plist= (h-let my-htbl (list :id .id :url .payload.url)) (let-alist my-alist (list :id .id :url .payload.url)) '(:id 1234 :url "https://example.com"))) => t ; ^Adapted from a =let-alist= example from elisp-demos. (let ((tbl (h* :a 1 :foo "bar"))) (h-let tbl (format "Great %s!" .foo))) => "Great bar!" (h-let (h* 'user "bob" 'editor (h* 'home "Vim" 'work "Emacs")) (format "%s uses %s at home but %s at work." (capitalize .user) .editor.home .editor.work)) => "Bob uses Vim at home but Emacs at work." ;;;; Works with keys in different formats (h-let (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) .c.e) => 5 (h-let (h* 'a 1 'b 2 'c (h* 'd 4 'e 5)) .c.e) => 5 (h-let (h* :a 1 :b 2 :c (h* :d 4 :e 5)) .c.e) => 5 (h-let (h* "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21")) .b.name) => "bob" (h-let (h* 'b (h* 'id "b" 'name "bob" 'age "30") 'e (h* 'id "e" 'name "emily" 'age "21")) .b.name) => "bob" (h-let (h* :b (h* :id "b" :name "bob" :age "30") :e (h* :id "e" :name "emily" :age "21")) .b.name) => "bob" (h-let (h* :b (h* 'id "b" 'name "bob" 'age "30") :e (h* 'id "e" 'name "emily" 'age "21")) .b.name) => "bob" (h-let (h* 1 (h* 'id "b" 'name "bob" 'age "30") 2 (h* 'id "e" 'name "emily" 'age "21")) (capitalize .1.name)) => "Bob" (h-let (h* 1 (h* 'id "b" 'name "bob" 'age 30) 2 (h* 'id "e" 'name "emily" 'age 21)) .2.age) => 21 (h-let (h* "01" (h* "id" "01" "name" "Alice" "age" "42" "editor" "Emacs") "02" (h* "id" "02" "name" "Bob" "age" "30" "editor" "Vim") "03" (h* "id" "03" "name" "Emily" "age" "21" "editor" "Nano")) (downcase .01.editor)) => "emacs" ;;;; Can promptly be used after conversions (h-let (h<-orgtbl "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |") .01.editor) => "Emacs" (-> "| name | age | editor | |-------+-----+--------| | Alice | 42 | Emacs | | Bob | 30 | Vim | | Emily | 21 | Nano |" h<-orgtbl (h-let .Alice.editor)) => "Emacs" (--> "| id | name | age | editor | |------+-------+-----+--------| | id01 | Alice | 42 | Emacs | | id02 | Bob | 30 | Vim | | id03 | Emily | 21 | Nano |" h<-orgtbl (h-let it .id01.age) string-to-number) => 42 (--> "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |" h<-orgtbl (h-let it .b.name) capitalize) => "Bob"
(thing &rest body)
Let-bind dotted symbols to their value in THING and execute BODY.
Behind the scenes, it converts key–value THING to hash table using
h<-it, and then dot-binds the result using h-let.
;; NOTE: For lists and lines: better decompose and use the specific ;; conversion functions to avoid wrong auto-detection of types. ;; ;; A list with even number of items will be thought of as a plist, for ;; example; and a string with many lines could be parsed as TSV or KVL ;; depending on the presence of characters that could be detected as ;; separators. ;;; 1D: implicit ;;;; list (let ((thing '(Alice Bob Charlie))) (h-let-it thing .\1)) => 'Bob ;; the above works only because no other kv thing would match — better ;; to explicitly decompose it as follows: (let ((thing '(Alice Bob Charlie))) (h-let (h<-list thing) .\1)) => 'Bob (let ((thing '(Alice Bob Charlie))) (h-let (h<-list thing) (format "%s and %s" .\0 .\1))) => "Alice and Bob" (let ((thing '(a b c d e f g h i j k l m n o p q r s t u v w x y z))) (h-let (h<-list thing) (format "%s and %s" (s-titleize (format "%s%s%s%s%s" .\0 .\11 .\8 .\2 .\4)) (s-titleize (format "%s%s%s" .\1 .\14 .\1))))) => "Alice and Bob" ;;;; vector (let ((thing [Alice Bob Charlie])) (h-let-it thing (format "%s and %s" .\0 .\1))) => "Alice and Bob" (let ((thing "Alice\nBob\nCharlie")) (h-let-it thing (format "%s and %s" .\0 .\1))) => "Alice and Bob" ;;;; lines (let ((thing "\ Alice Bob Charlie")) (h-let-it thing (format "%s and %s" .\0 .\1))) => "Alice and Bob" ;; the above works only because no other kv thing would match — better ;; to explicitly decompose as below: (let ((thing "\ Alice Bob Charlie")) (h-let (h<-lines thing) (format "%s and %s" .\0 .\1))) => "Alice and Bob" ;;; 1D: explicit ;;;; key–value lines (kvl) (let ((thing "Alice=42\nBob=21")) (h-let-it thing .Alice)) => "42" (let ((thing "Alice = 42\nBob = 21")) (h-let-it thing .Alice)) => "42" (let ((h-kvl-sep-re ":") (thing "Alice:42\nBob:21")) (h-let-it thing (h-as-number .Alice))) => 42 (let ((h-kvl-sep-re " *: *") (thing "\ Alice : 42 Bob : 21")) (h-let-it thing (h-as-number .Alice))) => 42 ;;;; cons cell (let ((thing '(Alice . 42))) (h-let-it thing .Alice)) => 42 ;;; ≥1D unspecific ;;;; alist (let ((thing '((a . Alice) (b . ((ba . Barbara) (bo . Bob)))))) (h-let-it thing .b.bo)) => 'Bob (let ((thing '((:a . Alice) (:b . ((:ba . Barbara) (:bo . Bob)))))) (h-let-it thing .b.bo)) => 'Bob (let ((thing '(("a" . "Alice") ("b" . (("ba" . "Barbara") ("bo" . "Bob")))))) (h-let-it thing .b.bo)) => "Bob" ;;;; plist (let ((thing (list 'a 'Alice 'b (list 'ba 'Barbara 'bo 'Bob)))) (h-let-it thing .b.bo)) => 'Bob (let ((thing (list :a 'Alice :b (list :ba 'Barbara :bo 'Bob)))) (h-let-it thing .b.bo)) => 'Bob (let ((thing (list "a" "Alice" "b" (list "ba" "Barbara" "bo" "Bob")))) (h-let-it thing (h-as-keyword .b.bo))) => :Bob ;;;; json (let ((thing "{ \"a\": \"Alice\", \"b\": { \"ba\": \"Barbara\", \"bo\": \"Bob\" } }")) (h-let-it thing .b.bo)) => "Bob" ;;;; hash table (let ((thing (h* 'a 'Alice 'b (h* 'ba 'Barbara 'bo 'Bob)))) (h-let-it thing .b.bo)) => 'Bob ;; the above is fine — but [[#h-let][h-let]], which expects a hash table, is shorter ;; and a bit cheaper: (let ((thing (h* 'a 'Alice 'b (h* 'ba 'Barbara 'bo 'Bob)))) (h-let thing .b.bo)) => 'Bob ;; any hash table format should work — here, in (ht…): (let ((thing (ht ('a 'Alice) ('b (ht ('ba 'Barbara) ('bo 'Bob)))))) (h-let thing .b.bo)) => 'Bob ;; and here in that ungainly native format (trimmed of some params): (let ((thing #s(hash-table size 2 test equal data (a Alice b #s(hash-table size 2 test equal data (ba Barbara bo Bob)))))) (h-let thing .b.bo)) => 'Bob ;;; 2D ;;;; list of lists (lol) (let ((thing '((name age editor) (Alice 42 Emacs) (Bob 30 Vim) (Emily 21 Nano)))) (h-let-it thing .Alice.age)) => 42 (let ((thing '((name age editor) (Alice 42 Emacs) (Bob 30 Vim) (Emily 21 Nano)))) (h-let-it thing (format "%s is %s and prefers %s." "Alice" .Alice.age .Alice.editor))) => "Alice is 42 and prefers Emacs." ;;;; org table (let ((thing "| name | age | editor | |-------+-----+--------| | Alice | 42 | Emacs | | Bob | 30 | Vim | | Emily | 21 | Nano |")) (h-let-it thing (h-as-number .Alice.age))) => 42 ;;;; tsv (let ((thing "name\tage\nAlice\t42\nBob\t21")) (h-let-it thing .Alice.age)) => "42" (let ((thing "\ name\tage Alice\t42 Bob\t21")) (h-let-it thing .Alice.age)) => "42" (let ((thing "\ name age Alice 42 Bob 21")) (h-let-it thing .Alice.age)) => "42" (--> "name\tage\nAlice\t42\nBob\t21" (h-let-it it ;<--- we're literally h-letting-it! .Alice.age)) => "42" ;;;; csv (let ((thing "name,age\nAlice,42\nBob,21")) (h-let-it thing .Alice.age)) => "42" (let ((thing "\ name,age Alice,42 Bob,21")) (h-let-it thing .Alice.age)) => "42" ;;;; ssv (let ((thing "\ name age editor Alice 42 Emacs Bob 21 Vim Emily 30 Nano")) (h-let-it thing (format "%s is %s and prefers %s." "Alice" .Alice.age .Alice.editor))) => "Alice is 42 and prefers Emacs."
Canonical representations
Changing canonical representation — from table
Functions returning a canonical form that would've generated a hash table.
Note that hash tables will fail equality tests such as:
(eq #s(hash-table test equal data ('a 1)) #s(hash-table test equal data ('a 1))) ;=> nil
or:
(eql #s(hash-table test equal data ('a 1)) #s(hash-table test equal data ('a 1))) ;=> nil
or:
(equal #s(hash-table test equal data ('a 1)) #s(hash-table test equal data ('a 1))) ;=> nil
or:
(equal (ht ('a 1)) (ht ('a 1))) => nil
But, of course:
(equal '(ht ('a 1)) '(ht ('a 1))) => t (equal '(h* 'a 1) '(h* 'a 1)) => t
So the comparisons below work.
Note: h-htbl-form, not shown here, is just the table itself in
regular form: #s(hash-table…)
(table)
Hash TABLE object as itself.
Function defined only so we have a parallel with ht and h* forms.
(h-htbl-form (ht ("a" 1))) H=> #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1)) (h-htbl-form (ht ("a" 1) ("b" 2))) H=> #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1 "b" 2)) (h-htbl-form #s(hash-table test equal data ("a" 1))) H=> #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1)) (h-htbl-form #s(hash-table test equal data ("a" 1 "b" 2))) H=> #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1 "b" 2)) (h-htbl-form (ht ("a" 1) ("b" (ht ("c" 3))))) H=> #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1 "b" #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("c" 3)))) (h-htbl-form (ht-create)) ; ht doesn't specify size/test H=> #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data ()) (h-htbl-form (h-new 3)) H=> #s(hash-table size 3 test equal rehash-size 1.5 rehash-threshold 0.8125 data ())
(table)
Hash TABLE object as a string in compact representation.
(h-htbl-cm-str (ht ("a" 1))) => "#s(hash-table size 65 test equal rehash-size 1.5\ rehash-threshold 0.8125 data (\"a\" 1))" (h-htbl-cm-str (ht ("a" 1) ("b" 2))) => "#s(hash-table size 65 test equal rehash-size 1.5\ rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))" (h-htbl-cm-str #s(hash-table test equal data ("a" 1))) => "#s(hash-table size 65 test equal rehash-size 1.5\ rehash-threshold 0.8125 data (\"a\" 1))" (h-htbl-cm-str #s(hash-table test equal data ("a" 1 "b" 2))) => "#s(hash-table size 65 test equal rehash-size 1.5\ rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))" (h-htbl-cm-str (ht ("a" 1) ("b" (ht ("c" 3))))) => "#s(hash-table size 65 test equal rehash-size 1.5\ rehash-threshold 0.8125 data (\"a\" 1 \"b\" #s(hash-table size 65\ test equal rehash-size 1.5 rehash-threshold 0.8125 data (\"c\" 3))))" (h-htbl-cm-str (ht-create)) ; ht doesn't specify size/test => "#s(hash-table size 65 test equal rehash-size 1.5\ rehash-threshold 0.8125 data ())" (h-htbl-cm-str (h-new 3)) => "#s(hash-table size 3 test equal rehash-size 1.5\ rehash-threshold 0.8125 data ())"
(table)
Hash TABLE object as a string in pretty-printed representation.
;; The let is only necessary for testing. Used to make sure the string is ;; EXACTLY the same when pretty-printing, since different Emacsen may have ;; different configs for indentation: (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-htbl-pp-str (ht ("a" 1) ("b" 2)))) => "#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"a\" 1 \"b\" 2))\n" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-htbl-pp-str #s(hash-table test equal data ("a" 1 "b" 2)))) => "#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"a\" 1 \"b\" 2))\n" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-htbl-pp-str (ht ("a" 1) ("b" (ht ("c" 3) ("d" 4)))))) => "#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"a\" 1 \"b\" #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"c\" 3 \"d\" 4))))\n" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-htbl-pp-str (ht-create))) ; ht doesn't specify size/test => "#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n ())\n" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-htbl-pp-str (h-new 3 'eq))) => "#s(hash-table size 3 test eq rehash-size 1.5 rehash-threshold 0.8125 data\n ())\n"
(table)
Restore canonical (ht…) form that creates hash TABLE.
Note that this doesn't preserve TABLE's equality test: (ht…)
defaults to 'equal as test, so if TABLE's test is anything else, it
will become 'equal if the form is evaluated.
(h-ht-form (ht ("a" 1))) => '(ht ("a" 1)) (h-ht-form (ht ("a" 1) ("b" 2))) => '(ht ("a" 1) ("b" 2)) (h-ht-form #s(hash-table test equal data ("a" 1))) => '(ht ("a" 1)) (h-ht-form #s(hash-table test equal data ("a" 1 "b" 2))) => '(ht ("a" 1) ("b" 2)) (h-ht-form (ht ("a" 1) ("b" (ht ("c" 3))))) => '(ht ("a" 1) ("b" (ht ("c" 3)))) (h-ht-form (ht-create)) => '(ht) ; ht doesn't specify size/test (h-ht-form (h-new 3 'eq)) => '(ht)
(table)
Restore as string canonical (ht…) form that creates hash TABLE.
Compact representation.
(h-ht-cm-str (ht ("a" 1))) => "(ht (\"a\" 1))" (h-ht-cm-str (ht ("a" 1) ("b" 2))) => "(ht (\"a\" 1) (\"b\" 2))" (h-ht-cm-str #s(hash-table test equal data ("a" 1))) => "(ht (\"a\" 1))" (h-ht-cm-str #s(hash-table test equal data ("a" 1 "b" 2))) => "(ht (\"a\" 1) (\"b\" 2))" (h-ht-cm-str (ht ("a" 1) ("b" (ht ("c" 3))))) => "(ht (\"a\" 1) (\"b\" (ht (\"c\" 3))))" (h-ht-cm-str (ht-create)) => "(ht)" ; ht doesn't specify size/test (h-ht-cm-str (h-new 3 'eq)) => "(ht)"
(table)
The pretty-print string of (ht…) form that creates hash TABLE.
(h-ht-pp-str (ht ("a" 1))) => "(ht (\"a\" 1))" ;; The let is only necessary for testing. Used to make sure the string is ;; EXACTLY the same when pretty-printing, since different Emacsen may have ;; different configs for indentation: (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-ht-pp-str (ht ("a" 1) ("b" 2)))) => "(ht (\"a\" 1)\n (\"b\" 2))" (h-ht-pp-str #s(hash-table test equal data ("a" 1))) => "(ht (\"a\" 1))" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-ht-pp-str #s(hash-table test equal data ("a" 1 "b" 2)))) => "(ht (\"a\" 1)\n (\"b\" 2))" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-ht-pp-str (ht ("a" 1) ("b" (ht ("c" 3) ("d" 4)))))) => "(ht (\"a\" 1)\n (\"b\" (ht (\"c\" 3)\n (\"d\" 4))))" (h-ht-pp-str (ht-create)) => "(ht)" ; ht doesn't specify size/test (h-ht-pp-str (h-new 3 'eq)) => "(ht)"
(table)
Restore canonical (h*…) or (h-st*…) form that creates hash TABLE.
If TABLE's test is:
- 'equal, we use (h*…), which preserves it. Note that if this form
is evaluated the nominal size of that table will be determined by
h*. (This is unlikely to be an issue.) - not 'equal, we recover its nominal size and equality test and
use (h-st*…).
(h-h*-form (h* "a" 1)) => '(h* "a" 1) (h-h*-form (ht ("a" 1))) => '(h* "a" 1) (h-h*-form #s(hash-table test equal data ("a" 1))) => '(h* "a" 1) (h-h*-form (h* "a" 1 "b" 2)) => '(h* "a" 1 "b" 2) (h-h*-form (ht ("a" 1) ("b" 2))) => '(h* "a" 1 "b" 2) (h-h*-form #s(hash-table test equal data ("a" 1 "b" 2))) => '(h* "a" 1 "b" 2) (h-h*-form (ht ("a" 1) ("b" (ht ("c" 3))))) => '(h* "a" 1 "b" (h* "c" 3)) (h-h*-form (ht-create)) => '(h*) (h-h*-form (h-new nil 'eq)) => '(h-st* 65 'eq) (h-h*-form (h-new 3 'eq)) => '(h-st* 3 'eq) (h-h*-form (h-t* 'eq :a 1)) => '(h-st* 1 'eq :a 1) ;; when only size is different, this is ignored and ;; the simpler form is used: (h-h*-form (h-new 3 'equal)) => '(h*) (h-h*-form (h-new 3)) => '(h*) (h-h*-form (h-s* 1 "a" 1)) => '(h* "a" 1)
(table)
Restore as string (h*…) or (h-st*…) form that creates hash TABLE.
Compact representation.
(h-h*-cm-str (h* "a" 1)) => "(h* \"a\" 1)" (h-h*-cm-str (ht ("a" 1))) => "(h* \"a\" 1)" (h-h*-cm-str #s(hash-table test equal data ("a" 1))) => "(h* \"a\" 1)" (h-h*-cm-str (h* "a" 1 "b" 2)) => "(h* \"a\" 1 \"b\" 2)" (h-h*-cm-str (ht ("a" 1) ("b" 2))) => "(h* \"a\" 1 \"b\" 2)" (h-h*-cm-str #s(hash-table test equal data ("a" 1 "b" 2))) => '"(h* \"a\" 1 \"b\" 2)" (h-h*-cm-str (ht ("a" 1) ("b" (ht ("c" 3) ("d" 4))))) => "(h* \"a\" 1 \"b\" (h* \"c\" 3 \"d\" 4))" (h-h*-cm-str (ht-create)) => "(h*)" (h-h*-cm-str (h-new nil 'eq)) => "(h-st* 65 'eq)" (h-h*-cm-str (h-new 3 'eq)) => "(h-st* 3 'eq)" (h-h*-cm-str (h-t* 'eq :a 1)) => "(h-st* 1 'eq :a 1)" ;; when only size is different, this is ignored and ;; the simpler form is used: (h-h*-cm-str (h-new 3 'equal)) => "(h*)" (h-h*-cm-str (h-new 3)) => "(h*)" (h-h*-cm-str (h-s* 1 "a" 1)) => "(h* \"a\" 1)"
(table)
The pretty-print string of (h*…) form that creates hash TABLE.
If table's test isn't 'equal, use (h-st*…) form.
(h-h*-pp-str (h* "a" 1)) => "(h* \"a\" 1)" (h-h*-pp-str (ht ("a" 1))) => "(h* \"a\" 1)" (h-h*-pp-str #s(hash-table test equal data ("a" 1))) => "(h* \"a\" 1)" ;; The let is only necessary for testing. Used to make sure the string is ;; EXACTLY the same when pretty-printing, since different Emacsen may have ;; different configs for indentation: (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-h*-pp-str (h* "a" 1 "b" 2))) => "(h* \"a\" 1\n \"b\" 2)" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-h*-pp-str (ht ("a" 1) ("b" 2)))) => "(h* \"a\" 1\n \"b\" 2)" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-h*-pp-str #s(hash-table test equal data ("a" 1 "b" 2)))) => '"(h* \"a\" 1\n \"b\" 2)" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-h*-pp-str (ht ("a" 1) ("b" (ht ("c" 3) ("d" 4)))))) => "(h* \"a\" 1\n \"b\" (h* \"c\" 3\n \"d\" 4))" (h-h*-pp-str (ht-create)) => "(h*)" (h-h*-pp-str (h-new nil 'eq)) => "(h-st* 65 'eq)" (h-h*-pp-str (h-new 3 'eq)) => "(h-st* 3 'eq)" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-h*-pp-str (h-t* 'eq :a 1))) => "(h-st* 1 'eq\n :a 1)" ;; when only size is different, this is ignored and ;; the simpler form is used: (h-h*-pp-str (h-new 3 'equal)) => "(h*)" (h-h*-pp-str (h-new 3)) => "(h*)" (h-h*-pp-str (h-s* 1 "a" 1)) => "(h* \"a\" 1)"
Changing canonical representation — from any ht-like form
Choose different representations of hash tables.
(ht-like)
Convert an HT-LIKE object into a lambda that creates the table.
HT-LIKE may come in any of the following autodetected formats:
As Lisp objects:
- #s(hash-table…) = regular representation
- (ht…) form
- (h*…), (h-s*…), (h-t*…), or (h-st*…) form
or any of the above formatted as string (presumably with %S).
(h-lambdify '(h*)) => `(lambda () (h*)) (h-lambdify "(h* :a 1 :b 2)") => `(lambda () (h* :a 1 :b 2)) (-> "(h* :a 1 :b 2)" h-lambdify funcall) H=> (h* :a 1 :b 2) (-> '(h* :a 1 :b 2) h-lambdify funcall) H=> (h* :a 1 :b 2) (-> '(ht (:a 1) (:b 2)) h-lambdify funcall) H=> (h* :a 1 :b 2)
(format ht-like)
Format as FORMAT hash table–like object HT-LIKE.
HT-LIKE may come in any of the following autodetected formats:
As Lisp objects:
- #s(hash-table…) = regular representation
- (ht…) form
- (h*…), (h-s*…), (h-t*…), or (h-st*…) form
or as strings:
Any of the above formatted as string (presumably with %S)
The output FORMAT, in turn, must be specified as one of:
- htbl :: #s(hash-table…) (Lisp object)
- h*-form :: (h*…) form (Lisp object)
- ht-form :: (ht…) form (Lisp object)
- htbl-cm-str :: #s(hash-table…) string in compact format
- htbl-pp-str :: #s(hash-table…) string in pretty-printed format
- h*-cm-str :: (h*…) string in compact format
- h*-pp-str :: (h*…) string in pretty-printed format
- ht-cm-str :: (ht…) string in compact format
- ht-pp-str :: (ht…) string in pretty-printed format
Any of these FORMAT options may be passed as either a string, a
keyword, or a symbol — as you prefer. So, for example, any among
"htbl", :htbl, or 'htbl works.
(h-format :htbl (h* :a 1 :b (h* :c 3 :d 4))) H=> #s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data (:a 1 :b #s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data (:c 3 :d 4)))) (h-format :htbl (h* :a 1 :b (h* :c 3 :d 4))) H=> (h* :a 1 :b (h* :c 3 :d 4)) (h-format :h*-form (h* :a 1 :b (h* :c 3 :d 4))) => '(h* :a 1 :b (h* :c 3 :d 4)) (h-format 'h*-form (h* :a 1 :b (h* :c 3 :d 4))) => '(h* :a 1 :b (h* :c 3 :d 4)) (h-format "h*-form" (h* :a 1 :b (h* :c 3 :d 4))) => '(h* :a 1 :b (h* :c 3 :d 4)) (h-format :ht-form (h* :a 1 :b (h* :c 3 :d 4))) => '(ht (:a 1) (:b (ht (:c 3) (:d 4)))) (h-format :htbl-cm-str (h* :a 1 :b (h* :c 3 :d 4))) => "#s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data\ (:a 1 :b #s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data (:c 3 :d 4))))" (h-format :htbl-pp-str (h* :a 1 :b (h* :c 3 :d 4))) => "#s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data\n\ (:a 1 :b #s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data\n\ (:c 3 :d 4))))\n" (h-format :h*-cm-str (h* :a 1 :b (h* :c 3 :d 4))) => "(h* :a 1 :b (h* :c 3 :d 4))" (h-format :ht-cm-str (h* :a 1 :b (h* :c 3 :d 4))) => "(ht (:a 1) (:b (ht (:c 3) (:d 4))))" ;; The let is only necessary for testing. Used to make sure the string is ;; EXACTLY the same when pretty-printing, since different Emacsen may have ;; different configs for indentation: (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-format :h*-pp-str (h* :a 1 :b (h* :c 3 :d 4)))) => "(h* :a 1\n :b (h* :c 3\n :d 4))" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (h-format :ht-pp-str (h* :a 1 :b (h* :c 3 :d 4)))) => "(ht (:a 1)\n (:b (ht (:c 3)\n (:d 4))))" (h-format :h*-form (h-t* 'eq :a "a" :b "b")) => '(h-st* 2 'eq :a "a" :b "b") (h-format :h*-form '((I . am) (an . alist!))) !!> error
Mapping from hash table
to vector
Functions that apply a function to every key–value pair of a
hash table and write the results to a vector.
In particular, here's a guideline for the 'vmap family' of
functions:
- the added 'v' means 'result is a vector'
- an added '-' means 'anaphoric: enter a form, not a lambda'
In the anaphoric version, if you don't use any or either of the
let-bound variables key and value in any or either of the forms,
it's ok — no warnings.
Summary:
Side-effect-free: don't modify original TABLE, return a vector
a lambda | a form (anaphoric) | |
---|---|---|
vector may have nils | h-vmap | h--vmap |
(fun table &optional reverse)
Apply FUN to each key–value pair of TABLE and return vector.
Function FUN is called with two arguments, key and value.
If REVERSE is non-nil, show it in reverse order.
If you don't use both of these variables, then to avoid warnings
use an underscore before the to-be-ignored variable. For example,
to return a vector of all (expected to be integer) values plus 1:
(h-vmap (lambda (_key value) (1+ value)) table)
Its anaphoric counterpart is h--vmap.
(let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-vmap fun h31)) => ["Bob: cat!" "Whiskers: cat!" "Bubbly: fish!"] (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-vmap fun h31 'rev)) => ["Bubbly: fish!" "Whiskers: cat!" "Bob: cat!"] (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (_key value) (when (numberp value) (expt (+ 10 value) 2))))) (h-vmap fun h41)) => [121 144 nil] (let ((h42 (h* "a" 1 'd (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (key value) (format "%s→%s" (type-of key) (type-of value))))) (h-vmap fun h42)) => ["string→integer" "symbol→hash-table"] (h-vmap (lambda (key value) (concat (upcase key) "-" (int-to-string value))) (h* "a" 1 "b" 2 "c" 3)) => ["A-1" "B-2" "C-3"] (let ((h11 (h-new 5)) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-vmap fun h11)) => []
(form table &optional reverse)
Anaphoric version of h-vmap.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound. Results given as vector.
If REVERSE is non-nil, show it in reverse order.
(let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--vmap (format "%s: %s!" key value) h31)) => ["Bob: cat!" "Whiskers: cat!" "Bubbly: fish!"] (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--vmap (format "%s: %s!" key value) h31 'rev)) => ["Bubbly: fish!" "Whiskers: cat!" "Bob: cat!"] (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--vmap (when (numberp value) (expt (+ 10 value) 2)) h41)) => [121 144 nil] (let ((h42 (h* "a" 1 'd (h* "d1" 41 "d2" 42 "e1" 51)))) (h--vmap (format "%s→%s" (type-of key) (type-of value)) h42)) => ["string→integer" "symbol→hash-table"] (h--vmap (concat (upcase key) "-" (int-to-string value)) (h* "a" 1 "b" 2 "c" 3)) => ["A-1" "B-2" "C-3"] (let ((h11 (h-new 5))) (h--vmap (format "%s: %s!" key value) h11)) => []
(key-form val-form table &optional reverse)
Anaphoric version of h-vunzip-with.
The form KEY-FORM modifies each key.
The form VAL-FORM modifies each value.
Both forms take two variables: key and value, respectively.
Each key and value is taken one by one from their corresponding
pairs at the hash table TABLE.
If REVERSE is non-nil, reverse the order of both vectors.
The (roughly) inverse of this function is h-zip-vectors.
See also: h--lunzip-with.
(h--vunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a 1 'b nil)) => '(["a" "b"] ["1" nil]) (h--vunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a 1 'b 2)) => '(["a" "b"] ["1" "2"]) (h--vunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a 1 'b 2 'c nil)) => '(["a" "b" "c"] ["1" "2" nil]) (h--vunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a nil 'b nil)) => '(["a" "b"] [nil nil]) (h--vunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a nil 'b nil)) => '(["a" "b"] [nil nil]) (h--vunzip-with (symbol-name key) (when value (number-to-string value)) (h-new 1)) => '([] []) (h--vunzip-with (symbol-name key) (when value (number-to-string value)) ) !!> wrong-number-of-arguments (h--vunzip-with (symbol-name key) (when value (number-to-string value)) '()) !!> error (h--vunzip-with (symbol-name key) (when value (number-to-string value)) 3) !!> error
(key-fun val-fun table &optional reverse)
Return a list of modified vectors of KEYS and VALUES from TABLE.
If REVERSE is non-nil, reverse the order of both vectors.
The function KEY-FUN modifies each key in the results.
The function VAL-FUN modifies each value in the results.
Both functions take two variables: key and value, respectively.
Each key and value is taken one by one from their corresponding
pairs at the hash table TABLE.
The (roughly) inverse of this function is h-zip-vectors.
Its anaphoric counterpart is h--vunzip-with.
See also: h-lunzip-with.
(h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a 1 'b nil)) => '(["a" "b"] ["1" nil]) (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a 1 'b 2)) => '(["a" "b"] ["1" "2"]) (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a 1 'b 2 'c nil)) => '(["a" "b" "c"] ["1" "2" nil]) (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a nil 'b nil)) => '(["a" "b"] [nil nil]) (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a nil 'b nil)) => '(["a" "b"] [nil nil]) (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h-new 1)) => '([] []) (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) ) !!> wrong-number-of-arguments (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '()) !!> error (h-vunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) 3) !!> error
(table &optional reverse)
Return a list of the vectors of KEYS and VALUES from TABLE.
If REVERSE is non-nil, reverse the order of both vectors.
The (roughly) inverse of this function is h-zip-vectors.
See also: h-lunzip.
(h-vunzip (h* :a 1 :b nil)) => '([:a :b] [1 nil]) (h-vunzip (h* :a 1 :b 2)) => '([:a :b] [1 2]) (h-vunzip (h* :a 1 :b 2 :c nil)) => '([:a :b :c] [1 2 nil]) (h-vunzip (h* :a nil :b nil)) => '([:a :b] [nil nil]) (h-vunzip (h* :a nil :b nil)) => '([:a :b] [nil nil]) (h-vunzip (h-new 1)) => '([] []) (h-vunzip) !!> wrong-number-of-arguments (h-vunzip '()) !!> error (h-vunzip 3) !!> error
(table &optional reverse)
Return a vector of two-element lists '(key value) from TABLE.
If REVERSE is non-nil, show it in reverse order (the vector;
the pair remains in the (key, value) order).
(h-vitems (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) => [("Bob" "cat") ("Whiskers" "cat") ("Bubbly" "fish")] (h-vitems (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish") 'reverse) => [("Bubbly" "fish") ("Whiskers" "cat") ("Bob" "cat")] (h-vitems (h-new 5)) => []
(table &optional reverse)
Return a vector of all the keys in TABLE.
If REVERSE is non-nil, show it in reverse order.
(h-vkeys (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) => ["Bob" "Whiskers" "Bubbly"] (h-vkeys (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish") 'reverse) => ["Bubbly" "Whiskers" "Bob"] (h-vkeys (h-new 5)) => [] ;; This function isn't recursive: (h-vkeys (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) => ["a" "b" "d"] (h-vkeys (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)) t) => ["d" "b" "a"]
(table &optional reverse)
Return a vector of all the values in TABLE.
If REVERSE is non-nil, show it in reverse order.
(h-vvalues (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) => ["cat" "cat" "fish"] (h-vvalues (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish") 'rev) => ["fish" "cat" "cat"] (h-vvalues (h-new 5)) => []
(table &optional n)
Return a vector of N pseudo-randomly chosen items from hash TABLE.
N must be an integer; if not, signal error.
- If N is nil, make it N=1.
- If N≥ size(TABLE), return a vector of all TABLE's items (same
result as running h-vitems). - If N=0, return nil.
- If N=1, item is returned as a two-item list (key, value).
- If N>1, items are returned as a vector of two-item lists (key, value).
- If N is negative, make N = N + size(TABLE). For example, if TABLE
has 10 items and N=-2, return 8 pseudo-random items.
See also: h-lrandom.
;;;; It's random, so all we can do is check length and whether subset (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (items (h-items htbl)) (res (h-vrandom htbl 2)) (res-l (append res nil))) (and (= 2 (length res)) (equal res-l (-intersection res-l items)))) => t ;;;; One by one is repetitive. Let's check many at once? (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (items (h-items htbl)) (ns '(1 2 3 4 5 -1 -2)) (sizes '(1 2 3 4 4 3 2))) (--every (and (= (nth it-index sizes) (length it)) (equal (append it nil) (-intersection (append it nil) items))) (-map (-cut h-vrandom htbl <>) ns))) => t (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (res (h-vrandom htbl)) (res-l (append res nil))) (and (= 1 (length res)) (equal res-l (-intersection res-l (h-items htbl))))) => t (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4))) (null (h-vrandom htbl 0))) => t (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4))) (null (h-vrandom htbl "something"))) !!> error (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4))) (null (h-vrandom htbl 3.5))) !!> error
to list
Functions that apply a function to every key–value pair of a
hash table and write the results to a list.
In particular, here's a guideline for the 'lmap/lkeep family' of
functions:
- the added 'l' means 'result is a list'
- an added '-' means 'anaphoric: enter a form, not a lambda'
In the anaphoric versions, if you don't use any or either of the
let-bound variables key and value in any or either of the forms,
it's ok — no warnings.
Summary:
Side-effect-free: don't modify original TABLE, return a list
a lambda | a form (anaphoric) | |
---|---|---|
list may have nils | h-lmap | h--lmap |
any nils are removed | h-lkeep | h--lkeep |
(fun table &optional reverse)
Apply FUN to each key–value pair of TABLE; list the results.
Function FUN is called with two arguments, key and value.
Identical to current ht-map
, with the difference that the
returned list matches the current order of the TABLE's keys.
If REVERSE is non-nil, show it in reverse order.
If you don't use both of these variables, then to avoid warnings
use an underscore before the to-be-ignored variable. For example,
to return a list of all (expected to be integer) values plus 1:
(h-lmap (lambda (_key value) (1+ value)) table).
Its anaphoric counterpart is h--lmap.
See also: h-vmap.
(h-lmap #'concat (ht ("Bob" "cat") ("Whiskers" "cat") ("Bubbly" "fish"))) => '("Bobcat" "Whiskerscat" "Bubblyfish") (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-lmap fun h31)) => '("Bob: cat!" "Whiskers: cat!" "Bubbly: fish!") (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-lmap fun h31 'rev)) => '("Bubbly: fish!" "Whiskers: cat!" "Bob: cat!") (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (_key value) (when (numberp value) (expt (+ 10 value) 2))))) (h-lmap fun h41)) => '(121 144 nil) (let ((h42 (h* "a" 1 'd (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (key value) (format "%s→%s" (type-of key) (type-of value))))) (h-lmap fun h42)) => '("string→integer" "symbol→hash-table") (h-lmap (lambda (key value) (concat (upcase key) "-" (int-to-string value))) (h* "a" 1 "b" 2 "c" 3)) => '("A-1" "B-2" "C-3") (let ((h11 (h-new 5)) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-lmap fun h11)) => '()
(form table &optional reverse)
Anaphoric version of h-lmap.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound. List the results.
If REVERSE is non-nil, show it in reverse order.
See also: h--vmap.
(let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--lmap (format "%s: %s!" key value) h31)) => '("Bob: cat!" "Whiskers: cat!" "Bubbly: fish!") (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--lmap (when (numberp value) (expt (+ 10 value) 2)) h41)) => '(121 144 nil) (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--lmap (when (numberp value) (expt (+ 10 value) 2)) h41 'rev)) => '(nil 144 121) (let ((h42 (h* "a" 1 'd (h* "d1" 41 "d2" 42 "e1" 51)))) (h--lmap (format "%s→%s" (type-of key) (type-of value)) h42)) => '("string→integer" "symbol→hash-table") (h--lmap (concat (upcase key) "-" (int-to-string value)) (h* "a" 1 "b" 2 "c" 3)) => '("A-1" "B-2" "C-3") (let ((h11 (h-new 5))) (h--lmap (format "%s: %s!" key value) h11)) => '()
(fun table &optional reverse)
Apply FUN to each key–value pair of TABLE; list non-nil results.
We follow Dash's naming scheme here, for which "keep" is just like
"map" — but with the nils removed.
Function FUN is called with two arguments, key and value.
If REVERSE is non-nil, show it in reverse order.
As with h-lmap, if you don't use both of these variables, then to
avoid warnings use an underscore before the to-be-ignored variable.
Its anaphoric counterpart is h--lkeep.
(let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-lkeep fun h31)) => '("Bob: cat!" "Whiskers: cat!" "Bubbly: fish!") (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (_key value) (when (numberp value) (expt (+ 10 value) 2))))) (h-lkeep fun h41)) => '(121 144) (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (_key value) (when (numberp value) (expt (+ 10 value) 2))))) (h-lkeep fun h41 'rev)) => '(144 121) (let ((h42 (h* "a" 1 'd (h* "d1" 41 "d2" 42 "e1" 51))) (fun (lambda (key value) (format "%s→%s" (type-of key) (type-of value))))) (h-lkeep fun h42)) => '("string→integer" "symbol→hash-table") (let ((h11 (h-new 5)) (fun (lambda (key value) (format "%s: %s!" key value)))) (h-lkeep fun h11)) => '()
(form table &optional reverse)
Anaphoric version of h-lkeep.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound. List the non-nil results.
If REVERSE is non-nil, show it in reverse order.
(let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--lkeep (format "%s: %s!" key value) h31)) => '("Bob: cat!" "Whiskers: cat!" "Bubbly: fish!") (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--lkeep (when (numberp value) (expt (+ 10 value) 2)) h41)) => '(121 144) (let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--lkeep (when (numberp value) (expt (+ 10 value) 2)) h41 'rev)) => '(144 121) (let ((h42 (h* "a" 1 'd (h* "d1" 41 "d2" 42 "e1" 51)))) (h--lkeep (format "%s→%s" (type-of key) (type-of value)) h42)) => '("string→integer" "symbol→hash-table") (let ((h11 (h-new 5))) (h--lkeep (format "%s: %s!" key value) h11)) => '()
(key-form val-form table &optional reverse)
Anaphoric version of h-lunzip-with.
The form KEY-FORM modifies each key.
The form VAL-FORM modifies each value.
Each element of KEYS is bound to the symbol key.
Each element of VALUES is bound to the symbol value.
Each key and value is taken one by one from their corresponding
pairs at the hash table TABLE.
If REVERSE is non-nil, reverse the order of both lists.
The (roughly) inverse of this function is h-zip-lists.
Alias: h--unzip-with
.
See also: h--vunzip-with.
(h--lunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a 1 'b nil)) => '(("a" "b") ("1" nil)) (h--lunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a 1 'b 2)) => '(("a" "b") ("1" "2")) (h--lunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a 1 'b 2 'c nil)) => '(("a" "b" "c") ("1" "2" nil)) (h--lunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a nil 'b nil)) => '(("a" "b") (nil nil)) (h--lunzip-with (symbol-name key) (when value (number-to-string value)) (h* 'a nil 'b nil)) => '(("a" "b") (nil nil)) (h--lunzip-with (symbol-name key) (when value (number-to-string value)) (h-new 1)) => '(nil nil) (h--lunzip-with (symbol-name key) (when value (number-to-string value))) !!> wrong-number-of-arguments (h--lunzip-with (symbol-name key) (when value (number-to-string value)) '()) !!> error (h--lunzip-with (symbol-name key) (when value (number-to-string value)) 3) !!> error
(key-fun val-fun table &optional reverse)
Return a list of modified lists of KEYS and VALUES from TABLE.
If REVERSE is non-nil, reverse the order of both lists.
The function KEY-FUN modifies each key in the results.
The function VAL-FUN modifies each value in the results.
Both functions take two variables: key and value, respectively.
Each key and value is taken one by one from their corresponding
pairs at the hash table TABLE.
The (roughly) inverse of this function is h-zip-lists.
Its anaphoric counterpart is h--lunzip-with.
Alias: h-unzip-with
.
See also: h-vunzip-with.
(h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a 1 'b nil)) => '(("a" "b") ("1" nil)) (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a 1 'b 2)) => '(("a" "b") ("1" "2")) (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a 1 'b 2 'c nil)) => '(("a" "b" "c") ("1" "2" nil)) (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a nil 'b nil)) => '(("a" "b") (nil nil)) (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h* 'a nil 'b nil)) => '(("a" "b") (nil nil)) (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) (h-new 1)) => '(nil nil) (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) ) !!> wrong-number-of-arguments (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) '()) !!> error (h-lunzip-with (lambda (key _value) (symbol-name key)) (lambda (_key value) (when value (number-to-string value))) 3) !!> error
(table &optional reverse)
Return a list of the lists of KEYS and VALUES from TABLE.
If REVERSE is non-nil, reverse the order of both lists.
The (roughly) inverse of this function is h-zip-lists.
Alias: h-unzip
.
See also: h-vunzip.
(h-lunzip (h* :a 1 :b nil)) => '((:a :b) (1 nil)) (h-lunzip (h* :a 1 :b 2)) => '((:a :b) (1 2)) (h-lunzip (h* :a 1 :b 2 :c nil)) => '((:a :b :c) (1 2 nil)) (h-lunzip (h* :a nil :b nil)) => '((:a :b) (nil nil)) (h-lunzip (h* :a nil :b nil)) => '((:a :b) (nil nil)) (h-lunzip (h-new 1)) => '(nil nil) (h-lunzip) !!> wrong-number-of-arguments (h-lunzip '()) !!> error (h-lunzip 3) !!> error
(table &optional reverse)
Return a list of two-element lists '(key value) from TABLE.
Like ht-items
, but in TABLE's stored order by default.
If REVERSE is non-nil, show it in reverse order (the larger list;
the pair remains in the (key, value) order).
Alias: h-items
.
(h-litems (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) => '(("Bob" "cat") ("Whiskers" "cat") ("Bubbly" "fish")) (h-litems (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish") 'reverse) => '(("Bubbly" "fish") ("Whiskers" "cat") ("Bob" "cat")) (h-litems (h-new 5)) => '()
(table &optional reverse)
Return a list of all the keys in TABLE.
Like ht-keys
, but in TABLE's stored order by default.
If REVERSE is non-nil, show it in reverse order.
Aliases: h-keys
, h-2d-keys
.
(h-lkeys (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) => '("Bob" "Whiskers" "Bubbly") (h-lkeys (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish") 'rev) => '("Bubbly" "Whiskers" "Bob") (h-lkeys (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) => '("a" "b" "d") (h-lkeys (h-new 5)) => '()
(table &optional reverse)
Return a list of all the values in TABLE.
Like ht-values
, but in TABLE's stored order.
If REVERSE is non-nil, show it in reverse order.
Alias: h-values
.
(h-lvalues (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) => '("cat" "cat" "fish") (h-lvalues (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish") 'rev) => '("fish" "cat" "cat") (h-lvalues (h-new 5)) => '()
(table &optional n)
Return a list of N pseudo-randomly chosen items from hash TABLE.
N must be an integer; if not, signal error.
- If N is nil, make it N=1.
If N≥ size(TABLE), return a list of all TABLE's items in
pseudo-random order.
(like h-litems, but shuffled).
- If N=0, return nil.
- If N=1, item is returned as a two-item list (key, value).
- If N>1, items are returned as a list of two-item lists (key, value).
- If N is negative, make N = N + size(TABLE). For example, if TABLE has 10
items and N=-2, return 8 pseudo-random items.
See also: h-vrandom, h-pop-random, and h-pop-random!.
;;;; It's random, so all we can do is check length and whether subset (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (items (h-items htbl)) (res (h-lrandom htbl 2))) (and (= 2 (length res)) (equal res (-intersection res items)))) => t ;;;; One by one is repetitive. Let's check many at once? (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (items (h-items htbl)) (ns '(1 2 3 4 5 -1 -2)) (sizes '(1 2 3 4 4 3 2))) (--every (and (= (nth it-index sizes) (length it)) (equal it (-intersection it items))) (-map (-cut h-lrandom htbl <>) ns))) => t (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (res (h-lrandom htbl))) (and (= 1 (length res)) (equal res (-intersection res (h-items htbl))))) => t (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4))) (null (h-lrandom htbl 0))) => t (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4))) (null (h-lrandom htbl "something"))) !!> error (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4))) (null (h-lrandom htbl 3.5))) !!> error
to hash table
Functions that apply two functions to every key–value pair of a hash table
and write the results to a hash table.
For example, if you want to produce a fresh table whose keys
(currently strings) are upcased and the values (currently
integers) are increased by 1, you could run:
(h-hmap (lambda (key _value) (upcase key)) (lambda (_key value) (1+ value)) table)
The underlines above are so that there's no warnings about unused
variables. We do it because we didn't use VALUE in KEY-FUN, nor KEY
in VAL-FUN — but this doesn't need to be so: we could want that the
new key be also (or instead) a function of the VALUE, and
vice-versa.
Note that the pair is only added if KEY-FUN returns non-nil. So no
spurious (nil, something) pair results from some reasonable
conditional KEY-FUN such as:
(lambda (key, value) (when (> value 2) key))
In the rare case where for some reason you chose to have nil as a
key, you'll have to treat it specially.
IMPORTANT: unlike mapping to a list, mapping to a hash table
demands that the results of the keys be unique. So you must pay
attention to possible collisions. If, for example, in the case
above the original table had both "a" and "A" as original keys,
one of them would end up overwritten, because both return "A"
when upcased. Which one will depend on the key order. So:
actual size of resulting table ≤ size of original table.
In mathematical terms, excepting the rare case of a nil key in the
original, the number of keys in the resulting hash table will match
the number of keys in the original one if, and only if:
- there's a bijection between the set of keys and the set of
key-fun(key,value). - there's no (key,value) for which key-fun(key,value) returns nil.
Guideline for the 'hmap family' of functions:
- the added 'h' means 'result is a hash table'
- an added '-' means 'anaphoric: enter 2 forms, not 2 lambdas'
- an added '*' means 'recurse: apply the values-function or -form
to the values whenever these are hash tables' - an added '!' means 'destructive: modify TABLE instead of
creating a fresh one'
In the anaphoric versions, if you don't use any or either of the
let-bound variables key and value in any or either of the forms,
it's ok — no warnings.
Summary:
Side-effect-free: don't modify original TABLE, return fresh table
2 lambdas | 2 forms (anaphoric) | |
---|---|---|
simple, doesn't recurse | h-hmap | h--hmap |
nesting-aware, recurses | h-hmap* | h--hmap* |
Destructive: modify original TABLE, return nil
2 lambdas | 2 forms (anaphoric) | |
---|---|---|
simple, doesn't recurse | h-hmap! | h--hmap! |
nesting-aware, recurses | h-hmap*! | h--hmap*! |
See also: h-2d-hmap, h--2d-hmap, h-2d-hmap!, h--2d-hmap!
(key-fun val-fun table)
Return a table from applying KEY-FUN, VAL-FUN to each pair of TABLE.
While h-lmap produces a list, this one produces a hash table.
Both KEY-FUN and VAL-FUN are each called with two arguments, KEY
and VALUE.
- The result of KEY-FUN is the new key.
- The result of VAL-FUN is the new value.
Its anaphoric counterpart is h--hmap.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (kfun (lambda (key _value) (read (format ":%s" key)))) (vfun (lambda (_key value) (if (numberp value) (expt (+ 10 value) 2) value)))) (h-hmap kfun vfun h41)) H=> (h* :a 121 :b 144 :d (h* "d1" 41 "d2" 42 "e1" 51)) (let ((h21 (h* :a 1 :b 2 :c 3))) (h-hmap (lambda (key value) (when (>= value 2) key)) (lambda (_key value) value) h21)) H=> (h* :b 2 :c 3) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (kfun (lambda (key _value) (upcase key))) (vfun (lambda (key value) (concat key (capitalize value))))) (h-hmap kfun vfun h31)) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") ;;;; Beware of collisions! (let ((h31 (h* "a" "cat" "A" "nother cat")) (kfun (lambda (key _value) (upcase key))) (vfun (lambda (key value) (concat key (upcase value))))) (h-hmap kfun vfun h31)) H=> (h* "A" "ANOTHER CAT") (let ((h31 (h* "A" "nother cat" "a" "cat")) (kfun (lambda (key _value) (upcase key))) (vfun (lambda (key value) (concat key (upcase value))))) (h-hmap kfun vfun h31)) H=> (h* "A" "aCAT") ;; In the previous two examples, the transforming function upcase ;; made a previous key be overridden. (let ((h11 (h* 'a 1 'b 2)) (kfun (lambda (key _value) key)) (vfun (lambda (_key value) (format "%s!" value)))) (h-hmap kfun vfun h11)) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h* 'a 1 'b 2)) (kfun (lambda (key _value) key)) (vfun (lambda (_key value) (format "%s!" value)))) (h-hmap kfun vfun h11)) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5)) (kfun (lambda (key _value) key)) (vfun (lambda (key value) (format "%s: %s!" key value)))) (h-hmap kfun vfun h11)) H=> (h-new 5)
(key-form val-form table)
Anaphoric version of h-hmap.
The form KEY-FORM modifies each key.
The form VAL-FORM modifies each value.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
While h--lkeep produces a list, this one produces a hash table.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--hmap (read (format ":%s" key)) (if (numberp value) (expt (+ 10 value) 2) value) h41)) H=> (h* :a 121 :b 144 :d (h* "d1" 41 "d2" 42 "e1" 51)) (let ((h21 (h* :a 1 :b 2 :c 3))) (h--hmap (when (>= value 2) key) value h21)) H=> (h* :b 2 :c 3) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--hmap (upcase key) (concat key (capitalize value)) h31)) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2))) (h--hmap key (format "%s!" value) h11)) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5))) (h--hmap key (format "%s: %s!" key value) h11)) H=> (h-new 5)
(key-fun val-fun table)
Update TABLE by applying KEY-FUN, VAL-FUN to its pairs.
Just like h-hmap (which see), but modifies TABLE instead of
producing a fresh one.
Its anaphoric counterpart is h--hmap!.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (kfun (lambda (key _value) (read (format ":%s" key)))) (vfun (lambda (_key value) (if (numberp value) (expt (+ 10 value) 2) value)))) (h-hmap! kfun vfun h41) h41) H=> (h* :a 121 :b 144 :d (h* "d1" 41 "d2" 42 "e1" 51)) (let ((h21 (h* :a 1 :b 2 :c 3))) (h-hmap! (lambda (key value) (when (>= value 2) key)) (lambda (_key value) value) h21) h21) H=> (h* :b 2 :c 3) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (kfun (lambda (key _value) (upcase key))) (vfun (lambda (key value) (concat key (capitalize value))))) (h-hmap! kfun vfun h31) h31) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2)) (kfun (lambda (key _value) key)) (vfun (lambda (_key value) (format "%s!" value)))) (h-hmap! kfun vfun h11) h11) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5)) (kfun (lambda (key _value) key)) (vfun (lambda (key value) (format "%s: %s!" key value)))) (h-hmap! kfun vfun h11) h11) H=> (h-new 5)
(key-form val-form table)
Anaphoric version of h-hmap!.
For every key–value pair in TABLE, evaluate KEY-FORM and VAL-FORM
with the variables key and value bound.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--hmap! (read (format ":%s" key)) (if (numberp value) (expt (+ 10 value) 2) value) h41) h41) H=> (h* :a 121 :b 144 :d (h* "d1" 41 "d2" 42 "e1" 51)) (let ((h21 (h* :a 1 :b 2 :c 3))) (h--hmap! (when (>= value 2) key) value h21) h21) H=> (h* :b 2 :c 3) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--hmap! (upcase key) (concat key (capitalize value)) h31) h31) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2))) (h--hmap! key (format "%s!" value) h11) h11) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5))) (h--hmap! key (format "%s: %s!" key value) h11) h11) H=> (h-new 5)
(key-fun val-fun table)
Return a table from applying KEY-FUN, VAL-FUN to each pair of TABLE.
Just like h-hmap, but recurses wherever TABLE is nested.
This means that for any VALUE that is itself a hash table, instead
of replacing it with the result of VAL-FUN, as h-hmap would
do, VALUE will be this very h-hmap* recursively applied to
that hash table with KEY-FUN and VAL-FUN.
Its anaphoric counterpart is h--hmap*.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (kfun (lambda (key _value) (read (format ":%s" key)))) (vfun (lambda (_key value) (expt (+ 10 value) 2)))) (h-hmap* kfun vfun h41)) H=> (h* :a 121 :b 144 :d (h* :d1 2601 :d2 2704 :e1 3721)) (let ((h21 (h* :a 1 :b 2 :c (h* :d 1 :e 5 :f 0 :g 7)))) (h-hmap* (lambda (key value) (when (or (ht? value) (>= value 2)) key)) (lambda (_key value) value) h21)) H=> (h* :b 2 :c (h* :e 5 :g 7)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (kfun (lambda (key _value) (upcase key))) (vfun (lambda (key value) (concat key (capitalize value))))) (h-hmap* kfun vfun h31)) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2)) (kfun (lambda (key _value) key)) (vfun (lambda (_key value) (format "%s!" value)))) (h-hmap* kfun vfun h11)) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5)) (kfun (lambda (key _value) key)) (vfun (lambda (key value) (format "%s: %s!" key value)))) (h-hmap* kfun vfun h11)) H=> (h-new 5)
(key-form val-form table)
Anaphoric version of h-hmap*.
For every key–value pair in TABLE, evaluate KEY-FORM and VAL-FORM
with the variables key and value bound.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--hmap* (read (format ":%s" key)) (expt (+ 10 value) 2) h41)) H=> (h* :a 121 :b 144 :d (h* :d1 2601 :d2 2704 :e1 3721)) (let ((h21 (h* :a 1 :b 2 :c (h* :d 1 :e 5 :f 0 :g 7)))) (h--hmap* (when (or (ht? value) (>= value 2)) key) value h21)) H=> (h* :b 2 :c (h* :e 5 :g 7)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--hmap* (upcase key) (concat key (capitalize value)) h31)) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2))) (h--hmap* key (format "%s!" value) h11)) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5))) (h--hmap* key (format "%s: %s!" key value) h11)) H=> (h-new 5)
(key-fun val-fun table)
Update TABLE by applying KEY-FUN, VAL-FUN to each of its pairs.
Just like h-hmap* (which see), but modifies TABLE instead of
producing a fresh one.
Its anaphoric counterpart is h--hmap*!.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51))) (kfun (lambda (key _value) (read (format ":%s" key)))) (vfun (lambda (_key value) (expt (+ 10 value) 2)))) (h-hmap*! kfun vfun h41) h41) H=> (h* :a 121 :b 144 :d (h* :d1 2601 :d2 2704 :e1 3721)) (let ((h21 (h* :a 1 :b 2 :c (h* :d 1 :e 5 :f 0 :g 7)))) (h-hmap*! (lambda (key value) (when (or (ht? value) (>= value 2)) key)) (lambda (_key value) value) h21) h21) H=> (h* :b 2 :c (h* :e 5 :g 7)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (kfun (lambda (key _value) (upcase key))) (vfun (lambda (key value) (concat key (capitalize value))))) (h-hmap*! kfun vfun h31) h31) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2)) (kfun (lambda (key _value) key)) (vfun (lambda (_key value) (format "%s!" value)))) (h-hmap*! kfun vfun h11) h11) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5)) (kfun (lambda (key _value) key)) (vfun (lambda (key value) (format "%s: %s!" key value)))) (h-hmap*! kfun vfun h11) h11) H=> (h-new 5)
(key-form val-form table)
Anaphoric version of h-hmap*!.
For every key–value pair in TABLE, evaluate KEY-FORM and VAL-FORM
with the variables key and value bound.
(let ((h41 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51)))) (h--hmap*! (read (format ":%s" key)) (expt (+ 10 value) 2) h41) h41) H=> (h* :a 121 :b 144 :d (h* :d1 2601 :d2 2704 :e1 3721)) (let ((h21 (h* :a 1 :b 2 :c (h* :d 1 :e 5 :f 0 :g 7)))) (h--hmap*! (when (or (ht? value) (>= value 2)) key) value h21) h21) H=> (h* :b 2 :c (h* :e 5 :g 7)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--hmap*! (upcase key) (concat key (capitalize value)) h31) h31) H=> (h* "BOB" "BobCat" "WHISKERS" "WhiskersCat" "BUBBLY" "BubblyFish") (let ((h11 (h* 'a 1 'b 2))) (h--hmap*! key (format "%s!" value) h11) h11) H=> (h* 'a "1!" 'b "2!") (let ((h11 (h-new 5))) (h--hmap*! key (format "%s: %s!" key value) h11) h11) H=> (h-new 5)
for side-effects only
Functions that map over the hash table for producing side-effects
somewhere.
(table fun)
Apply function FUN to each key–value pair of TABLE.
FUN is called with two arguments: key and value.
Intended to be used for side-effects only. Returns nil.
This function is similar to current ht-each
(in turn an alias to
maphash
), but with TABLE as the first argument.
Its anaphoric counterpart is h--each.
(let ((table (h* "a" 1 "b" 2 "c" 3)) res) (h-each table (lambda (k v) (push (format "%s--%d" (upcase k) v) res))) (nreverse res)) => '("A--1" "B--2" "C--3") (let ((table (h* "a" 1 "b" 2 "c" 3))) (with-output-to-string (h-each table (lambda (key value) (princ (upcase key)) (princ " = ") (princ value) (princ "\n"))))) => "A = 1\nB = 2\nC = 3\n" (let ((table (h* 'a 1 'b 2)) res) (h-each table (lambda (key _value) (push (upcase (format "%s%s" key key)) res))) (nreverse res)) => '("AA" "BB") (let ((table (h*)) res) (h-each table (lambda (key _value) (push (format "%s" (upcase key)) res))) (nreverse res)) => '()
(table &rest body)
Anaphoric version of h-each.
For every key–value pair in TABLE, evaluate BODY with the variables
key and value bound.
This function is similar to current ht-aeach
. Differences:
- TABLE is the first argument.
- BODY instead of FORM, obviating the need of
progn
whenever
more than one form is needed. - issues no warnings if either KEY or VALUE isn't used.
(let ((table (h* "a" 1 "b" 2 "c" 3)) res) (h--each table (push (format "%s--%d" (upcase key) value) res)) (nreverse res)) => '("A--1" "B--2" "C--3") (let ((table (h* "a" 1 "b" 2 "c" 3))) (with-output-to-string (h--each table (princ (upcase key)) (princ " = ") (princ value) (princ "\n")))) => "A = 1\nB = 2\nC = 3\n" ;;;;; it can be used to call destructive functions on the table... (let ((table (h* 0 "alice" 1 "bob"))) (h--each table (h-put! table key (capitalize value))) table) H=> (h* 0 "Alice" 1 "Bob") ;;;;; ...but for that, better use h--hmap! instead (let ((table (h* 0 "alice" 1 "bob"))) (h--hmap! key (capitalize value) table) table) H=> (h* 0 "Alice" 1 "Bob") ;;;;; empty (let ((table (h*)) res) (h--each table (push (format "%s--%d" (upcase key) value) res)) (nreverse res)) => '()
Keys operations
Retrieval (getting)
Functions that retrieve key–value pairs from tables.
Currently, there are no functions here.
h-get
is aliased toht-get
.h-get*
is aliased toht-get*
.
(table)
Return the very first pair from TABLE.
Pair is returned as a two-element list: '(key value).
Note that this function is not destructive: it won't remove the
pair from TABLE. For that, use h-pop!.
;;;; Return the first pair (h-pop (h* "a" 1 "b" 2 "c" 3)) => '("a" 1) ;;;; Return the key of first-found pair (car (h-pop (h* "a" 1 "b" 2 "c" 3))) => "a" ;;;; Nested (cadr (h-pop (h* "a" (h* "b" 2 "c" 3) "d" 4 "e" 5))) H=> (h* "b" 2 "c" 3) ;;;; Null (h-pop (h*)) => '()
(table)
Return the very first pair from TABLE and remove it.
Pair is returned as a two-element list: '(key value).
If you don't want it removed, use h-pop instead.
;;;; Return the first pair (let ((tbl (h* "a" 1 "b" 2 "c" 3))) (h-pop! tbl)) => '("a" 1) (let ((tbl (h* "a" 1 "b" 2 "c" 3))) (h-pop! tbl) tbl) H=> (h* "b" 2 "c" 3) ;;;; Return the key of first-found pair (car (h-pop! (h* "a" 1 "b" 2 "c" 3))) => "a" ;;;; Nested (let ((tbl (h* "a" (h* "b" 2 "c" 3) "d" 4 "e" 5))) (cadr (h-pop! tbl))) H=> (h* "b" 2 "c" 3) (let ((tbl (h* "a" (h* "b" 2 "c" 3) "d" 4 "e" 5))) (h-pop! tbl) tbl) H=> (h* "d" 4 "e" 5) ;;;; Null (h-pop! (h*)) => '()
(table)
Return a pseudo-random pair from TABLE.
Pair is returned as a two-element list: '(key value).
Note that this function is not destructive: it won't remove the
pair from TABLE. For that, use h-pop-random!.
See also: h-vrandom and h-lrandom.
;;;; It's random, so as a weak test we'll check whether a subset, ;;;; as well as the hash table's size after the operation (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (size (h-size htbl)) (items (h-items htbl)) (res (h-pop-random htbl))) (and (member res items) (= (h-size htbl) size))) => t
(table)
Return a pseudo-random pair from TABLE and remove it.
Pair is returned as a two-element list: '(key value).
If you don't want it removed, use h-pop-random.
See also: h-vrandom and h-lrandom.
;;;; It's random, so as a weak test we'll check whether a subset, ;;;; as well as the hash table's size after the operation (let* ((htbl (h* :a 1 :b 2 :c 3 :d 4)) (size (h-size htbl)) (items (h-items htbl)) (res (h-pop-random! htbl))) (and (member res items) (= (h-size htbl) (1- size)))) => t
(fun table)
Return the first pair from TABLE for which FUN returns non-nil.
In this case, pair is returned as a two-element list: '(key value).
Return nil otherwise.
Function FUN is called with two arguments, key and value.
This function is exactly like current ht-find
.
;;;; Return the key that has the first-found value >1 (car (h-first (lambda (_k value) (> value 1)) (h* "a" 1 "b" 2 "c" 3))) => "b" ;;;; Return the very first pair stored in the table (like h-pop) (h-first (lambda (_k _v) t) (h* "a" 1 "b" 2 "c" 3)) => '("a" 1)
(form table)
Anaphoric version of h-first.
Return the first pair from TABLE for which FORM returns non-nil.
In this case, pair is returned as a two-element list: '(key value).
Return nil otherwise.
FORM is called with two arguments: key and value.
;;;; Return the key that has the first-found value >1 (car (h--first (> value 1) (h* "a" 1 "b" 2 "c" 3))) => "b" ;;;; Return the very first pair stored in the table (like h-pop) (h--first t (h* "a" 1 "b" 2 "c" 3)) => '("a" 1)
(fun table)
Remove from TABLE the first pair for which FUN returns non-nil.
In this case, pair is returned as a two-element list: '(key value).
Return nil otherwise.
Function FUN is called with two arguments, key and value.
;;;; Return the key that has the first-found value >1 (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (car (h-first! (lambda (_k value) (> value 1)) htbl))) => "b" ;;;;; ...and here is the hash table after the above (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (car (h-first! (lambda (_k value) (> value 1)) htbl)) htbl) H=> (h* "a" 1 "c" 3) ;;;; Return the very first pair stored in the table (like h-pop!) (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (h-first! (lambda (_k _v) t) htbl)) => '("a" 1) ;;;;; ...and here is the hash table after the above (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (h-first! (lambda (_k _v) t) htbl) htbl) H=> (h* "b" 2 "c" 3)
(form table)
Anaphoric version of h-first!.
Remove from TABLE the first pair for which FORM returns non-nil.
In this case, pair is returned as a two-element list: '(key value).
Return nil otherwise.
FORM is called with two arguments, key and value.
;;;; Return the key that has the first-found value >1 (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (car (h--first! (> value 1) htbl))) => "b" ;;;;; ...and here is the hash table after the above (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (car (h--first! (> value 1) htbl)) htbl) H=> (h* "a" 1 "c" 3) ;;;; Return the very first pair stored in the table (like h-pop!) (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (h--first! t htbl)) => '("a" 1) ;;;;; ...and here is the hash table after the above (let ((htbl (h* "a" 1 "b" 2 "c" 3))) (h--first! t htbl) htbl) H=> (h* "b" 2 "c" 3)
Addition (setting)
Functions that add a key–value pair to tables, either destructively or
side-effects-free. Included are those that take a sequence of keys
and a final value — for nested hash tables.
Same logic applies to put functions as with mix functions — only
that it's table + key–value instead of table + tables; and only one
kv pair makes sense (otherwise it'd be either a hash table with
the pairs, to which we'd apply mixing; or some alist or plist,
which we'd convert to htbl then mix).
It may seem a bit odd to "put a pair non-destructively", but the
logic is the same as that of h-mix, and having an exactly analogous
abstraction is useful.
Summary:
Non-destructive | Destructive | |
---|---|---|
simple, doesn't recurse | h-put | h-put! |
nesting-aware, recurses | h-put* | h-put*! |
While the destructive h-put! is like ht-set!
, there's no non-destructive
counterparts in ht library.
(table key value)
Associate KEY in TABLE with VALUE.
This is EXACTLY equivalent to current ht-set!
.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-put.
This function is not nesting-aware.
Its also-destructive but nesting-aware counterpart is h-put*!.
For nesting-awareness with no side-effects, use h-put*.
;;;;; replacing nested won't work (let ((tbl (ht ("a" (ht ("b" (ht ("c" (ht ("d" 'something)))))))))) (h-put! tbl "a" "b" "c" "d" 4) tbl) !!> wrong-number-of-arguments ;;;;; replace a value-that-is-ht with non-ht value (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put! tbl :a 5) tbl) H=> (h* :z 'a :a 5) ;;;;; replace a non-ht value with a non-ht value (let ((tbl (ht ("a" 1)))) (h-put! tbl "a" 2) tbl) H=> (ht ("a" 2)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put! tbl "b" 2) tbl) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put! tbl "a" 2) tbl) H=> (ht ("a" 2))
(table key value)
Return a table that is TABLE with KEY–VALUE applied.
This is a side-effect-free function.
Its destructive counterpart is h-put!.
This function is not nesting-aware.
Its also-side-effect-free but nesting-aware counterpart is h-put*.
For nesting-awareness with side-effects, use h-put*!.
;;;;; replacing nested won't work (let ((tbl (ht ("a" (ht ("b" (ht ("c" (ht ("d" 'something)))))))))) (h-put tbl "a" "b" "c" "d" 4)) !!> wrong-number-of-arguments ;;;;; replace a value-that-is-ht with non-ht value (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put tbl :a 5)) H=> (h* :z 'a :a 5) ;;;;; replace a non-ht value with a non-ht value (let ((tbl (ht ("a" 1)))) (h-put tbl "a" 2)) H=> (ht ("a" 2)) ;;;;; note: original tbl remains unmodified (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put tbl :a 5) tbl) H=> (h* :z 'a :a (h* :x 4 :b 2)) (let ((tbl (ht ("a" 1)))) (h-put tbl "a" 2) tbl) H=> (ht ("a" 1)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put tbl "b" 2)) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put tbl "a" 2)) H=> (ht ("a" 2))
(table &rest keys-value)
Set VALUE to KEYS sequence in hash table TABLE.
KEYS-VALUE are KEYS and VALUE.
Each key in KEYS has as value the next, all of which hash tables,
except for the final key, which could return any value.
This last key will be set to VALUE.
If any of the keys in the sequence KEYS can't be found,
they will be created on-the-fly.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-put*.
This function is nesting-aware.
Its also-destructive but only-first-level counterpart is h-put!.
For no nesting-awareness and no side-effects, use h-put.
;;;;; a quick comparison with ht ;; In 'ht's README, it says that these are equivalent — and they are: (let ((table (ht (1 (ht (2 (ht (3 "three")))))))) (ht-set! (ht-get (ht-get table 1) 2) 3 :three) table) H=> (let ((table (ht (1 (ht (2 (ht (3 "three")))))))) (setf (ht-get* table 1 2 3) :three) table) ;; With h-put*! it gets even shorter: (let ((table (ht (1 (ht (2 (ht (3 "three")))))))) (h-put*! table 1 2 3 :three) table) H=> (h* 1 (h* 2 (h* 3 :three))) ;;;;; replacing last key (let ((tbl (ht ("a" (ht ("b" (ht ("c" (ht ("d" 'something)))))))))) (h-put*! tbl "a" "b" "c" "d" 4) tbl) H=> (ht ("a" (ht ("b" (ht ("c" (ht ("d" 4)))))))) ;;;;; creating keys on-the-fly (replace non-ht key) (let ((tbl (ht ("a" "b")))) (h-put*! tbl "a" "b" "c" "d" 4) tbl) H=> (ht ("a" (ht ("b" (ht ("c" (ht ("d" 4)))))))) ;;;;; Replace a value-that-is-ht with a non-ht value (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put*! tbl :a :b :c :d 1) tbl) H=> (h* :z 'a :a (h* :x 4 :b (h* :c (h* :d 1)))) (let* ((tbl (h* :z 'a :a (h* :x 4 :b (h* :e 2))))) (h-put*! tbl :a :b :c :d 1) tbl) H=> (h* :z 'a :a (h* :x 4 :b (h* :e 2 :c (h* :d 1)))) (let* ((tbl (h* :z 5 :a 4))) (h-put*! tbl :w :b :c :d 1) tbl) H=> (h* :z 5 :a 4 :w (h* :b (h* :c (h* :d 1)))) ;;;; Simple: behave like ht-set! ;;;;; replace a value-that-is-ht with non-ht value (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put*! tbl :a 5) tbl) H=> (h* :z 'a :a 5) ;;;;; replace a non-ht value with a non-ht value (let ((tbl (ht ("a" 1)))) (h-put*! tbl "a" 2) tbl) H=> (ht ("a" 2)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put*! tbl "b" 2) tbl) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put*! tbl "a" 2) tbl) H=> (ht ("a" 2))
(table &rest keys-value)
Return a table that is TABLE with KEYS–VALUE applied.
KEYS-VALUE are KEYS and VALUE.
Each key in KEYS has as value the next, all of which hash tables,
except for the final key, which could return any value.
This last key will be set to VALUE.
If any of the keys in the sequence KEYS can't be found,
they will be created on-the-fly.
This is a side-effect-free function.
Its destructive counterpart is h-put*!.
This function is nesting-aware.
Its also-side-effect-free but only-first-level counterpart is h-put.
For no nesting-awareness but with side-effects, use h-put!.
;;;;; a quick comparison with ht ;; In 'ht's README, it says that these are equivalent — and they are: (let ((table (ht (1 (ht (2 (ht (3 "three")))))))) (ht-set! (ht-get (ht-get table 1) 2) 3 :three) table) H=> (let ((table (ht (1 (ht (2 (ht (3 "three")))))))) (setf (ht-get* table 1 2 3) :three) table) ;; With h-put* it gets even shorter: (let ((table (ht (1 (ht (2 (ht (3 "three")))))))) (h-put* table 1 2 3 :three)) H=> (h* 1 (h* 2 (h* 3 :three))) ;;;;; replacing last key (let ((tbl (ht ("a" (ht ("b" (ht ("c" (ht ("d" 'something)))))))))) (h-put* tbl "a" "b" "c" "d" 4)) H=> (ht ("a" (ht ("b" (ht ("c" (ht ("d" 4)))))))) ;;;;; creating keys on-the-fly (replace non-ht key) (let ((tbl (ht ("a" "b")))) (h-put* tbl "a" "b" "c" "d" 4)) H=> (ht ("a" (ht ("b" (ht ("c" (ht ("d" 4)))))))) ;;;;; replace a value-that-is-ht with a non-ht value (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put* tbl :a :b :c :d 1)) H=> (h* :z 'a :a (h* :x 4 :b (h* :c (h* :d 1)))) (let* ((tbl (h* :z 'a :a (h* :x 4 :b (h* :e 2))))) (h-put* tbl :a :b :c :d 1)) H=> (h* :z 'a :a (h* :x 4 :b (h* :e 2 :c (h* :d 1)))) (let* ((tbl (h* :z 5 :a 4))) (h-put* tbl :w :b :c :d 1)) H=> (h* :z 5 :a 4 :w (h* :b (h* :c (h* :d 1)))) ;;;;; replace a value-that-is-ht with non-ht value (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put* tbl :a 5)) H=> (h* :z 'a :a 5) ;;;;; replace a non-ht value with a non-ht value (let ((tbl (ht ("a" 1)))) (h-put* tbl "a" 2)) H=> (ht ("a" 2)) ;;;;; note: original tbl remains unmodified (let* ((tbl (h* :z 5 :a 4))) (h-put* tbl :w :b :c :d 1) tbl) H=> (h* :z 5 :a 4) (let* ((tbl (h* :z 'a :a (h* :x 4 :b 2)))) (h-put* tbl :a 5) tbl) H=> (h* :z 'a :a (h* :x 4 :b 2)) (let ((tbl (ht ("a" 1)))) (h-put* tbl "a" 2) tbl) H=> (ht ("a" 1)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put* tbl "b" 2)) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put* tbl "a" 2)) H=> (ht ("a" 2)) ;;;; A few proofs that the original table isn't modified ;; (it uses h-clone* to generate the initial copy) (let ((tbl (h* :a 1 :b (h* :c 3)))) (-> tbl (h-put* :a 2)) tbl) H=> (h* :a 1 :b (h* :c 3)) (let ((tbl (h* :a 1 :b (h* :c 3)))) (-> tbl (h-put* :b :c 4)) tbl) H=> (h* :a 1 :b (h* :c 3)) (let ((tbl (h* :a '(1 2) :b (h* :c 3)))) (-> tbl (h-put* :a '(6 7))) tbl) H=> (h* :a '(1 2) :b (h* :c 3)) (let ((tbl (h* :a '(1 2) :b (h* :c 3)))) (-> tbl (h-put* :a (cons 7 (h-get tbl :a)))) tbl) H=> (h* :a '(1 2) :b (h* :c 3)) (let ((tbl (h* :a 1 :b (h* :c 3 :d 4)))) (-> tbl (h-put* :a 2) (h-put* :b :c 5)) tbl) H=> (h* :a 1 :b (h* :c 3 :d 4)) (let ((tbl (h* :a 1 :b (h* :c 3 :d (h* :e 5))))) (-> tbl (h-put* :a 2) (h-put* :b :d :e 7)) tbl) H=> (h* :a 1 :b (h* :c 3 :d (h* :e 5)))
While 'put' replaces any current value (call it 'curval')
with the newly provided value, 'put-add' adds to it.
The idea is that you are adding to a set. So:
Current value | New value |
---|---|
#s(hash table) | - if VALUE is also a hash table, |
then (h-mix*! VALUE CURVAL); | |
- if VALUE is an explicit key–value structure, try | |
to convert it to hash table, then h-mix*!; | |
- otherwise, don't add: keep CURVAL intact. | |
By 'explicit' I mean: no implicit ones, such as | |
simple list, vector, or string lines. | |
And for simplicity, 'key–value lines' string | |
is also out. | |
[some vector] | [VALUE some vector] |
'(some list) | '(VALUE some list) |
nil | VALUE |
With anything else, make a list, adding VALUE to its first place.
Example:
Current value | New value |
---|---|
42 | '(VALUE 42) |
:keyword | '(VALUE :keyword) |
'symbol | '(VALUE symbol) |
"string" | '(VALUE "string") |
etc. |
Summary:
Non-destructive | Destructive | |
---|---|---|
simple, doesn't recurse | h-put-add | h-put-add! |
nesting-aware, recurses | h-put-add* | h-put-add*! |
(table key value)
Add VALUE to the current value of KEY in hash table TABLE.
While h-put! replaces any current value (call it "curval")
with the newly provided value, h-put-add! adds to it.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-put-add.
This function is not nesting-aware.
Its also-destructive but nesting-aware counterpart is h-put-add*!.
For nesting-awareness with no side-effects, use h-put-add*.
;;;; Nope: this is for h-put-add*! (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add! tbl :a :c :d 5) tbl) !!> wrong-number-of-arguments (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add! tbl :a :c (h* :d 5)) tbl) !!> wrong-number-of-arguments (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add! tbl :a :c (h* :e 5)) tbl) !!> wrong-number-of-arguments (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add! tbl :a :c '(:e 5)) tbl) !!> wrong-number-of-arguments ;;;; Behavior should be different from h-put! (let ((tbl (ht ("a" 1)))) (h-put-add! tbl "a" 2) tbl) H=> (ht ("a" '(2 1))) (let ((tbl (ht ("a" 1) ("k" 10)))) (h-put-add! tbl "a" 2) tbl) H=> (ht ("a" '(2 1)) ("k" 10)) (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add! tbl "a" 2) tbl) H=> (ht ("a" (ht ("c" 3))) ("k" 10)) ;;;;; adding hash table (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add! tbl :a (ht (:d 4))) tbl) H=> (h* :a (h* :c 3 :d 4) :k 10) (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add! tbl :a (ht (:c 30))) tbl) H=> (h* :a (h* :c 30) :k 10) ;;;;; adding plist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add! tbl :a '(:d 4 :e 5)) tbl) H=> (h* :a (h* :c 3 :d 4 :e 5) :k 10) ;;;;; adding alist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add! tbl :a '((:c . 30) (:e . 5))) tbl) H=> (h* :a (h* :c 30 :e 5) :k 10) ;;;;; adding json (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add! tbl "a" "{\n \"c\": 30,\n \"e\": 5\n}") tbl) H=> (h* "a" (h* "c" 30 "e" 5) "k" 10) ;;;;; adding lol (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" '(("id" "name" "age") ("b" "bob" "30") ("e" "emily" "21"))) tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding orgtbl (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding tsv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add! tbl "m" "id\tname\nb\tbob\ne\temily") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "id\tname\tage\nb\tbob\t30\ne\temily\t21") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "id\tname\tage\nb\tbob\t30\na\talice\t35") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding csv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add! tbl "m" "id,name\nb,bob\ne,emily") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "id,name,age\nb,bob,30\ne,emily,21") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "id,name,age\nb,bob,30\na,alice,35") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding ssv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add! tbl "m" "id name\nb bob\ne emily") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "id name age\nb bob 30\ne emily 21") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add! tbl "m" "id name age\nb bob 30\na alice 35") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;; Behavior should be like ht-put! ;;;;; replace key (let ((tbl (ht ("a" nil)))) (h-put-add! tbl "a" 2) tbl) H=> (ht ("a" 2)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put-add! tbl "b" 2) tbl) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put-add! tbl "a" 2) tbl) H=> (ht ("a" 2))
(table key value)
Return a table that is TABLE with VALUE added to KEY's current.
While h-put replaces any current value with the newly provided
value, h-put-add adds to it.
This is a side-effect-free function.
Its destructive counterpart is h-put-add!.
This function is not nesting-aware.
Its also-side-effect-free but nesting-aware counterpart is h-put-add*.
For nesting-awareness with side-effects, use h-put-add*!.
;;;; Nope: this is for h-put-add* (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add tbl :a :c :d 5)) !!> wrong-number-of-arguments (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add tbl :a :c (h* :d 5))) !!> wrong-number-of-arguments (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add tbl :a :c (h* :e 5))) !!> wrong-number-of-arguments (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add tbl :a :c '(:e 5))) !!> wrong-number-of-arguments ;;;; Behavior should be different from h-put (let ((tbl (ht ("a" 1)))) (h-put-add tbl "a" 2)) H=> (ht ("a" '(2 1))) (let ((tbl (ht ("a" 1) ("k" 10)))) (h-put-add tbl "a" 2)) H=> (ht ("a" '(2 1)) ("k" 10)) (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add tbl "a" 2)) H=> (ht ("a" (ht ("c" 3))) ("k" 10)) ;;;;; adding hash table (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add tbl :a (ht (:d 4)))) H=> (h* :a (h* :c 3 :d 4) :k 10) (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add tbl :a (ht (:c 30)))) H=> (h* :a (h* :c 30) :k 10) ;;;;; adding plist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add tbl :a '(:d 4 :e 5))) H=> (h* :a (h* :c 3 :d 4 :e 5) :k 10) ;;;;; adding alist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add tbl :a '((:c . 30) (:e . 5)))) H=> (h* :a (h* :c 30 :e 5) :k 10) ;;;;; adding json (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add tbl "a" "{\n \"c\": 30,\n \"e\": 5\n}")) H=> (h* "a" (h* "c" 30 "e" 5) "k" 10) ;;;;; adding lol (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" '(("id" "name" "age") ("b" "bob" "30") ("e" "emily" "21")))) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding orgtbl (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding tsv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add tbl "m" "id\tname\nb\tbob\ne\temily")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "id\tname\tage\nb\tbob\t30\ne\temily\t21")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "id\tname\tage\nb\tbob\t30\na\talice\t35")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding csv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add tbl "m" "id,name\nb,bob\ne,emily")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "id,name,age\nb,bob,30\ne,emily,21")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "id,name,age\nb,bob,30\na,alice,35")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding ssv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add tbl "m" "id name\nb bob\ne emily")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "id name age\nb bob 30\ne emily 21")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add tbl "m" "id name age\nb bob 30\na alice 35")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;; Behavior should be like ht-put ;;;;; replace key (let ((tbl (ht ("a" nil)))) (h-put-add tbl "a" 2)) H=> (ht ("a" 2)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put-add tbl "b" 2)) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put-add tbl "a" 2)) H=> (ht ("a" 2))
(table &rest keys-value)
Add VALUE to the current KEYS sequence's value in hash table TABLE.
KEYS-VALUE are KEYS and VALUE.
While h-put*! replaces any current value with the newly provided
value, h-put-add*! adds to it.
If any of the keys in the sequence KEYS can't be found,
they will be created on-the-fly.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-put-add*.
This function is nesting-aware.
Its also-destructive but only-first-level counterpart is h-put-add!.
For no nesting-awareness and no side-effects, use h-put-add.
;;;; Can't be done with h-put-add! (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add*! tbl :a :c :d 5) tbl) ;; adding the 5 to the 4: a list H=> (h* :a (h* :c (h* :d '(5 4))) :k 10) (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add*! tbl :a :c (h* :d 5)) tbl) ;; adding (h* :d 5) to (h* :d 4): a hash table H=> (h* :a (h* :c (h* :d 5)) :k 10) (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add*! tbl :a :c (h* :e 5)) tbl) ;; adding (h* :e 5) to (h* :d 4): a hash table H=> (h* :a (h* :c (h* :d 4 :e 5)) :k 10) (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add*! tbl :a :c '(:e 5)) tbl) ;; adding plist '(:e 5) to (h* :d 4): a hash table H=> (h* :a (h* :c (h* :d 4 :e 5)) :k 10) ;;;; Behavior should be different from h-put*! (let ((tbl (ht ("a" 1)))) (h-put-add*! tbl "a" 2) tbl) H=> (ht ("a" '(2 1))) (let ((tbl (ht ("a" 1) ("k" 10)))) (h-put-add*! tbl "a" 2) tbl) H=> (ht ("a" '(2 1)) ("k" 10)) (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add*! tbl "a" 2) tbl) H=> (ht ("a" (ht ("c" 3))) ("k" 10)) ;;;;; adding hash table (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add*! tbl :a (ht (:d 4))) tbl) H=> (h* :a (h* :c 3 :d 4) :k 10) (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add*! tbl :a (ht (:c 30))) tbl) H=> (h* :a (h* :c 30) :k 10) ;;;;; adding plist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add*! tbl :a '(:d 4 :e 5)) tbl) H=> (h* :a (h* :c 3 :d 4 :e 5) :k 10) ;;;;; adding alist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add*! tbl :a '((:c . 30) (:e . 5))) tbl) H=> (h* :a (h* :c 30 :e 5) :k 10) ;;;;; adding json (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add*! tbl "a" "{\n \"c\": 30,\n \"e\": 5\n}") tbl) H=> (h* "a" (h* "c" 30 "e" 5) "k" 10) ;;;;; adding lol (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" '(("id" "name" "age") ("b" "bob" "30") ("e" "emily" "21"))) tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding orgtbl (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding tsv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add*! tbl "m" "id\tname\nb\tbob\ne\temily") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "id\tname\tage\nb\tbob\t30\ne\temily\t21") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "id\tname\tage\nb\tbob\t30\na\talice\t35") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding csv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add*! tbl "m" "id,name\nb,bob\ne,emily") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "id,name,age\nb,bob,30\ne,emily,21") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "id,name,age\nb,bob,30\na,alice,35") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding ssv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add*! tbl "m" "id name\nb bob\ne emily") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "id name age\nb bob 30\ne emily 21") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add*! tbl "m" "id name age\nb bob 30\na alice 35") tbl) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;; Behavior should be like h-put! ;;;;; replace key (let ((tbl (ht ("a" nil)))) (h-put-add*! tbl "a" 2) tbl) H=> (ht ("a" 2)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put-add*! tbl "b" 2) tbl) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put-add*! tbl "a" 2) tbl) H=> (ht ("a" 2))
(table &rest keys-value)
Return a table that is TABLE with VALUE added to KEYS' current.
KEYS-VALUE are KEYS and VALUE.
While h-put* replaces any current value with the newly provided
value, h-put-add* adds to it.
For more information, please see h-put-add!.
If any of the keys in the sequence KEYS can't be found,
they will be created on-the-fly.
This is a side-effect-free function.
Its destructive counterpart is h-put-add*!.
This function is nesting-aware.
Its also-side-effect-free but only-first-level counterpart is h-put-add.
For no nesting-awareness but with side-effects, use h-put-add!.
;;;; Can't be done with h-put-add ;; adding the 5 to the 4: a list (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add* tbl :a :c :d 5)) H=> (h* :a (h* :c (h* :d '(5 4))) :k 10) ;; adding (h* :d 5) to (h* :d 4): a hash table (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add* tbl :a :c (h* :d 5))) H=> (h* :a (h* :c (h* :d 5)) :k 10) ;; adding (h* :e 5) to (h* :d 4): a hash table (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add* tbl :a :c (h* :e 5))) H=> (h* :a (h* :c (h* :d 4 :e 5)) :k 10) ;; adding plist '(:e 5) to (h* :d 4): a hash table (let ((tbl (h* :a (h* :c (h* :d 4)) :k 10))) (h-put-add* tbl :a :c '(:e 5))) H=> (h* :a (h* :c (h* :d 4 :e 5)) :k 10) ;;;; Behavior should be different from h-put* (let ((tbl (ht ("a" 1)))) (h-put-add* tbl "a" 2)) H=> (ht ("a" '(2 1))) (let ((tbl (ht ("a" 1) ("k" 10)))) (h-put-add* tbl "a" 2)) H=> (ht ("a" '(2 1)) ("k" 10)) (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add* tbl "a" 2)) H=> (ht ("a" (ht ("c" 3))) ("k" 10)) ;;;;; adding hash table (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add* tbl :a (ht (:d 4)))) H=> (h* :a (h* :c 3 :d 4) :k 10) (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add* tbl :a (ht (:c 30)))) H=> (h* :a (h* :c 30) :k 10) ;;;;; adding plist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add* tbl :a '(:d 4 :e 5))) H=> (h* :a (h* :c 3 :d 4 :e 5) :k 10) ;;;;; adding alist (let ((tbl (ht (:a (ht (:c 3))) (:k 10)))) (h-put-add* tbl :a '((:c . 30) (:e . 5)))) H=> (h* :a (h* :c 30 :e 5) :k 10) ;;;;; adding json (let ((tbl (ht ("a" (ht ("c" 3))) ("k" 10)))) (h-put-add* tbl "a" "{\n \"c\": 30,\n \"e\": 5\n}")) H=> (h* "a" (h* "c" 30 "e" 5) "k" 10) ;;;;; adding lol (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" '(("id" "name" "age") ("b" "bob" "30") ("e" "emily" "21")))) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding orgtbl (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) ;;;;; adding tsv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add* tbl "m" "id\tname\nb\tbob\ne\temily")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "id\tname\tage\nb\tbob\t30\ne\temily\t21")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "id\tname\tage\nb\tbob\t30\na\talice\t35")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding csv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add* tbl "m" "id,name\nb,bob\ne,emily")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "id,name,age\nb,bob,30\ne,emily,21")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "id,name,age\nb,bob,30\na,alice,35")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;;; adding ssv (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice"))))) (h-put-add* tbl "m" "id name\nb bob\ne emily")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice") "b" (h* "id" "b" "name" "bob") "e" (h* "id" "e" "name" "emily"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "id name age\nb bob 30\ne emily 21")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42") "b" (h* "id" "b" "name" "bob" "age" "30") "e" (h* "id" "e" "name" "emily" "age" "21"))) (let ((tbl (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "42"))))) (h-put-add* tbl "m" "id name age\nb bob 30\na alice 35")) H=> (h* "k" 10 "m" (h* "a" (h* "id" "a" "name" "alice" "age" "35") "b" (h* "id" "b" "name" "bob" "age" "30"))) ;;;; Behavior should be like h-put ;;;;; replace key (let ((tbl (ht ("a" nil)))) (h-put-add* tbl "a" 2)) H=> (ht ("a" 2)) ;;;;; new key (let ((tbl (ht ("a" 1)))) (h-put-add* tbl "b" 2)) H=> (ht ("a" 1) ("b" 2)) ;;;;; new key from empty (let ((tbl (ht))) (h-put-add* tbl "a" 2)) H=> (ht ("a" 2))
Removal
Functions that remove a key–value pair from tables, either destructively
or side-effects-free. Included are those that take a sequence of keys — for
nested hash tables.
Summary:
Non-destructive | Destructive | |
---|---|---|
simple, doesn't recurse | h-rem | h-rem! |
nesting-aware, recurses | h-rem* | h-rem*! |
(table key)
Remove KEY from hash table TABLE.
This is EXACTLY equivalent to current ht-remove!
.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-rem.
This function is not nesting-aware.
Its also-destructive but nesting-aware counterpart is h-rem*!.
For nesting-awareness with no side-effects, use h-rem*.
;;;;; replacing nested won't work — this is for h-rem*! (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem! htbl :a :b :c) htbl) !!> wrong-number-of-arguments (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem! htbl :a :b) htbl) !!> wrong-number-of-arguments (let ((htbl (h* :b 1))) (h-rem! htbl :a :c) htbl) !!> wrong-number-of-arguments ;;;;; regular (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem! htbl :a) htbl) H=> (h* :b (h* :c 3 :d 4)) (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem! htbl :b) htbl) H=> (h* :a 1) (let ((htbl (h* :a 1))) (h-rem! htbl :a) htbl) H=> (ht) (let ((htbl (h* :b 1))) (h-rem! htbl :a) htbl) H=> (h* :b 1) (let ((htbl (ht))) (h-rem! htbl :a) htbl) H=> (ht)
(table key)
Return a table that is TABLE with KEY removed.
This is a side-effect-free function.
Its destructive counterpart is h-rem!.
This function is not nesting-aware.
Its also-side-effect-free but nesting-aware counterpart is h-rem*.
For nesting-awareness with side-effects, use h-rem*!.
;;;;; replacing nested won't work — this is for h-rem* (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem htbl :a :b :c)) !!> wrong-number-of-arguments (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem htbl :a :b)) !!> wrong-number-of-arguments (let ((htbl (h* :b 1))) (h-rem htbl :a :c)) !!> wrong-number-of-arguments ;;;;; regular (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem htbl :a)) H=> (h* :b (h* :c 3 :d 4)) (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem htbl :b)) H=> (h* :a 1) (let ((htbl (h* :a 1))) (h-rem htbl :a)) H=> (ht) (let ((htbl (h* :b 1))) (h-rem htbl :a)) H=> (h* :b 1) (let ((htbl (ht))) (h-rem htbl :a)) H=> (ht)
(table &rest keys)
Remove key at end of KEYS sequence from hash table TABLE.
Each key in KEYS has as value the next, all of which hash tables,
except for the final key, which could return any value.
The last will be removed, together with the value associated with it.
If any of the keys in the sequence KEYS can't be found,
no action is taken.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-rem*.
This function is nesting-aware.
Its also-destructive but only-first-level counterpart is h-rem!.
For no nesting-awareness and no side-effects, use h-rem.
;;;;; replacing nested will work here (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem*! htbl :a :b :c) htbl) H=> (h* :a (h* :b (h* :d 4))) (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem*! htbl :a :b) htbl) H=> (h* :a (h*)) (let ((htbl (h* :b 1))) (h-rem*! htbl :a :c) htbl) H=> (h* :b 1) ;;;;; regular (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem*! htbl :a) htbl) H=> (h* :b (h* :c 3 :d 4)) (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem*! htbl :b) htbl) H=> (h* :a 1) (let ((htbl (h* :a 1))) (h-rem*! htbl :a) htbl) H=> (ht) (let ((htbl (h* :b 1))) (h-rem*! htbl :a) htbl) H=> (h* :b 1) (let ((htbl (ht))) (h-rem*! htbl :a) htbl) H=> (ht)
(table &rest keys)
Return a table that is TABLE with key at end of KEYS removed.
Each key in KEYS has as value the next, all of which hash tables,
except for the final key, which could return any value.
The last will be removed, together with the value associated with it.
If any of the keys in the sequence KEYS can't be found,
no action is taken.
This is a side-effect-free function.
Its destructive counterpart is h-rem*!.
This function is nesting-aware.
Its also-side-effect-free but only-first-level counterpart is h-rem.
For no nesting-awareness but with side-effects, use h-rem!.
;;;;; replacing nested will work here (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem* htbl :a :b :c)) H=> (h* :a (h* :b (h* :d 4))) (let ((htbl (h* :a (h* :b (h* :c 3 :d 4))))) (h-rem* htbl :a :b)) H=> (h* :a (h*)) (let ((htbl (h* :b 1))) (h-rem* htbl :a :c)) H=> (h* :b 1) ;;;;; regular (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem* htbl :a)) H=> (h* :b (h* :c 3 :d 4)) (let ((htbl (h* :a 1 :b (h* :c 3 :d 4)))) (h-rem* htbl :b)) H=> (h* :a 1) (let ((htbl (h* :a 1))) (h-rem* htbl :a)) H=> (ht) (let ((htbl (h* :b 1))) (h-rem* htbl :a)) H=> (h* :b 1) (let ((htbl (ht))) (h-rem* htbl :a)) H=> (ht)
Selection
Functions that create a hash table that has only the specified keys.
Summary:
Non-destructive | Destructive | |
---|---|---|
simple, doesn't recurse | h-sel-keys | h-sel-keys! |
(table keys)
Return a copy of TABLE with only the pairs specified by KEYS.
KEYS is a list.
This function is similar to current ht-select-keys
. The main
difference is that the copy's size is equal to TABLE's instead of
the default 65. All properties are preserved.
This is a side-effect-free function.
Its destructive counterpart is h-sel-keys!.
(let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-sel-keys htbl '(:a :d :e))) H=> (h* :a 1 :d 4 :e 5) (let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-sel-keys htbl '(:a :x :c :z))) H=> (h* :a 1 :c 3) (let ((htbl (h* :a 1))) (h-sel-keys htbl '(:x :c :z))) H=> (h-new)
(table keys)
Update TABLE to contain only the pairs specified by KEYS.
KEYS is a list.
This is a destructive function.
Its side-effect-free counterpart is h-sel-keys.
(let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-sel-keys! htbl '(:a :d :e)) htbl) H=> (h* :a 1 :d 4 :e 5) (let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-sel-keys! htbl '(:a :x :c :z)) htbl) H=> (h* :a 1 :c 3) (let ((htbl (h* :a 1))) (h-sel-keys! htbl '(:x :c :z)) htbl) H=> (h-new)
Guideline for the 'sel family' of functions:
- 'sel' means 'select: a nil in the result of FUN excludes KEY
from the results.' - an added '-' means 'anaphoric: enter a form, not a lambda'
- an added '*' means 'recurse: apply the values-function or -form
to the values whenever these are hash tables' - an added '!' means 'destructive: modify TABLE instead of
creating a fresh one'
In the anaphoric versions, if you don't use any or either of the
let-bound variables key and value in any or either of the forms,
it's ok — no warnings.
Summary:
Side-effect-free: don't modify original TABLE, return a fresh table
a lambda | a form (anaphoric) | |
---|---|---|
simple, doesn't recurse | h-sel | h--sel |
nesting-aware, recurses | h-sel* | h--sel* |
Destructive: modify original TABLE, return nil
a lambda | a form (anaphoric) | |
---|---|---|
simple, doesn't recurse | h-sel! | h--sel! |
nesting-aware, recurses | h-sel*! | h--sel*! |
(fun table)
Return a table with pairs from TABLE for which FUN returns non-nil.
Function FUN is called with two arguments, key and value.
This function is similar to current ht-select
.
This is a side-effect-free function.
Its destructive counterpart is h-sel!.
Its anaphoric counterpart is h--sel.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (and (numberp value) (= 2 (% value 10)))))) (h-sel fun h51)) H=> (h* "b" 2) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-sel fun h31)) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-sel fun h1)) H=> (h-new)
(form table)
Anaphoric version of h-sel.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a side-effect-free function.
Its destructive counterpart is h--sel!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--sel (and (numberp value) (= 2 (% value 10))) h51)) H=> (h* "b" 2) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--sel (or (equal key "Bob") (equal value "fish")) h31)) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2))) (h--sel (> value 3) h1)) H=> (h-new)
(fun table)
Update TABLE by keeping only pairs for which FUN returns non-nil.
Function FUN is called with two arguments, key and value.
This is a destructive function.
Its side-effect-free counterpart is h-sel.
Its anaphoric counterpart is h--sel!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (and (numberp value) (= 2 (% value 10)))))) (h-sel! fun h51) h51) H=> (h* "b" 2) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-sel! fun h31) h31) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-sel! fun h1) h1) H=> (h-new)
(form table)
Anaphoric version of h-sel!.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a destructive function.
Its side-effect-free counterpart is h--sel.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--sel! (and (numberp value) (= 2 (% value 10))) h51) h51) H=> (h* "b" 2) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--sel! (or (equal key "Bob") (equal value "fish")) h31) h31) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2))) (h--sel! (> value 3) h1) h1) H=> (h-new)
(fun table)
Return a table with pairs from TABLE for which FUN returns non-nil.
Function FUN is called with two arguments, key and value.
Just like h-sel, but recurses wherever TABLE is nested.
This means that for any VALUE that is itself a hash table, instead
of returning it as it is when FUNCTION returns non-nil, as h-sel
would do, VALUE will be the very h-sel* recursively applied to
that hash table with the same FUN.
This is a side-effect-free function.
Its destructive counterpart is h-sel*!.
Its anaphoric counterpart is h--sel*.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (or (ht? value) (and (numberp value) (= 2 (% value 10))))))) (h-sel* fun h51)) H=> (h* "b" 2 "d" (h* "d2" 42 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-sel* fun h31)) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-sel* fun h1)) H=> (h-new)
(form table)
Anaphoric version of h-sel*.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a side-effect-free function.
Its destructive counterpart is h--sel*!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--sel* (or (ht? value) (and (numberp value) (= 2 (% value 10)))) h51)) H=> (h* "b" 2 "d" (h* "d2" 42 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--sel* (or (equal key "Bob") (equal value "fish")) h31)) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2))) (h--sel* (> value 3) h1)) H=> (h-new)
(fun table)
Update TABLE by keeping only pairs for which FUN returns non-nil.
Function FUN is called with two arguments, key and value.
This is a destructive function.
Its side-effect-free counterpart is h-sel*.
Its anaphoric counterpart is h--sel*!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (or (ht? value) (and (numberp value) (= 2 (% value 10))))))) (h-sel*! fun h51) h51) H=> (h* "b" 2 "d" (h* "d2" 42 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-sel*! fun h31) h31) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-sel*! fun h1) h1) H=> (h-new)
(form table)
Anaphoric version of h-sel*!.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a destructive function.
Its side-effect-free counterpart is h--sel*.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--sel*! (or (ht? value) (and (numberp value) (= 2 (% value 10)))) h51) h51) H=> (h* "b" 2 "d" (h* "d2" 42 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--sel*! (or (equal key "Bob") (equal value "fish")) h31) h31) H=> (h* "Bob" "cat" "Bubbly" "fish") (let ((h1 (h* "a" 1 "b" 2))) (h--sel*! (> value 3) h1) h1) H=> (h-new)
Rejection
Functions that create a hash table that has all but the specified keys.
Summary:
Non-destructive | Destructive | |
---|---|---|
simple, doesn't recurse | h-rej-keys | h-rej-keys! |
(table keys)
Return a copy of TABLE with all but the pairs specified by KEYS.
KEYS is a list.
This is a side-effect-free function.
Its destructive counterpart is h-rej-keys!.
(let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-rej-keys htbl '(:a :d :e))) H=> (h* :b 2 :c 3 :f 6) (let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-rej-keys htbl '(:a :x :c :z))) H=> (h* :b 2 :d 4 :e 5 :f 6) (let ((htbl (h* :a 1))) (h-rej-keys htbl '(:x :c :z))) H=> (h* :a 1)
(table keys)
Update TABLE by removing the pairs specified by KEYS.
KEYS is a list.
This is a destructive function.
Its side-effect-free counterpart is h-rej-keys.
(let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-rej-keys! htbl '(:a :d :e)) htbl) H=> (h* :b 2 :c 3 :f 6) (let ((htbl (h* :a 1 :b 2 :c 3 :d 4 :e 5 :f 6))) (h-rej-keys! htbl '(:a :x :c :z)) htbl) H=> (h* :b 2 :d 4 :e 5 :f 6) (let ((htbl (h* :a 1))) (h-rej-keys! htbl '(:x :c :z)) htbl) H=> (h* :a 1)
Guideline for the 'rej family' of functions:
- 'rej' means 'reject: non-nil in the result of FUN excludes KEY
from the results.' - an added '-' means 'anaphoric: enter a form, not a lambda'
- an added '*' means 'recurse: apply the values-function or -form
to the values whenever these are hash tables' - an added '!' means 'destructive: modify TABLE instead of
creating a fresh one'
In the anaphoric versions, if you don't use any or either of the
let-bound variables key and value in any or either of the forms,
it's ok — no warnings.
Summary:
Side-effect-free: don't modify original TABLE, return a fresh table
a lambda | a form (anaphoric) | |
---|---|---|
simple, doesn't recurse | h-rej | h--rej |
nesting-aware, recurses | h-rej* | h--rej* |
Destructive: modify original TABLE, return nil
a lambda | a form (anaphoric) | |
---|---|---|
simple, doesn't recurse | h-rej! | h--rej! |
nesting-aware, recurses | h-rej*! | h--rej*! |
(fun table)
Return a table with pairs from TABLE for which FUN returns nil.
Function FUN is called with two arguments, key and value.
This function is similar to current ht-reject
.
This is a side-effect-free function.
Its destructive counterpart is h-rej!.
Its anaphoric counterpart is h--rej.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (and (numberp value) (= 2 (% value 10)))))) (h-rej fun h51)) H=> (h* "a" 1 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-rej fun h31)) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-rej fun h1)) H=> (h* "a" 1 "b" 2)
(form table)
Anaphoric version of h-rej.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a side-effect-free function.
Its destructive counterpart is h--rej!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--rej (and (numberp value) (= 2 (% value 10))) h51)) H=> (h* "a" 1 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--rej (or (equal key "Bob") (equal value "fish")) h31)) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2))) (h--rej (> value 3) h1)) H=> (h* "a" 1 "b" 2)
(fun table)
Update TABLE by keeping only pairs for which FUN returns nil.
Function FUN is called with two arguments, key and value.
This function is similar to current ht-reject!
.
This is a destructive function.
Its side-effect-free counterpart is h-rej.
Its anaphoric counterpart is h--rej!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (and (numberp value) (= 2 (% value 10)))))) (h-rej! fun h51) h51) H=> (h* "a" 1 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-rej! fun h31) h31) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-rej! fun h1) h1) H=> (h* "a" 1 "b" 2)
(form table)
Anaphoric version of h-rej!.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a destructive function.
Its side-effect-free counterpart is h--rej.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--rej! (and (numberp value) (= 2 (% value 10))) h51) h51) H=> (h* "a" 1 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--rej! (or (equal key "Bob") (equal value "fish")) h31) h31) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2))) (h--rej! (> value 3) h1) h1) H=> (h* "a" 1 "b" 2)
(fun table)
Return a table with pairs from TABLE for which FUN returns nil.
Function FUN is called with two arguments, key and value.
Just like h-rej, but recurses wherever TABLE is nested.
This means that for any VALUE that is itself a hash table, instead
of returning it as it is when FUN returns nil, as h-rej would do,
VALUE will be the very h-rej* recursively applied to that hash
table with the same FUN.
This is a side-effect-free function.
Its destructive counterpart is h-rej*!.
Its anaphoric counterpart is h--rej*.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (or (ht? value) (and (numberp value) (= 2 (% value 10))))))) (h-rej* fun h51)) H=> (h* "a" 1) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-rej* fun h31)) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-rej* fun h1)) H=> (h* "a" 1 "b" 2)
(form table)
Anaphoric version of h-rej*.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a side-effect-free function.
Its destructive counterpart is h--rej*!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--rej* (or (ht? value) (and (numberp value) (= 2 (% value 10)))) h51)) H=> (h* "a" 1) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--rej* (or (equal key "Bob") (equal value "fish")) h31)) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2))) (h--rej* (> value 3) h1)) H=> (h* "a" 1 "b" 2)
(fun table)
Update TABLE by keeping only pairs for which FUN returns nil.
Function FUN is called with two arguments, key and value.
This is a destructive function.
Its side-effect-free counterpart is h-rej*.
Its anaphoric counterpart is h--rej*!.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52))) (fun (lambda (_key value) (or (ht? value) (and (numberp value) (= 2 (% value 10))))))) (h-rej*! fun h51) h51) H=> (h* "a" 1) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish")) (fun (lambda (key value) (or (equal key "Bob") (equal value "fish"))))) (h-rej*! fun h31) h31) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2)) (fun (lambda (_key value) (> value 3)))) (h-rej*! fun h1) h1) H=> (h* "a" 1 "b" 2)
(form table)
Anaphoric version of h-rej*!.
For every key–value pair in TABLE, evaluate FORM with the variables
key and value bound.
This is a destructive function.
Its side-effect-free counterpart is h--rej*.
(let ((h51 (h* "a" 1 "b" 2 "d" (h* "d1" 41 "d2" 42 "e1" 51 "e2" 52)))) (h--rej*! (or (ht? value) (and (numberp value) (= 2 (% value 10)))) h51) h51) H=> (h* "a" 1) (let ((h31 (h* "Bob" "cat" "Whiskers" "cat" "Bubbly" "fish"))) (h--rej*! (or (equal key "Bob") (equal value "fish")) h31) h31) H=> (h* "Whiskers" "cat") (let ((h1 (h* "a" 1 "b" 2))) (h--rej*! (> value 3) h1) h1) H=> (h* "a" 1 "b" 2)
Reverse order
Functions to reverse the order in which the items are stored in
the table.
Summary:
non-destructive | destructive | |
---|---|---|
simple, doesn't recurse | h-reverse | h-reverse! |
nesting-aware, recurses | h-reverse* | h-reverse*! |
(table)
Create a hash table identical to TABLE with key order reversed.
(let ((htbl (h* 'a 1 'b (h* :c 3 :d 4 :e 5) 'f (h* :g 7 :h 8)))) (h-reverse htbl)) H=> (h* 'f (h* :g 7 :h 8) 'b (h* :c 3 :d 4 :e 5) 'a 1) (let ((htbl (h* 'a 1 'b 2 'c 3))) (h-reverse htbl)) H=> (h* 'c 3 'b 2 'a 1) (let ((htbl (h* 'a 1))) (h-reverse htbl)) H=> (h* 'a 1) (let ((htbl (h-new))) (h-reverse htbl)) H=> (h-new)
(table)
Reverse the key order of hash table TABLE.
(let ((htbl (h* 'a 1 'b (h* :c 3 :d 4 :e 5) 'f (h* :g 7 :h 8)))) (h-reverse! htbl) htbl) H=> (h* 'f (h* :g 7 :h 8) 'b (h* :c 3 :d 4 :e 5) 'a 1) (let ((htbl (h* 'a 1 'b 2 'c 3))) (h-reverse! htbl) htbl) H=> (h* 'c 3 'b 2 'a 1) (let ((htbl (h* 'a 1))) (h-reverse! htbl) htbl) H=> (h* 'a 1) (let ((htbl (h-new))) (h-reverse! htbl) htbl) H=> (h-new)
(table)
Create a hash table identical to TABLE with key order reversed.
When TABLE is nested, do the same to all values that are hash
tables.
(let ((htbl (h* 'a 1 'b (h* :c 3 :d 4 :e 5) 'f (h* :g 7 :h 8)))) (h-reverse* htbl)) H=> (h* 'f (h* :h 8 :g 7) 'b (h* :e 5 :d 4 :c 3) 'a 1) (let ((htbl (h* 'a 1 'b 2 'c 3))) (h-reverse* htbl)) H=> (h* 'c 3 'b 2 'a 1) (let ((htbl (h* 'a 1))) (h-reverse* htbl)) H=> (h* 'a 1) (let ((htbl (h-new))) (h-reverse* htbl)) H=> (h-new)
(table)
Reverse the key order of hash table TABLE.
When TABLE is nested, do the same to all values that are hash
tables.
(let ((htbl (h* 'a 1 'b (h* :c 3 :d 4 :e 5) 'f (h* :g 7 :h 8)))) (h-reverse*! htbl) htbl) H=> (h* 'f (h* :h 8 :g 7) 'b (h* :e 5 :d 4 :c 3) 'a 1) (let ((htbl (h* 'a 1 'b 2 'c 3))) (h-reverse*! htbl) htbl) H=> (h* 'c 3 'b 2 'a 1) (let ((htbl (h* 'a 1))) (h-reverse*! htbl) htbl) H=> (h* 'a 1) (let ((htbl (h-new))) (h-reverse*! htbl) htbl) H=> (h-new)
Change numerical value, including #'1+ and #'1-
Functions to alter values of keys when these values are numeric or
number-like.
Summary:
Non-destructive | Destructive | |
---|---|---|
Generic | h-put-num-with* | h-put-num-with*! |
Increase by 1 | h-put-inc* | h-put-inc*! |
Decrease by 1 | h-put-dec* | h-put-dec*! |
(fun table &rest keys)
Return copy of TABLE with FUN applied to numeric value of KEYS.
KEYS is a chain of keys to access the key of an internal hash table
if TABLE is nested — just as with h-get*
. If it's a simple,
non-nested TABLE, or if the value is at the "first level", enter
just one key.
FUN is a unary function that can be applied to numerical values.
These are some examples: 1+
, 1-
, sqrt
, log10
.
You may also create partial application ones, such as:
(-partial '+ 10) (-partial '* 2)
or:
(lambda (x) (expt x 2))
The value must, of course, be a number — but the function is
flexible about what a number is. If it quacks like a number, we
increase it. So it may be found stored also as a string or keyword,
and it will be increased while maintaining the type. Moreover, it
tries to preserve original leading zeros when they were there in
strings, keywords, or symbols.
So "041", receiving #'1+, becomes "042", and so on.
When value is nil or key is not found, interpret it as being 0.
The function uses base 10.
This is a side-effect-free function.
Its destructive counterpart is h-put-num-with*!.
;;;; Integers (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-num-with* #'1+ htbl 'a)) H=> (h* 'a 2 'b (h* :c 3 :d 4)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-num-with* #'1+ htbl 'b :d)) H=> (h* 'a 1 'b (h* :c 3 :d 5)) ;;;; Floats (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-num-with* #'1+ htbl 'a)) H=> (h* 'a 2.1 'b (h* :c 3.1 :d 3.2)) (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-num-with* (-partial '/ 32) htbl 'b :d)) H=> (h* 'a 1.1 'b (h* :c 3.1 :d 10.0)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-num-with* #'sqrt htbl 'b :d)) H=> (h* 'a 1 'b (h* :c 3 :d 2.0)) ;;;; Strings (let* ((htbl (h* 'a "006" 'b (h* :c "3" :d "4")))) (h-put-num-with* #'1+ htbl 'a)) H=> (h* 'a "007" 'b (h* :c "3" :d "4")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-num-with* #'sqrt htbl 'b :d)) H=> (h* 'a "1" 'b (h* :c "3" :d "2.0")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "003")))) (h-put-num-with* (-partial '* 10) htbl 'b :d)) H=> (h* 'a "1" 'b (h* :c "3" :d "030")) ;;;; Keywords (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-num-with* (lambda (x) (expt (+ 3 x) 2)) ;; (x+3)² htbl 'a)) H=> (h* 'a :16 'b (h* :c :3 :d :4)) (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :004)))) (h-put-num-with* (-partial '+ 38) htbl 'b :d)) H=> (h* 'a :1 'b (h* :c :3 :d :042)) ;;;; New values and nil (let* ((htbl (h-new))) (h-put-num-with* #'1- htbl 'b)) H=> (h* 'b -1) (let* ((htbl (h* "a" 1))) (h-put-num-with* #'1- htbl "b")) H=> (h* "a" 1 "b" -1) (let* ((htbl (h* "a" 1 "b" nil))) (h-put-num-with* #'1- htbl "b")) H=> (h* "a" 1 "b" -1) ;;;; Not number-like (let* ((htbl (h* 'a "A"))) (h-put-num-with* #'1- htbl 'a)) !!> error
(fun table &rest keys)
Apply FUN to the numeric value of KEYS in possibly nested hash TABLE.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-put-num-with*.
;;;; Integers (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-num-with*! #'1+ htbl 'a) htbl) H=> (h* 'a 2 'b (h* :c 3 :d 4)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-num-with*! #'1+ htbl 'b :d) htbl) H=> (h* 'a 1 'b (h* :c 3 :d 5)) ;;;; Floats (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-num-with*! #'1+ htbl 'a) htbl) H=> (h* 'a 2.1 'b (h* :c 3.1 :d 3.2)) (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-num-with*! (-partial '/ 32) htbl 'b :d) htbl) H=> (h* 'a 1.1 'b (h* :c 3.1 :d 10.0)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-num-with*! #'sqrt htbl 'b :d) htbl) H=> (h* 'a 1 'b (h* :c 3 :d 2.0)) ;;;; Strings (let* ((htbl (h* 'a "006" 'b (h* :c "3" :d "4")))) (h-put-num-with*! #'1+ htbl 'a) htbl) H=> (h* 'a "007" 'b (h* :c "3" :d "4")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-num-with*! #'sqrt htbl 'b :d) htbl) H=> (h* 'a "1" 'b (h* :c "3" :d "2.0")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "003")))) (h-put-num-with*! (-partial '* 10) htbl 'b :d) htbl) H=> (h* 'a "1" 'b (h* :c "3" :d "030")) ;;;; Keywords (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-num-with*! (lambda (x) (expt (+ 3 x) 2)) ;; (x+3)² htbl 'a) htbl) H=> (h* 'a :16 'b (h* :c :3 :d :4)) (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :004)))) (h-put-num-with*! (-partial '+ 38) htbl 'b :d) htbl) H=> (h* 'a :1 'b (h* :c :3 :d :042)) ;;;; New values and nil (let* ((htbl (h-new))) (h-put-num-with*! #'1- htbl 'b) htbl) H=> (h* 'b -1) (let* ((htbl (h* "a" 1))) (h-put-num-with*! #'1- htbl "b") htbl) H=> (h* "a" 1 "b" -1) (let* ((htbl (h* "a" 1 "b" nil))) (h-put-num-with*! #'1- htbl "b") htbl) H=> (h* "a" 1 "b" -1) ;;;; Not number-like (let* ((htbl (h* 'a "A"))) (h-put-num-with*! #'1- htbl 'a) htbl) !!> error
(table &rest keys)
Return a copy of TABLE with 1 added to the value of KEYS.
KEYS is a chain of keys to access the key of an internal hash table
if TABLE is nested — just as with h-get*
. If it's a simple,
non-nested TABLE, or if the value is at the "first level", enter
just one key.
The value must, of course, be a number — but the function is
flexible about what a number is. If it quacks like a number, we
increase it. So it may be found stored also as a string or keyword,
and it will be increased while maintaining the type. Moreover, it
tries to preserve original leading zeros when they were there in
strings, keywords, or symbols.
So "041" becomes "042", and so on.
When value is nil or key is not found, interpret it as being 0.
The function uses base 10.
This is a side-effect-free function.
Its destructive counterpart is h-put-inc*!.
;;;; Integers (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-inc* htbl 'a)) H=> (h* 'a 2 'b (h* :c 3 :d 4)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-inc* htbl 'b :d)) H=> (h* 'a 1 'b (h* :c 3 :d 5)) ;;;; Floats (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-inc* htbl 'a)) H=> (h* 'a 2.1 'b (h* :c 3.1 :d 3.2)) (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-inc* htbl 'b :d)) H=> (h* 'a 1.1 'b (h* :c 3.1 :d 4.2)) ;;;; Strings (-> "| id | name | age | |------+-------+-----| | id01 | Alice | 41 | | id02 | Bob | 30 | | id03 | Emily | 21 |" h<-orgtbl (h-put-inc* "id01" "age") h->orgtbl) O=> "| id | name | age | |------+-------+-----| | id01 | Alice | 42 | | id02 | Bob | 30 | | id03 | Emily | 21 |" ;; (yes, I know that there are other ways to manipulate numbers in ;; org tables — but this works, too, and it's a nice example) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-inc* htbl 'a)) H=> (h* 'a "2" 'b (h* :c "3" :d "4")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-inc* htbl 'b :d)) H=> (h* 'a "1" 'b (h* :c "3" :d "5")) ;;;; Keywords (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-inc* htbl 'a)) H=> (h* 'a :2 'b (h* :c :3 :d :4)) (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-inc* htbl 'b :d)) H=> (h* 'a :1 'b (h* :c :3 :d :5)) ;;;; New values and nil (let* ((htbl (h-new))) (h-put-inc* htbl 'b)) H=> (h* 'b 1) (let* ((htbl (h* "a" 1))) (h-put-inc* htbl "b")) H=> (h* "a" 1 "b" 1) (let* ((htbl (h* "a" 1 "b" nil))) (h-put-inc* htbl "b")) H=> (h* "a" 1 "b" 1) ;;;; Not number-like (let* ((htbl (h* 'a "A"))) (h-put-inc* htbl 'a)) !!> error
(table &rest keys)
Add 1 to the value of KEYS in possibly nested hash TABLE.
See h-put-inc* for more information.
This function returns nil.
;;;; Integers (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-inc*! htbl 'a) htbl) H=> (h* 'a 2 'b (h* :c 3 :d 4)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-inc*! htbl 'b :d) htbl) H=> (h* 'a 1 'b (h* :c 3 :d 5)) ;;;; Floats (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-inc*! htbl 'a) htbl) H=> (h* 'a 2.1 'b (h* :c 3.1 :d 3.2)) (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-inc*! htbl 'b :d) htbl) H=> (h* 'a 1.1 'b (h* :c 3.1 :d 4.2)) ;;;; Strings (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-inc*! htbl 'a) htbl) H=> (h* 'a "2" 'b (h* :c "3" :d "4")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-inc*! htbl 'b :d) htbl) H=> (h* 'a "1" 'b (h* :c "3" :d "5")) ;;;; Keywords (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-inc*! htbl 'a) htbl) H=> (h* 'a :2 'b (h* :c :3 :d :4)) (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-inc*! htbl 'b :d) htbl) H=> (h* 'a :1 'b (h* :c :3 :d :5)) ;;;; New values and nil (let* ((htbl (h-new))) (h-put-inc*! htbl 'b) htbl) H=> (h* 'b 1) (let* ((htbl (h* "a" 1))) (h-put-inc*! htbl "b") htbl) H=> (h* "a" 1 "b" 1) (let* ((htbl (h* "a" 1 "b" nil))) (h-put-inc*! htbl "b") htbl) H=> (h* "a" 1 "b" 1) ;;;; Not number-like (let* ((htbl (h* 'a "A"))) (h-put-inc*! htbl 'a) htbl) !!> error
(table &rest keys)
Return a copy of TABLE with 1 subtracted from the value of KEYS.
KEYS is a chain of keys to access the key of an internal hash table
if TABLE is nested — just as with h-get*
. If it's a simple,
non-nested TABLE, or if the value is at the "first level", enter
just one key.
The value must, of course, be a number — but the function is
flexible about what a number is. If it quacks like a number, we
decrease it. So it may be found stored also as a string or keyword,
and it will be increased while maintaining the type. Moreover, it
tries to preserve original leading zeros when they were there in
strings, keywords, or symbols.
So "043" becomes "042", and so on.
The function uses base 10.
This is a side-effect-free function.
Its destructive counterpart is h-put-dec*!.
;;;; Integers (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-dec* htbl 'a)) H=> (h* 'a 0 'b (h* :c 3 :d 4)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-dec* htbl 'b :d)) H=> (h* 'a 1 'b (h* :c 3 :d 3)) ;;;; Floats (let* ((htbl (h* 'a 5.2 'b (h* :c 3.1 :d 3.2)))) (h-put-dec* htbl 'a)) H=> (h* 'a 4.2 'b (h* :c 3.1 :d 3.2)) (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-dec* htbl 'b :d)) H=> (h* 'a 1.1 'b (h* :c 3.1 :d 2.2)) ;;;; Strings (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-dec* htbl 'a)) H=> (h* 'a "0" 'b (h* :c "3" :d "4")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-dec* htbl 'b :d)) H=> (h* 'a "1" 'b (h* :c "3" :d "3")) ;;;; Keywords (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-dec* htbl 'a)) H=> (h* 'a :0 'b (h* :c :3 :d :4)) (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-dec* htbl 'b :d)) H=> (h* 'a :1 'b (h* :c :3 :d :3)) ;;;; New values and nil (let* ((htbl (h-new))) (h-put-dec* htbl 'b)) H=> (h* 'b -1) (let* ((htbl (h* "a" 1))) (h-put-dec* htbl "b")) H=> (h* "a" 1 "b" -1) (let* ((htbl (h* "a" 1 "b" nil))) (h-put-dec* htbl "b")) H=> (h* "a" 1 "b" -1) ;;;; Not number-like (let* ((htbl (h* 'a "A"))) (h-put-dec* htbl 'a)) !!> error
(table &rest keys)
Subtract 1 from the value of KEYS in possibly nested hash TABLE.
See h-put-dec* for more information.
This function returns nil.
;;;; Integers (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-dec*! htbl 'a) htbl) H=> (h* 'a 0 'b (h* :c 3 :d 4)) (let* ((htbl (h* 'a 1 'b (h* :c 3 :d 4)))) (h-put-dec*! htbl 'b :d) htbl) H=> (h* 'a 1 'b (h* :c 3 :d 3)) ;;;; Floats (let* ((htbl (h* 'a 5.2 'b (h* :c 3.1 :d 3.2)))) (h-put-dec*! htbl 'a) htbl) H=> (h* 'a 4.2 'b (h* :c 3.1 :d 3.2)) (let* ((htbl (h* 'a 1.1 'b (h* :c 3.1 :d 3.2)))) (h-put-dec*! htbl 'b :d) htbl) H=> (h* 'a 1.1 'b (h* :c 3.1 :d 2.2)) ;;;; Strings (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-dec*! htbl 'a) htbl) H=> (h* 'a "0" 'b (h* :c "3" :d "4")) (let* ((htbl (h* 'a "1" 'b (h* :c "3" :d "4")))) (h-put-dec*! htbl 'b :d) htbl) H=> (h* 'a "1" 'b (h* :c "3" :d "3")) ;;;; Keywords (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-dec*! htbl 'a) htbl) H=> (h* 'a :0 'b (h* :c :3 :d :4)) (let* ((htbl (h* 'a :1 'b (h* :c :3 :d :4)))) (h-put-dec*! htbl 'b :d) htbl) H=> (h* 'a :1 'b (h* :c :3 :d :3)) ;;;; New values and nil (let* ((htbl (h-new))) (h-put-dec*! htbl 'b) htbl) H=> (h* 'b -1) (let* ((htbl (h* "a" 1))) (h-put-dec*! htbl "b") htbl) H=> (h* "a" 1 "b" -1) (let* ((htbl (h* "a" 1 "b" nil))) (h-put-dec*! htbl "b") htbl) H=> (h* "a" 1 "b" -1) ;;;; Not number-like (let* ((htbl (h* 'a "A"))) (h-put-dec*! htbl 'a) htbl) !!> error
Miscellaneous key, value, pair lookups
Other functions about checking keys, values, and pairs.
(table key)
Return t if TABLE has KEY.
This function is just like current ht-contains?
(h-has-key? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "c") => t (h-has-key? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "f") => nil (h-has-key? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "d") => nil (h-has-key? (h* "d" 4 "e" 5) "d") => t (h-has-key? (h-new) "a") => nil (let ((my-ht-1 (h* :a 1 :b 2)) (my-ht-2 (h* :a 1 :b 2 :c nil)) (my-ht-3 (h* :a 1 :b 2 :c 3)) (my-ht-4 (h* :a 1 :b 2 :c "xht--not-found")) (my-ht-5 (h* :a 1 :b 2 :c 'xht--not-found)) (my-ht-6 (h* :a 1 :b 2 :c (make-symbol "xht--not-found"))) (my-ht-7 (h* :a 1 :b (h*))) (my-ht-8 (h* :a 1 :b (h* :c nil))) (my-ht-9 (h* :a 1 :b (h* :c 3))) (my-ht-A (h* :a 1 :b (h* :c "xht--not-found"))) (my-ht-B (h* :a 1 :b (h* :c 'xht--not-found))) (my-ht-C (h* :a 1 :b (h* :c (make-symbol "xht--not-found"))))) (--map (h-has-key? it :c) (list my-ht-1 my-ht-2 my-ht-3 my-ht-4 my-ht-5 my-ht-6 my-ht-7 my-ht-8 my-ht-9 my-ht-A my-ht-B my-ht-C))) => '(nil t t t t t nil nil nil nil nil nil)
(table key)
Whether possibly-nested TABLE has KEY at some level.
Similar to h-has-key?, but if TABLE is nested look for KEY also
in the internal hash tables of TABLE.
(h-has-key*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "c") => t (h-has-key*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "f") => nil (h-has-key*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "d") => t (h-has-key*? (h* "d" 4 "e" 5) "d") => t (h-has-key*? (h-new) "a") => nil (let ((my-ht-1 (h* :a 1 :b 2)) (my-ht-2 (h* :a 1 :b 2 :c nil)) (my-ht-3 (h* :a 1 :b 2 :c 3)) (my-ht-4 (h* :a 1 :b 2 :c "xht--not-found")) (my-ht-5 (h* :a 1 :b 2 :c 'xht--not-found)) (my-ht-6 (h* :a 1 :b 2 :c (make-symbol "xht--not-found"))) (my-ht-7 (h* :a 1 :b (h*))) (my-ht-8 (h* :a 1 :b (h* :c nil))) (my-ht-9 (h* :a 1 :b (h* :c 3))) (my-ht-A (h* :a 1 :b (h* :c "xht--not-found"))) (my-ht-B (h* :a 1 :b (h* :c 'xht--not-found))) (my-ht-C (h* :a 1 :b (h* :c (make-symbol "xht--not-found"))))) (--map (h-has-key*? it :c) (list my-ht-1 my-ht-2 my-ht-3 my-ht-4 my-ht-5 my-ht-6 my-ht-7 my-ht-8 my-ht-9 my-ht-A my-ht-B my-ht-C))) => '(nil t t t t t nil t t t t t)
(table value)
Return t if TABLE has some key whose value is VALUE.
(h-has-value? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) 2) => t (h-has-value? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) 7) => nil (h-has-value? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) 4) => nil (h-has-value? (h* "d" 4 "e" 5) 7) => nil (h-has-value? (h-new) 1) => nil (let ((my-ht-1 (h* :a 1 :b 2)) (my-ht-2 (h* :a 1 :b 2 :c 3)) (my-ht-3 (h* :a 3 :b 2 :c 1)) (my-ht-4 (h* :a 3 :b 2 :c 3)) (my-ht-5 (h* :a 1 :b (h* :c 3 :d 4))) (my-ht-6 (h* :a 1 :b (h* :c 4 :d 3))) (my-ht-7 (h* :a 1 :b (h* :d (h* :c 3)))) (my-ht-8 (h* :a 1 :b (h* :d (h* :c 4)))) (my-ht-9 (h* :a 3 :b (h* :d (h* :c 4))))) (--map (h-has-value? it 3) (list my-ht-1 my-ht-2 my-ht-3 my-ht-4 my-ht-5 my-ht-6 my-ht-7 my-ht-8 my-ht-9))) => '(nil t t t nil nil nil nil t)
(table value)
Whether possibly-nested TABLE has at some level key–VALUE pair.
Similar to h-has-value?, but if TABLE is nested look for VALUE
also in the internal hash tables of TABLE.
(h-has-value*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) 2) => t (h-has-value*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) 4) => t (h-has-value*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) 7) => nil (h-has-value*? (h* "d" 4 "e" 5) 7) => nil (h-has-value*? (h-new) 1) => nil (let ((my-ht-1 (h* :a 1 :b 2)) (my-ht-2 (h* :a 1 :b 2 :c 3)) (my-ht-3 (h* :a 3 :b 2 :c 1)) (my-ht-4 (h* :a 3 :b 2 :c 3)) (my-ht-5 (h* :a 1 :b (h* :c 3 :d 4))) (my-ht-6 (h* :a 1 :b (h* :c 4 :d 3))) (my-ht-7 (h* :a 1 :b (h* :d (h* :c 3)))) (my-ht-8 (h* :a 1 :b (h* :d (h* :c 4)))) (my-ht-9 (h* :a 3 :b (h* :d (h* :c 4))))) (--map (h-has-value*? it 3) (list my-ht-1 my-ht-2 my-ht-3 my-ht-4 my-ht-5 my-ht-6 my-ht-7 my-ht-8 my-ht-9))) => '(nil t t t t t t nil t)
(table key value)
Return t if TABLE has KEY and its value is VALUE.
(h-has-pair? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "a" 1) => t (h-has-pair? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "a" 2) => nil (h-has-pair? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "f" 6) => nil (h-has-pair? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "d" 4) => nil (h-has-pair? (h* "d" 4 "e" 5) "d" 4) => t (h-has-pair? (h-new) "a" 1) => nil (let ((my-ht-1 (h* :a 1 :b 2)) (my-ht-2 (h* :a 1 :b 2 :c 3)) (my-ht-3 (h* :a 3 :b 2 :c 1)) (my-ht-4 (h* :a 3 :b 2 :c 3)) (my-ht-5 (h* :a 1 :b (h* :c 3 :d 4))) (my-ht-6 (h* :a 1 :b (h* :c 4 :d 3))) (my-ht-7 (h* :a 1 :b (h* :d (h* :c 3)))) (my-ht-8 (h* :a 1 :b (h* :d (h* :c 4)))) (my-ht-9 (h* :a 3 :b (h* :d (h* :c 4))))) (--map (h-has-pair? it :c 3) (list my-ht-1 my-ht-2 my-ht-3 my-ht-4 my-ht-5 my-ht-6 my-ht-7 my-ht-8 my-ht-9))) => '(nil t nil t nil nil nil nil nil)
(table key value)
Whether possibly-nested TABLE has at some level KEY–VALUE pair.
Similar to h-has-pair?, but if TABLE is nested look for KEY–VALUE
pair also in the internal hash tables of TABLE.
(h-has-pair*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "a" 1) => t (h-has-pair*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "a" 2) => nil (h-has-pair*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "f" 6) => nil (h-has-pair*? (h* "a" 1 "b" 2 "c" (h* "d" 4 "e" 5)) "d" 4) => t (h-has-pair*? (h* "d" 4 "e" 5) "d" 4) => t (h-has-pair*? (h-new) "a" 1) => nil (let ((my-ht-1 (h* :a 1 :b 2)) (my-ht-2 (h* :a 1 :b 2 :c 3)) (my-ht-3 (h* :a 3 :b 2 :c 1)) (my-ht-4 (h* :a 3 :b 2 :c 3)) (my-ht-5 (h* :a 1 :b (h* :c 3 :d 4))) (my-ht-6 (h* :a 1 :b (h* :c 4 :d 3))) (my-ht-7 (h* :a 1 :b (h* :d (h* :c 3)))) (my-ht-8 (h* :a 1 :b (h* :d (h* :c 4)))) (my-ht-9 (h* :a 3 :b (h* :d (h* :c 4))))) (--map (h-has-pair*? it :c 3) (list my-ht-1 my-ht-2 my-ht-3 my-ht-4 my-ht-5 my-ht-6 my-ht-7 my-ht-8 my-ht-9))) => '(nil t nil t t nil t nil nil)
(table keys)
Return t if TABLE has all KEYS.
KEYS is either a single key or a list of keys.
(h-has-keys? (h* :a 1 :b 2 :c 3) '(:a :c)) => t (h-has-keys? (h* :a 1 :b 2 :c 3) '(:a :d)) => nil (h-has-keys? (h* :a 1 :b 2 :c 3) '(:a)) => t (h-has-keys? (h* :a 1 :b 2 :c 3) '(:e)) => nil (h-has-keys? (h* :a 1 :b 2 :c 3) '()) => t (h-has-keys? (h* :a 1 :b 2 :c 3) :a) => t (h-has-keys? (h* :a 1 :b 2 :c 3) :e) => nil (h-has-keys? (h-new) '(:a)) => nil ;; nope: either enter 1 key or a list of keys: (h-has-keys? (h* :a 1 :b 2 :c 3) :a :b) !!> wrong-number-of-arguments
(table values)
Return t if TABLE has all VALUES.
VALUES is either a single value or a list of values.
(h-has-values? (h* :a 1 :b 2 :c 3) '(1 2)) => t (h-has-values? (h* :a 1 :b 2 :c 3) '(1 4)) => nil (h-has-values? (h* :a 1 :b 2 :c 3) '(1)) => t (h-has-values? (h* :a 1 :b 2 :c 3) '(5)) => nil (h-has-values? (h* :a 1 :b 2 :c 3) '()) => t (h-has-values? (h* :a 1 :b 2 :c 3) 1) => t (h-has-values? (h* :a 1 :b 2 :c 3) 5) => nil (h-has-values? (h-new) '(1)) => nil ;; nope: either enter 1 value or a list of values: (h-has-values? (h* :a 1 :b 2 :c 3) 1 2) !!> wrong-number-of-arguments
(table pairs)
Return t if TABLE has all PAIRS.
PAIRS is either a single pair (a two-item list) or a list of such
two-item lists.
(h-has-pairs? (h* :a 1 :b 2 :c 3) '((:a 1) (:c 3))) => t (h-has-pairs? (h* :a 1 :b 2 :c 3) '((:a 1) (:d 4))) => nil (h-has-pairs? (h* :a 1 :b 2 :c 3) '((:a 1))) => t (h-has-pairs? (h* :a 1 :b 2 :c 3) '((:e 5))) => nil (h-has-pairs? (h* :a 1 :b 2 :c 3) '()) => t (h-has-pairs? (h* :a 1 :b 2 :c 3) '(:a 1)) => t (h-has-pairs? (h* :a 1 :b 2 :c 3) '(:e 5)) => nil (h-has-pairs? (h-new) '(:a 1)) => nil ;; nope: either enter 1 pair or a list of pairs: (h-has-pairs? (h* :a 1 :b 2 :c 3) '(:a 1) '(:b 2)) !!> wrong-number-of-arguments
Multi-table operations
Functions that act on two or more hash tables simultaneously.
XHT loves short function names.
Sometimes, however, their meaning might not be obvious at first
sight. Thankfully, this is easy to fix — and by third sight they'll
already be familiar to you.
With this table, it'll take you but a minute to figure out the
meaning of the multi-table functions you'll see next:
Function | Implying... | Operation |
---|---|---|
h-mix | Mixing the tables together | Union |
h-dif | Difference between tables | Difference |
h-cmn | Commonality between tables | Intersection |
Easy?
Let's see them.
Union
Functions that combine tables, either destructively or side-effects-free.
Note that:
- An '*' means it works with nesting; its absence means only first level.
- A '!' means destructive: it changes the original table; its absence
means that a new table is created, free of side-effects to the original. When input has multiple tables, the first one is 'updated' with the
others, which are processed from left to right. When not destructive,
a shallow copy of the first is updated.
So you can always think of it as: values of table1 updated with those of
table2; the result updated with those of table3; the result updated with
those of table4; and so on. The difference is that when there's a '!'
table1 will be itself modified; otherwise not.
- Functions for side-effect ('!') return nil; otherwise, the (1st) table.
The destructive h-mix! is similar to ht library's ht-update!
, whereas
the side-effect-free h-mix is similar to ht-merge
.
(table &rest from-tables)
Update TABLE according to every key–value pair in FROM-TABLES.
FROM-TABLES are processed from left to right, and
only the first table will be modified — not the others.
So (h-mix! table1 table2 table3 table4) will:
- modify table1 with the data from table2; then
- modify table1 with the data from table3; then
- modify table1 with the data from table4.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-mix.
This function only works at "the first level" — no nesting.
Its also-destructive nesting-aware counterpart is h-mix*!.
For nesting-awareness but no side-effects, use h-mix*.
;;;; works like ht's ht-update! — not aware of nesting (let* ((h1 (ht (:z 'a) (:y (ht (:x 4) (:b 2))))) (h2 (ht (:y (ht (:b 3)))))) (ht-update! h1 h2) h1) H=> (h* :z 'a :y (h* :b 3)) ;<-- all of it replaced rather than merged (let* ((h1 (ht (:z 'a) (:y (ht (:x 4) (:b 2))))) (h2 (ht (:y (ht (:b 3)))))) (h-mix! h1 h2) h1) H=> (h* :z 'a :y (h* :b 3)) ;<-- all of it replaced rather than merged ;;;; typical (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2)) (t3 (h* :e 50 :f 6))) (h-mix! t1 t2 t3) t1) H=> (h* :a 1 :b 2 :e 50 :f 6) (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix! t1 t2) t1) H=> (h* :a 1 :b 2 :e 5) (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix! t1 t2) t2) H=> (h* :e 5 :b 2) ;; t2 is intact (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b (h* :c 30 :f 6)))) (h-mix! t1 t2) t1) ;; note: no :d 4 -- value replaced at level 2 H=> (h* :a 1 :e 5 :b (h* :c 30 :f 6)) ;;;; some base cases (let ((t1 (h* :a 1)) (t2 (ht))) (h-mix! t1 t2) t1) H=> (h* :a 1) (let ((t1 (ht)) (t2 (h* :a 1))) (h-mix! t1 t2) t1) H=> (h* :a 1) (let ((t1 (ht)) (t2 (ht))) (h-mix! t1 t2) t1) H=> (ht)
(table &rest from-tables)
Return a table that has all key–value pairs from the tables.
The tables (TABLE plus FROM-TABLES) are processed from left to
right, and no table is modified.
So (h-mix table1 table2 table3 table4) will:
- create a clone of table1 (let's call it table0)
- modify table0 with the data from table2; then
- modify table0 with the data from table3; then
- modify table0 with the data from table4;
- return table0.
Tables 1 to 4 haven't changed.
This is a side-effect-free function.
Its destructive counterpart is h-mix!.
This function is not nesting-aware.
Its also-side-effect-free but nesting-aware counterpart is h-mix*.
For nesting-awareness with side-effects, use h-mix*!.
;;;; works like ht's ht-merge — not aware of nesting (let* ((h1 (ht (:z 'a) (:y (ht (:x 4) (:b 2))))) (h2 (ht (:y (ht (:b 3)))))) (ht-merge h1 h2)) H=> (h* :z 'a :y (h* :b 3)) ;<-- all of it replaced rather than merged (let* ((h1 (ht (:z 'a) (:y (ht (:x 4) (:b 2))))) (h2 (ht (:y (ht (:b 3)))))) (h-mix h1 h2)) H=> (h* :z 'a :y (h* :b 3)) ;<-- all of it replaced rather than merged ;;;; typical (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2)) (t3 (h* :e 50 :f 6))) (h-mix t1 t2 t3)) H=> (h* :a 1 :b 2 :e 50 :f 6) (let* ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix t1 t2)) H=> (h* :a 1 :b 2 :e 5) (let* ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix t1 t2) t1) H=> (h* :a 1 :b (h* :c 3 :d 4)) ;; t1 is intact (let* ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix t1 t2) t2) H=> (h* :e 5 :b 2) ;; t2 is intact (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b (h* :c 30 :f 6)))) (h-mix t1 t2)) ;; note: no :d 4 -- value replaced at level 2 H=> (h* :a 1 :e 5 :b (h* :c 30 :f 6)) ;;;; some base cases (let ((t1 (h* :a 1)) (t2 (ht))) (h-mix t1 t2)) H=> (h* :a 1) (let ((t1 (ht)) (t2 (h* :a 1))) (h-mix t1 t2)) H=> (h* :a 1) (let ((t1 (ht)) (t2 (ht))) (h-mix t1 t2)) H=> (ht)
(table &rest from-tables)
Update TABLE by adding every key–value pair in FROM-TABLES.
Unlike h-mix! and ht-update!
, it updates nested levels:
so if both values are themselves hash tables, the new value
will be the h-mix*! of them.
FROM-TABLES are processed from left to right, and
only the first table will be modified — not the others.
So (h-mix! table1 table2 table3 table4) will:
- modify table1 with the data from table2; then
- modify table1 with the data from table3; then
- modify table1 with the data from table4.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-mix*.
This function is nesting-aware.
Its also-destructive but only-first-level counterpart is h-mix!.
For no nesting-awareness and no side-effects, use h-mix.
;;;; Unlike ht-update! or h-mix!, it's nesting-aware (let* ((h1 (ht (:z 'a) (:y (ht (:x 4) (:b 2))))) (h2 (ht (:y (ht (:b 3)))))) (h-mix*! h1 h2) h1) H=> (h* :z 'a :y (h* :x 4 :b 3)) ;<-- merged and updated ;;;; typical (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b (h* :c 30 :f 6))) (t3 (h* :e 50 :g 7))) (h-mix*! t1 t2 t3) t1) H=> (h* :a 1 :e 50 :g 7 :b (h* :c 30 :d 4 :f 6)) (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix*! t1 t2) t1) H=> (h* :a 1 :b 2 :e 5) (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix*! t1 t2) t2) H=> (h* :e 5 :b 2) ;; t2 is intact (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b (h* :c 30 :f 6)))) (h-mix*! t1 t2) t1) ;;all mixed! H=> (h* :a 1 :e 5 :b (h* :c 30 :d 4 :f 6)) (let ((t1 (h* :a 1 :g 7 :b (h* :c 3 :d 4))) (t2 (h* :a 10 :e 5 :b (h* :c 30 :f 6)))) (h-mix*! t1 t2) t1) ;;all mixed! H=> (h* :a 10 :e 5 :g 7 :b (h* :c 30 :d 4 :f 6)) ;;;; some base cases (let ((t1 (h* :a 1)) (t2 (ht))) (h-mix*! t1 t2) t1) H=> (h* :a 1) (let ((t1 (ht)) (t2 (h* :a 1))) (h-mix*! t1 t2) t1) H=> (h* :a 1) (let ((t1 (ht)) (t2 (ht))) (h-mix*! t1 t2) t1) H=> (ht)
(table &rest from-tables)
Return a table that has all key–value pairs from the tables.
Unlike h-mix and ht-merge
, it updates nested levels:
so if both values are themselves hash tables, the new value
will be the h-mix* of them.
The tables (TABLE plus FROM-TABLES) are processed from left to
right, and no table is modified.
So (h-mix* table1 table2 table3 table4) will:
- create a clone of table1 (let's call it table0)
- modify table0 with the data from table2; then
- modify table0 with the data from table3; then
- modify table0 with the data from table4;
- return table0.
Tables 1 to 4 haven't changed.
This is a side-effect-free function.
Its destructive counterpart is h-mix*!.
This function is nesting-aware.
Its also-side-effect-free but only-first-level counterpart is h-mix.
For no nesting-awareness but with side-effects, use h-mix!.
;;;; Unlike ht-merge or h-mix, it's nesting-aware (let* ((h1 (ht (:z 'a) (:y (ht (:x 4) (:b 2))))) (h2 (ht (:y (ht (:b 3)))))) (h-mix* h1 h2)) H=> (h* :z 'a :y (h* :x 4 :b 3)) ;<-- merged and updated ;;;; typical (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b (h* :c 30 :f 6))) (t3 (h* :e 50 :g 7))) (h-mix* t1 t2 t3)) H=> (h* :a 1 :e 50 :g 7 :b (h* :c 30 :d 4 :f 6)) (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix* t1 t2)) H=> (h* :a 1 :b 2 :e 5) (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b 2))) (h-mix* t1 t2) t2) H=> (h* :e 5 :b 2) ;; t2 is intact (let ((t1 (h* :a 1 :b (h* :c 3 :d 4))) (t2 (h* :e 5 :b (h* :c 30 :f 6)))) (h-mix* t1 t2)) ;;all mixed! H=> (h* :a 1 :e 5 :b (h* :c 30 :d 4 :f 6)) (let ((t1 (h* :a 1 :g 7 :b (h* :c 3 :d 4))) (t2 (h* :a 10 :e 5 :b (h* :c 30 :f 6)))) (h-mix* t1 t2)) ;;all mixed! H=> (h* :a 10 :e 5 :g 7 :b (h* :c 30 :d 4 :f 6)) ;;;; some base cases (let ((t1 (h* :a 1)) (t2 (ht))) (h-mix* t1 t2)) H=> (h* :a 1) (let ((t1 (ht)) (t2 (h* :a 1))) (h-mix* t1 t2)) H=> (h* :a 1) (let ((t1 (ht)) (t2 (ht))) (h-mix* t1 t2)) H=> (ht)
Difference
Functions that subtract tables, either destructively or side-effects-free.
(table &rest from-tables)
Update TABLE by removing all matching key–value pairs in FROM-TABLES.
That is: the removal only happens when both the key and the value
in one of the tables in FROM-TABLES matches that found in TABLE.
If only the key is matched, there's no removal.
FROM-TABLES are processed from left to right, and
only the first table will be modified — not the others.
So (h-dif! table1 table2 table3 table4) will:
- modify table1 with the data from table2; then
- modify table1 with the data from table3; then
- modify table1 with the data from table4.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-dif.
This function only works at "the first level" — no nesting.
Its also-destructive nesting-aware counterpart is h-dif*!.
For nesting-awareness but no side-effects, use h-dif*.
;;;; won't work here — this is for h-dif*! (let ((tbl1 (h* :a (h* :b (h* :c 3 :e 5 :d 4) :g 7 :h 8))) (tbl2 (h* :a (h* :b (h* :c 3) :g 6))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :h 8)))) (h-dif! tbl1 tbl2 tbl3) tbl1) H=> (h* :a (h* :b (h* :c 3 :e 5 :d 4) :g 7 :h 8)) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-dif! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :c 3 :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-dif! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :c 3 :d 4))) ;;;; some base cases (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-dif! tbl1 tbl2) tbl1) H=> (ht) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-dif! tbl1 tbl2) tbl1) H=> (h* :a 1) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-dif! tbl1 tbl2) tbl1) H=> (h* :c 3) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 2))) (h-dif! tbl1 tbl2) tbl1) H=> (h* :a 1 :c 3) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-dif! tbl1 tbl2) tbl1) H=> (ht)
(table &rest from-tables)
Return a table of all key–value pairs in TABLE not in FROM-TABLES.
That is: all pairs in TABLE appear in the results, except those that also
appear in one of the tables in FROM-TABLES. If only the key is matched,
there's no removal.
FROM-TABLES are processed from left to right, and no table is modified.
So (h-dif table1 table2 table3 table4) will:
- create a clone of table1 (let's call it table0)
- modify table0 with the data from table2; then
- modify table0 with the data from table3; then
- modify table0 with the data from table4;
- return table0.
Tables 1 to 4 haven't changed.
This is a side-effect-free function.
Its destructive counterpart is h-dif!.
This function is not nesting-aware.
Its also-side-effect-free but nesting-aware counterpart is h-dif*.
For nesting-awareness with side-effects, use h-dif*!.
;;;; won't work here — this is for h-dif* (let ((tbl1 (h* :a (h* :b (h* :c 3 :e 5 :d 4) :g 7 :h 8))) (tbl2 (h* :a (h* :b (h* :c 3) :g 6))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :h 8)))) (h-dif tbl1 tbl2 tbl3)) H=> (h* :a (h* :b (h* :c 3 :e 5 :d 4) :g 7 :h 8)) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-dif tbl1 tbl2)) H=> (h* :a (h* :b (h* :c 3 :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-dif tbl1 tbl2)) H=> (h* :a (h* :b (h* :c 3 :d 4))) ;;;; some base cases (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-dif tbl1 tbl2)) H=> (ht) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-dif tbl1 tbl2)) H=> (h* :a 1) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-dif tbl1 tbl2)) H=> (h* :c 3) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 2))) (h-dif tbl1 tbl2)) H=> (h* :a 1 :c 3) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-dif tbl1 tbl2)) H=> (ht)
(table &rest from-tables)
Update TABLE by removing all matching key–value pairs in FROM-TABLES.
That is: the removal only happens when both the key and the value
in one of the tables in FROM-TABLES matches that found in TABLE.
If only the key is matched, there's no removal.
Unlike h-dif!, it updates nested levels:
so if both values are themselves hash tables, the new value
will be the h-dif*! of them.
FROM-TABLES are processed from left to right, and
only the first table will be modified — not the others.
So (h-dif! table1 table2 table3 table4) will:
- modify table1 with the data from table2; then
- modify table1 with the data from table3; then
- modify table1 with the data from table4.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-dif*.
This function is nesting-aware.
Its also-destructive but only-first-level counterpart is h-dif!.
For no nesting-awareness and no side-effects, use h-dif.
;;;; typical (let ((tbl1 (h* :a (h* :b (h* :c 3 :e 5 :d 4) :g 7 :h 8))) (tbl2 (h* :a (h* :b (h* :c 3) :g 6))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :h 8)))) (h-dif*! tbl1 tbl2 tbl3) tbl1) H=> (h* :a (h* :b (h* :e 5) :g 7)) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-dif*! tbl1 tbl2) tbl1) H=> (ht) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-dif*! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-dif*! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :c 3 :d 4))) ;;;; some base cases (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-dif*! tbl1 tbl2) tbl1) H=> (h* :a 1) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-dif*! tbl1 tbl2) tbl1) H=> (h* :c 3) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 2))) (h-dif*! tbl1 tbl2) tbl1) H=> (h* :a 1 :c 3) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-dif*! tbl1 tbl2) tbl1) H=> (ht)
(table &rest from-tables)
Return a table of all key–value pairs in TABLE not in FROM-TABLES.
That is: all pairs in TABLE appear in the results, except those that also
appear in one of the tables in FROM-TABLES. If only the key is matched,
there's no removal.
Unlike h-dif, it updates nested levels: so if both values are
themselves hash tables, the new value will be the h-dif* of them.
FROM-TABLES are processed from left to right, and no table is modified.
So (h-dif* table1 table2 table3 table4) will:
- create a clone of table1 (let's call it table0)
- modify table0 with the data from table2; then
- modify table0 with the data from table3; then
- modify table0 with the data from table4;
- return table0.
Tables 1 to 4 haven't changed.
This is a side-effect-free function.
Its destructive counterpart is h-dif*!.
This function is nesting-aware.
Its also-side-effect-free but only-first-level counterpart is h-dif.
For no nesting-awareness but with side-effects, use h-dif!.
;;;; typical (let ((tbl1 (h* :a (h* :b (h* :c 3 :e 5 :d 4) :g 7 :h 8))) (tbl2 (h* :a (h* :b (h* :c 3) :g 6))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :h 8)))) (h-dif* tbl1 tbl2 tbl3)) H=> (h* :a (h* :b (h* :e 5) :g 7)) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-dif* tbl1 tbl2)) H=> (ht) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-dif* tbl1 tbl2)) H=> (h* :a (h* :b (h* :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-dif* tbl1 tbl2)) H=> (h* :a (h* :b (h* :c 3 :d 4))) ;;;; some base cases (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-dif* tbl1 tbl2)) H=> (h* :a 1) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-dif* tbl1 tbl2)) H=> (h* :c 3) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 2))) (h-dif* tbl1 tbl2)) H=> (h* :a 1 :c 3) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-dif* tbl1 tbl2)) H=> (ht)
Intersection
Functions that build a table with the pairs common to all tables, either
destructively or side-effects-free.
cmn as in: 'common' to all tables.
(table &rest from-tables)
Update TABLE by keeping only the intersection with FROM-TABLES.
That is: the key is kept only when the pair is found in all TABLES.
If only the key is matched, not kept.
FROM-TABLES are processed from left to right, and
only the first table will be modified — not the others.
So (h-cmn! table1 table2 table3 table4) will:
- modify table1 with the data from table2; then
- modify table1 with the data from table3; then
- modify table1 with the data from table4.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-cmn.
This function only works at "the first level" — no nesting.
Its also-destructive nesting-aware counterpart is h-cmn*!.
For nesting-awareness but no side-effects, use h-cmn*.
;;;; won't work here — this is for h-cmn*! (let ((tbl1 (h* :a (h* :b (h* :c 3) :f 6 :g 7 :i 9))) (tbl2 (h* :a (h* :b (h* :c 3) :f 6 :g 7))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :f 6 :i 9)))) (h-cmn! tbl1 tbl2 tbl3) tbl1) H=> (h*) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-cmn! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :c 3 :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-cmn! tbl1 tbl2) tbl1) H=> (h*) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-cmn! tbl1 tbl2) tbl1) H=> (h*) ;;;; some base cases (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-cmn! tbl1 tbl2) tbl1) H=> (h*) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-cmn! tbl1 tbl2) tbl1) H=> (h* :a 1) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-cmn! tbl1 tbl2) tbl1) H=> (h* :a 1)
(table &rest from-tables)
Return a table of all key–value pairs in TABLE also in FROM-TABLES.
That is: no pairs in TABLE appear in the results, except those that
appear in all the tables in FROM-TABLES. If only the key is
matched, there's no removal.
FROM-TABLES are processed from left to right, and no table is modified.
So (h-cmn table1 table2 table3 table4) will:
- create a clone of table1 (let's call it table0)
- modify table0 with the data from table2; then
- modify table0 with the data from table3; then
- modify table0 with the data from table4;
- return table0.
Tables 1 to 4 haven't changed.
This is a side-effect-free function.
Its destructive counterpart is h-cmn!.
This function is not nesting-aware.
Its also-side-effect-free but nesting-aware counterpart is h-cmn*.
For nesting-awareness with side-effects, use h-cmn*!.
;;;; won't work here — this is for h-cmn* (let ((tbl1 (h* :a (h* :b (h* :c 3) :f 6 :g 7 :i 9))) (tbl2 (h* :a (h* :b (h* :c 3) :f 6 :g 7))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :f 6 :i 9)))) (h-cmn tbl1 tbl2 tbl3)) H=> (h*) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-cmn tbl1 tbl2)) H=> (h* :a (h* :b (h* :c 3 :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-cmn tbl1 tbl2)) H=> (h*) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-cmn tbl1 tbl2)) H=> (h*) ;;;; some base cases (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-cmn tbl1 tbl2)) H=> (h*) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-cmn tbl1 tbl2)) H=> (h* :a 1) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-cmn tbl1 tbl2)) H=> (h* :a 1)
(table &rest from-tables)
Update TABLE by keeping only the intersection with FROM-TABLES.
That is: the key is kept only when the pair is found in all TABLES.
If only the key is matched, not kept.
Unlike h-cmn!, it updates nested levels:
so if both values are themselves hash tables, the new value
will be the h-cmn*! of them.
FROM-TABLES are processed from left to right, and
only the first table will be modified — not the others.
So (h-cmn! table1 table2 table3 table4) will:
- modify table1 with the data from table2; then
- modify table1 with the data from table3; then
- modify table1 with the data from table4.
This function returns nil.
This is a destructive function.
Its side-effect-free counterpart is h-cmn*.
This function is nesting-aware.
Its also-destructive but only-first-level counterpart is h-cmn!.
For no nesting-awareness and no side-effects, use h-cmn.
;;;; typical (let ((tbl1 (h* :a (h* :b (h* :c 3) :f 6 :g 7 :i 9))) (tbl2 (h* :a (h* :b (h* :c 3) :f 6 :g 7))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :f 6 :i 9)))) (h-cmn*! tbl1 tbl2 tbl3) tbl1) H=> (h* :a (h* :b (h*) :f 6)) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-cmn*! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :c 3 :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-cmn*! tbl1 tbl2) tbl1) H=> (h* :a (h* :b (h* :c 3))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-cmn*! tbl1 tbl2) tbl1) H=> (h* :a (h*)) ;;;; some base cases (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-cmn*! tbl1 tbl2) tbl1) H=> (h*) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-cmn*! tbl1 tbl2) tbl1) H=> (h* :a 1) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-cmn*! tbl1 tbl2) tbl1) H=> (h* :a 1)
(table &rest from-tables)
Return a table of all key–value pairs in TABLE also in FROM-TABLES.
That is: no pairs in TABLE appear in the results, except those that
appear in all the tables in FROM-TABLES. If only the key is
matched, there's no removal.
Unlike h-cmn, it updates nested levels: so if both values are
themselves hash tables, the new value will be the h-cmn* of them.
FROM-TABLES are processed from left to right, and no table is modified.
So (h-cmn* table1 table2 table3 table4) will:
- create a clone of table1 (let's call it table0)
- modify table0 with the data from table2; then
- modify table0 with the data from table3; then
- modify table0 with the data from table4;
- return table0.
Tables 1 to 4 haven't changed.
This is a side-effect-free function.
Its destructive counterpart is h-cmn*!.
This function is nesting-aware.
Its also-side-effect-free but only-first-level counterpart is h-cmn.
For no nesting-awareness but with side-effects, use h-cmn!.
;;;; typical (let ((tbl1 (h* :a (h* :b (h* :c 3) :f 6 :g 7 :i 9))) (tbl2 (h* :a (h* :b (h* :c 3) :f 6 :g 7))) (tbl3 (h* :a (h* :b (h* :d 4 :e 1) :f 6 :i 9)))) (h-cmn* tbl1 tbl2 tbl3)) H=> (h* :a (h* :b (h*) :f 6)) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3 :d 4))))) (h-cmn* tbl1 tbl2)) H=> (h* :a (h* :b (h* :c 3 :d 4))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b (h* :c 3))))) (h-cmn* tbl1 tbl2)) H=> (h* :a (h* :b (h* :c 3))) (let ((tbl1 (h* :a (h* :b (h* :c 3 :d 4)))) (tbl2 (h* :a (h* :b 2)))) (h-cmn* tbl1 tbl2)) H=> (h* :a (h*)) ;;;; some base cases (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 2 :c 3))) (h-cmn* tbl1 tbl2)) H=> (h*) (let ((tbl1 (h* :a 1 :c 3)) (tbl2 (h* :a 1))) (h-cmn* tbl1 tbl2)) H=> (h* :a 1) (let ((tbl1 (h* :a 1)) (tbl2 (h* :a 1))) (h-cmn* tbl1 tbl2)) H=> (h* :a 1)
Comparison / Equality / Equivalence
Not needed.
Vectors, lists, and strings can be compared with equal
.
Examples:
(equal '(a x) '(a x)) ;=> t (equal [a x] [a x]) ;=> t (equal "a\nx\n" "a\nx\n") ;=> t
And they can only be equal to exactly themselves.
Also note that we'll keep these two (one of which with extra
trailing newline) not being equal:
(equal "a\nx\n" "a\nx") ;=> nil
Functions that compare if two or more objects are equal.
Key–value pairs are of dimension 1: not nested.
No need for a function to compare cons cells: equal will do:
(equal '(a . 1) '(a . 1)) ;=> t
(kvl1 kvl2 &rest more-kvls)
Return t only if all KEY–VALUE LINES have same KEY–VALUE pairs.
It accepts any number ≥2 of KEY–VALUE LINES as input.
(The arguments KVL1, KVL2, and MORE-KVLS are key–value lines.)
;;;; Identical (h-kvl= "Mary = 27\nJohn = 20\nAnne = 21" "Mary = 27\nJohn = 20\nAnne = 21") => t ;;;; Different row order (h-kvl= "Mary = 27\nJohn = 20\nAnne = 21" "Mary = 27\nAnne = 21\nJohn = 20") => t (h-kvl= "\ Mary = 27 John = 20 Anne = 21" "\ Mary = 27 Anne = 21 John = 20") => t ;;;; More than two (h-kvl= "Mary = 27\nJohn = 20\nAnne = 21" "Mary = 27\nJohn = 20\nAnne = 21" "Mary = 27\nAnne = 21\nJohn = 20") => t ;;;; Empty lines (h-kvl= "\ Mary = 27 John = 20 Anne = 21" "\ Mary = 27\n Anne = 21\n\n John = 20\n\n\n") => t (h-kvl= "\ Mary = 27 Anne = 21 John = 20 " "\ Mary = 27 John = 20 Anne = 21") => t ;;;; Repeated keys (h-kvl= "\ Mary = 27 John = 20 Anne = 21 Anne = 42 John = 31" "\ Mary = 27 Anne = 42 John = 31") => t ;;;; Different spacing (h-kvl= "\ Mary Lee = 27 John = 20 Ann = 21" "\ Mary Lee = 27 Ann = 21 John = 20" "\ Mary Lee = 27 Ann = 21 John = 20") => t ;;;; Leading spaces (h-kvl= "\ Mary Lee = 27 John = 20 Ann = 21" "\ Mary Lee = 27 Ann = 21 John = 20") => t ;;;; Same but non-default field delimiter (let ((h-kvl-sep-re " *: *")) (h-kvl= "\ Mary Lee : 27 John : 20 Ann : 21" "\ Mary Lee:27 Ann:21 John:20")) => t
Functions that compare if two or more hash tables are equivalent.
Key–value pairs are of unspecified dimension >1 (nested).
Many distinctions here, so a summary of what the operators mean
follows. These tests return t if:
Operator | Condition for returning a truthy value |
---|---|
h== | tables have same keys in THE SAME order, and the values: |
- when both are hash tables, are also h==. | |
- when both are of some different type, they are equal . |
|
h-pr== | same as the previous, but stricter: properties* |
also match: same size, test function, etc. | |
h= | tables have same keys in ANY order, and the values: |
- when both are hash tables, are also h=. | |
- when both are of some different type, they are equal . |
|
h-pr= | same as the previous, but stricter: properties* |
also match: same size, test function, etc. | |
h_= | tables have same keys in ANY order, and the values: |
- are equal , OR: |
|
-- when both are hash tables, are also h_=. | |
-- when both are of some different type, they return t | |
when tested with their respective equality function; | |
so, for example, if they're both alists, comparison | |
of them using h-alist= returns t; when both TSVs. | |
comparison of them using h-tsv= returns t; etc. | |
h~= | tables have same keys in ANY order, and the values: |
- are equal , OR: |
|
-- when both are hash tables, are also h~=. | |
-- when both are of some different type, they | |
return t when tested with h-it~, which in | |
turn means that they can even be of different | |
types but, when converted to a hash table, | |
become h~=. |
So there's a gradation of strictness of equivalence, where:
and:
And so h~= is the most permissive, where equivalences of each
item are themselves deeply tested.
The h== one may be useful, for example, for deciding whether
two hash tables would return the exact same results when mapping or
converting.
We could say that h= is to h-pr= as equal
is to
equal-including-properties
.
When in doubt, h= is likely the best choice, because:
- It accepts different ordering of keys inside the tables. Order is
not supposed to matter in hash tables, so this is most likely
what you want: being stricter with h== or any of the -pr
would deem unequal tables that for most regular purposes are
equivalent. - When nested, it applies the same logic to any hash tables stored
in it as values, ignoring order: suffices that their keys are the
same and that the values are eitherequal
or h=. - Any non–hash-table values are tested with
equal
, so you don't
run the risk of false equivalence of lists that were not intended
as plists (and therefore sensitive to ordering and to repeated
items), and other such oddities.
(table1 table2 &rest more-tables)
One of the ways to see if all hash tables TABLES are equivalent.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | YES |
Compare keys order? | YES |
Compare non–hash-table values with… | equal |
When the values are hash tables, they are compared with
this very function, recursively.
For more details, see the helper function xht--equal?
, as well as
the section heading description in the code source.
See all: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h-pr== (h-new 65 'equal) (h-new 5 'eq)) => nil (h-pr== (h* :a 1 :b 2 :c 3) (h* :b 2 :c 3 :a 1)) => nil (h-pr== (h* :a (h* :b 2 :c 3)) (h* :a (h* :b 2 :c 3))) => t ;;;; A single test that illustrates all hash table equality functions (let* ((h11 (h-new 65 'equal)) (h12 (h-new 5 'eq)) (h21 (h* :a 1 :b 2 :c 3)) (h22 (h* :b 2 :c 3 :a 1)) (h23 (h* :c 3 :a 1 :b 2)) (h31 (h* :a (h* :b 2 :c 3))) (h32 (h* :a (h* :b 2 :c 3))) (h33 (h* :a (h* :c 3 :b 2))) (h41 (h* :a 1 :b '(:c 3 :d 4))) (h42 (h* :a 1 :b '(:d 4 :c 3))) (h43 (h* :a 1 :b '(:d 4 :c 3 :c 5))) (h44 (h* :a 1 :b '((:c . 3) (:d . 4)))) (h51 (h* "a" "1" "b" '("c" "3" "d" "4" "c" "18"))) (h52 (h* "a" "1" "b" (h* "d" "4" "c" "3"))) (h61 (h* :a 1 :b '(:c 3 :d ((:e . 5) (:f . 6))))) (h62 (h* :a 1 :b '(:c 3 :d (:e 5 :f 6 :e 2)))) (h63 (h* :a 1 :b `(:c 3 :d ,(h* :e 5 :f 6)))) (otb7 "| id | name | age | |------+-------+-----| | id01 | Alice | 42 | | id02 | Bob | 30 | | id03 | Emily | 21 |") (lol7 '(("id" "name" "age") ("id01" "Alice" "42") ("id02" "Bob" "30") ("id03" "Emily" "21"))) (htb7 (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21"))) (h71 (h* :a 1 :b otb7)) (h72 (h* :b lol7 :a 1)) (h73 (h* :b htb7 :a 1)) (h81 (h* :a 1 :b `(:c ,otb7))) (h82 (h* :b `((:c . ,lol7)) :a 1)) (h83 (h* :a 1 :b (h* :c htb7))) (combos ; expected: -pr== | -pr= | == | = | _= | ~= | `((,h11 ,h12 ,h11 ) ; nil | nil | t | t | t | t | (,h21 ,h22 ,h23 ) ; nil | t | nil | t | t | t | (,h31 ,h32 ) ; t | t | t | t | t | t | (,h31 ,h32 ,h33 ) ; nil | t | nil | t | t | t | (,h41 ,h42 ,h43 ) ; nil | nil | nil | nil | t | t | (,h41 ,h42 ,h43 ,h44) ; nil | nil | nil | nil | nil | t | (,h51 ,h52 ) ; nil | nil | nil | nil | nil | t | (,h61 ,h62 ,h63 ) ; nil | nil | nil | nil | nil | nil | (,h71 ,h72 ,h73 ) ; nil | nil | nil | nil | nil | t | (,h81 ,h82 ,h83 ))) ; nil | nil | nil | nil | nil | nil | (funs '(h-pr== h-pr= h== h= h_= h~=))) (-map (lambda (combo) (-map (lambda (fun) (apply fun combo)) funs)) combos)) => '((nil nil t t t t) (nil t nil t t t) (t t t t t t) (nil t nil t t t) (nil nil nil nil t t) (nil nil nil nil nil t) (nil nil nil nil nil t) (nil nil nil nil nil nil) (nil nil nil nil nil t) (nil nil nil nil nil nil))
(table1 table2 &rest more-tables)
One of the ways to see if all hash tables TABLES are equivalent.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | YES |
Compare keys order? | NO |
Compare non–hash-table values with… | equal |
When the values are hash tables, they are compared with this very
function, recursively.
For more details, see the helper function xht--equal?
, as well as
the section heading description in the code source.
See all: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h-pr= (h-new 65 'equal) (h-new 5 'eq)) => nil (h-pr= (h* :a 1 :b 2 :c 3) (h* :b 2 :c 3 :a 1)) => t (h-pr= (h* :a (h* :b 2 :c 3)) (h* :a (h* :b 2 :c 3))) => t ;;;; 2 hash tables — "equal" ;;;;; regular (h-pr= (ht (:a 1)) (let ((H (make-hash-table))) (puthash :a 1 H) H)) => nil ; equal≠eql (h-pr= (ht ("a" 1)) (let ((H (ht-create))) (ht-set! H "a" 1) H)) => t (h-pr= (ht ("a" 1)) (ht ("a" 1))) => t (h-pr= (ht ("a" 1)("b" 2)) (ht ("a" 1)("b" 2))) => t (h-pr= (h* "a" 1 "b" 2) (h* "a" 1 "b" 2)) => t ;;;;; nested (h-pr= (h* "a" (h* "b" 2)) (h* "a" (h* "b" 2))) => t (h-pr= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b (h* :c 3 :d 4)))) => t (h-pr= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b 2))) => nil (h-pr= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b 2))) => nil ;;;; 3 hash tables — "equal" (h-pr= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1))) => t (h-pr= (h* "a" 1) (h* "a" 1) (h* "a" 1)) => t ;;;; 4 hash tables — "equal" (h-pr= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1))) => t (h-pr= (ht (:a 1)) (ht (:a 1)) (ht (:a 1)) (ht (:a 1))) => t (h-pr= (ht ('a 1)) (ht ('a 1)) (ht ('a 1)) (ht ('a 1))) => t ;;;; 2 hash tables — "different" (h-pr= (ht ("a" 1)) (ht (:a 1))) => nil (h-pr= (ht (:a 1)) (ht ("a" 1))) => nil (h-pr= (ht (:a 1)) (ht ('a 1))) => nil (h-pr= (ht ("a" 1)) (ht ("b" 1))) => nil (h-pr= (ht ("a" 1)) (ht ("a" 2))) => nil (h-pr= (ht ("a" 1)) (ht ("a" 1) ("b" 1))) => nil ;;;; 2+ hash tables — "different": equivalence of plist etc NOT tested (h-pr= (h* :a '(:b 2 :c 3)) (h* :a '(:c 3 :b 2)) (h* :a '(:b 2 :c 3 :b 22))) => nil (h-pr= (h* :a '(:b 2 :c 3)) (h* :a '(:b 2 :c 3 :b 22))) => nil (h-pr= (h* :a '((:b . 2) (:c . 3))) (h* :a '((:c . 3) (:b . 2)))) => nil ;; different order, repeated (updated) entry (h-pr= (h* :a "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |") (h* :a "| id | name | age | |----+-------+-----| | e | emily | 21 | | b | bob | 30 | | e | emily | 12 |")) => nil ;;;; 3 hash tables — "different" (h-pr= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 2))) => nil (h-pr= (ht ("a" 1)) (ht ("a" 2)) (ht ("a" 1))) => nil (h-pr= (ht ("a" 2)) (ht ("a" 1)) (ht ("a" 1))) => nil ;; <2 hash tables — error (h-pr= (ht (:a 1))) !!> wrong-number-of-arguments (h-pr=) !!> wrong-number-of-arguments ;;;; Comparing properties (h-pr= (ht) (h*) (h*)) => nil (h-pr= (ht (:a 1)) (h* :a 1)) => nil (h-pr= (ht-create) (h-new)) => t ;; different properties detected, plists "sameness" not detected. (h-pr= (ht (:a '(:b 2 :c 3))) (h* :a '(:c 3 :b 2))) => nil
(table1 table2 &rest more-tables)
One of the ways to see if all hash tables TABLES are equivalent.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | NO |
Compare keys order? | YES |
Compare non–hash-table values with… | equal |
When the values are hash tables, they are compared with
this very function, recursively.
For more details, see the helper function xht--equal?
, as well as
the section heading description in the code source.
See all: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h== (h-new 65 'equal) (h-new 5 'eq)) => t (h== (h* :a 1 :b 2 :c 3) (h* :b 2 :c 3 :a 1)) => nil (h== (h* :a (h* :b 2 :c 3)) (h* :a (h* :b 2 :c 3))) => t
(table1 table2 &rest more-tables)
One of the ways to see if all hash tables TABLES are equivalent.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | NO |
Compare keys order? | NO |
Compare non–hash-table values with… | equal |
When the values are hash tables, they are compared with this very
function, recursively.
When in doubt, use this one.
This function can be thought of as a variadic version of
ht-equal?
: it accepts any number ≥2 of tables as input.
Moreover, it returns t when comparing two indistinguishable nested
hash tables (of same key–value pairs), for which ht-equal?
until
v2.4 returns nil:
(ht-equal? (ht (:a 1)) (ht (:a 1))) ;=> t (ht-equal? (ht (:a (ht (:b 2)))) (ht (:a (ht (:b 2))))) ;=> nil !!
For more details, see the helper function xht--equal?
, as well as
the section heading description in the code source.
See all: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h= (h-new 65 'equal) (h-new 5 'eq)) => t (h= (h* :a 1 :b 2 :c 3) (h* :b 2 :c 3 :a 1)) => t (h= (h* :a (h* :b 2 :c 3)) (h* :a (h* :b 2 :c 3))) => t (h= (h* :a 1 :b '(:c 3 :d 4)) (h* :a 1 :b '(:d 4 :c 3)) (h* :a 1 :b '(:d 4 :c 3 :c 5))) => nil (h= (h* "a" "1" "b" '("c" "3" "d" "4" "c" "18")) (h* "a" "1" "b" (h* "d" "4" "c" "3"))) => nil (h= (h* :a 1 :b '(:c 3 :d ((:e . 5) (:f . 6)))) (h* :a 1 :b '(:c 3 :d (:e 5 :f 6 :e 2))) (h* :a 1 :b `(:c 3 :d ,(h* :e 5 :f 6)))) => nil ;;;; 2 hash tables — "equal" ;;;;; regular (h= (ht (:a 1)) (let ((H (make-hash-table))) (puthash :a 1 H) H)) => t (h= (ht ("a" 1)) (let ((H (ht-create))) (ht-set! H "a" 1) H)) => t (h= (ht ("a" 1)) (ht ("a" 1))) => t (h= (ht ("a" 1)("b" 2)) (ht ("a" 1)("b" 2))) => t (h= (h* "a" 1 "b" 2) (h* "a" 1 "b" 2)) => t ;;;;; nested (h= (h* "a" (h* "b" 2)) (h* "a" (h* "b" 2))) => t (h= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b (h* :c 3 :d 4)))) => t (h= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b 2))) => nil (h= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b 2))) => nil ;;;; 3 hash tables — "equal" (h= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1))) => t (h= (h* "a" 1) (h* "a" 1) (h* "a" 1)) => t ;;;; 4 hash tables — "equal" (h= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1))) => t (h= (ht (:a 1)) (ht (:a 1)) (ht (:a 1)) (ht (:a 1))) => t (h= (ht ('a 1)) (ht ('a 1)) (ht ('a 1)) (ht ('a 1))) => t ;;;; 2 hash tables — "different" (h= (ht ("a" 1)) (ht (:a 1))) => nil (h= (ht (:a 1)) (ht ("a" 1))) => nil (h= (ht (:a 1)) (ht ('a 1))) => nil (h= (ht ("a" 1)) (ht ("b" 1))) => nil (h= (ht ("a" 1)) (ht ("a" 2))) => nil (h= (ht ("a" 1)) (ht ("a" 1) ("b" 1))) => nil ;;;; 2+ hash tables — "different": equivalence of plist etc NOT tested (h= (h* :a '(:b 2 :c 3)) (h* :a '(:c 3 :b 2)) (h* :a '(:b 2 :c 3 :b 22))) => nil (h= (h* :a '(:b 2 :c 3)) (h* :a '(:b 2 :c 3 :b 22))) => nil (h= (h* :a '((:b . 2) (:c . 3))) (h* :a '((:c . 3) (:b . 2)))) => nil ;; different order, repeated (updated) entry (h= (h* :a "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |") (h* :a "| id | name | age | |----+-------+-----| | e | emily | 21 | | b | bob | 30 | | e | emily | 12 |")) => nil ;;;; 3 hash tables — "different" (h= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 2))) => nil (h= (ht ("a" 1)) (ht ("a" 2)) (ht ("a" 1))) => nil (h= (ht ("a" 2)) (ht ("a" 1)) (ht ("a" 1))) => nil ;; <2 hash tables — error (h= (ht (:a 1))) !!> wrong-number-of-arguments (h=) !!> wrong-number-of-arguments ;;;; Comparing properties (h= (ht) (h*) (h*)) => t (h= (ht (:a 1)) (h* :a 1)) => t (h= (ht-create) (h-new)) => t ;; different properties not detected, plists "sameness" not detected. (h= (ht (:a '(:b 2 :c 3))) (h* :a '(:c 3 :b 2))) => nil
(table1 table2 &rest more-tables)
One of the ways to see if all hash tables TABLES are equivalent.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | NO |
Compare keys order? | NO |
Compare non–hash-table values with… | h-it= |
When the values are hash tables, they are compared with this very
function, recursively.
For more details, see the helper function xht--equal?
, as well as
the section heading description in the code source.
See all: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h_= (h* :a 1 :b '(:c 3 :d 4)) (h* :a 1 :b '(:d 4 :c 3)) (h* :a 1 :b '(:d 4 :c 3 :c 5))) => t (h_= (h* "a" "1" "b" '("c" "3" "d" "4" "c" "18")) (h* "a" "1" "b" (h* "d" "4" "c" "3"))) => nil (h_= (h* :a 1 :b '(:c 3 :d ((:e . 5) (:f . 6)))) (h* :a 1 :b '(:c 3 :d (:e 5 :f 6 :e 2))) (h* :a 1 :b `(:c 3 :d ,(h* :e 5 :f 6)))) => nil ;;;; 2 hash tables — "equal" ;;;;; regular (h_= (ht (:a 1)) (let ((H (make-hash-table))) (puthash :a 1 H) H)) => t (h_= (ht ("a" 1)) (let ((H (ht-create))) (ht-set! H "a" 1) H)) => t (h_= (ht ("a" 1)) (ht ("a" 1))) => t (h_= (ht ("a" 1)("b" 2)) (ht ("a" 1)("b" 2))) => t (h_= (h* "a" 1 "b" 2) (h* "a" 1 "b" 2)) => t ;;;;; nested (h_= (h* "a" (h* "b" 2)) (h* "a" (h* "b" 2))) => t (h_= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b (h* :c 3 :d 4)))) => t (h_= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b 2))) => nil (h_= (h* :a (h* :b (h* :c 3 :d 4))) (h* :a (h* :b 2))) => nil ;;;; 3 hash tables — "equal" (h_= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1))) => t (h_= (h* "a" 1) (h* "a" 1) (h* "a" 1)) => t ;;;; 4 hash tables — "equal" (h_= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 1))) => t (h_= (ht (:a 1)) (ht (:a 1)) (ht (:a 1)) (ht (:a 1))) => t (h_= (ht ('a 1)) (ht ('a 1)) (ht ('a 1)) (ht ('a 1))) => t ;;;; 2 hash tables — "SAME" (h_= (ht ("a" 1)) (ht (:a 1))) => nil (h_= (ht (:a 1)) (ht ("a" 1))) => nil (h_= (ht (:a 1)) (ht ('a 1))) => nil (h_= (ht ("a" 1)) (ht ("b" 1))) => nil (h_= (ht ("a" 1)) (ht ("a" 2))) => nil (h_= (ht ("a" 1)) (ht ("a" 1) ("b" 1))) => nil ;;;; 2+ hash tables — "SAME": equivalence of plist etc IS tested (h_= (h* :a '(:b 2 :c 3)) (h* :a '(:c 3 :b 2)) (h* :a '(:b 2 :c 3 :b 22))) => t (h_= (h* :a '(:b 2 :c 3)) (h* :a '(:c 3 :b 2))) => t (h_= (h* :a '(:b 2 :c 3)) (h* :a '(:b 2 :c 3 :b 22))) => t (h_= (h* :a '((:b . 2) (:c . 3))) (h* :a '((:c . 3) (:b . 2)))) => t ;; different order, repeated (updated) entry (h_= (h* :a "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |") (h* :a "| id | name | age | |----+-------+-----| | e | emily | 21 | | b | bob | 30 | | e | emily | 12 |")) => t ;;;; 3 hash tables — "different" (h_= (ht ("a" 1)) (ht ("a" 1)) (ht ("a" 2))) => nil (h_= (ht ("a" 1)) (ht ("a" 2)) (ht ("a" 1))) => nil (h_= (ht ("a" 2)) (ht ("a" 1)) (ht ("a" 1))) => nil ;; <2 hash tables — error (h_= (ht (:a 1))) !!> wrong-number-of-arguments (h_=) !!> wrong-number-of-arguments ;;;; Comparing properties (h_= (ht) (h*) (h*)) => t (h_= (ht (:a 1)) (h* :a 1)) => t (h_= (ht-create) (h-new)) => t ;; different properties not detected, plists "sameness" detected. (h_= (ht (:a '(:b 2 :c 3))) (h* :a '(:c 3 :b 2))) => t
(table1 table2 &rest more-tables)
One of the ways to see if all hash tables TABLES are equivalent.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | NO |
Compare keys order? | NO |
Compare non–hash-table values with… | h-it~ |
When the values are hash tables, they are compared with this very
function, recursively.
For more details, see the helper function xht--equal?
, as well as
the section heading description in the code source.
See all: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h~= (h* :a 1 :b '(:c 3 :d 4)) (h* :a 1 :b '(:d 4 :c 3)) (h* :a 1 :b '(:d 4 :c 3 :c 5))) => t (h~= (h* "a" "1" "b" '("c" "3" "d" "4" "c" "18")) (h* "a" "1" "b" (h* "d" "4" "c" "3"))) => t (h~= (h* :a 1 :b '(:c 3 :d ((:e . 5) (:f . 6)))) (h* :a 1 :b '(:c 3 :d (:e 5 :f 6 :e 2))) (h* :a 1 :b `(:c 3 :d ,(h* :e 5 :f 6)))) => nil
(table1 table2 &rest more-tables)
Whether all hash tables TABLES have the same properties.
Note: but not necessarily the same key–value pairs, just the properties.
(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.)
Compare properties? | YES |
Compare data? | NO |
See also: h-pr==, h-pr=, h==, h=, h_=, h~=.
(h-props= (h* :a 1) (h* :b 2) (h* :c 2)) => t (h-props= (h* :a 1) (h* :b 2)) => t ;; different nominal size: 1≠2 (h-props= (h* :a 1) (h* :a 1 :b 2)) => nil ;; different nominal size: 1≠65 (h-props= (ht (:a 1)) (h* :a 1)) => nil ;; same nominal size: 65 (h-props= (ht (:a 1)) (ht (:a 1) (:b 2))) => t (h-props= (ht-create) (h-new)) => t
Functions that compare if two or more alists, plists, or json objects are
equivalent. Key–value pairs are of unspecified dimension >1 (nested).
(alist1 alist2 &rest more-alists)
Return t only if all ALISTs have same KEY–VALUE pairs.
It accepts any number ≥2 of ALISTs as input.
(The arguments ALIST1, ALIST2, and MORE-ALISTS are association lists.)
See h-lol= for more information on equivalence, which also
applies here.
'(("b" ("c" . 3)) ("a" . 1) ("a" . 4)) A=> '(("a" . 1) ("b" ("c" . 3))) '(("a" . 1)("b" . 2)) A=> '(("b" . 2)("a" . 1)) '(("a" . 1)("b" . 2)) A=> '(("b" . 2)("a" . 1)("a" . 4)) '(("a" . 1)) A=> '(("a" . 1))
(plist1 plist2 &rest more-plists)
Return t only if all PLISTs have same KEY–VALUE pairs.
It accepts any number ≥2 of PLISTs as input.
(The arguments PLIST1, PLIST2, and MORE-PLISTS are property lists.)
See h-lol= for more information on equivalence, which also
applies here.
'("a" 1 "b" ("c" 3)) P=> '("b" ("c" 3) "a" 1) '("a" 1 "b" 2) P=> '("b" 2 "a" 1) '("a" 1 "b" 2) P=> '("b" 2 "a" 1 "a" 4) '("a" 1) P=> '("a" 1)
(json1 json2 &rest more-jsons)
Return t only if all JSONs have same KEY–VALUE pairs.
It accepts any number ≥2 of JSONs as input.
(The arguments JSON1, JSON2, and MORE-JSONS are JSONs.)
See h-lol= for more information on equivalence, which also
applies here.
(h-json= "{ \"a\": 1, \"b\": { \"c\": 3 } }" "{ \"b\": { \"c\": 3 }, \"a\": 1 }") => t (h-json= "{ \"a\": 1, \"b\": 2 }" "{ \"b\": 2, \"a\": 1 }") => t (h-json= "{\n \"a\": 1\n \n}" "{\n \"a\": 1\n \n}" => t)
Functions that compare if two or more objects are equal.
Key–value pairs are of dimension 2: tabular.
(lol1 lol2 &rest more-lols)
Return t only if all LISTS OF LISTS have same KEY–VALUE pairs.
It accepts any number ≥2 of LISTS OF LISTS as input.
(The arguments LOL1, LOL2, and MORE-LOLS are lists of lists.)
Note that regular lists and vectors are the same if they have the
same elements at the same indices, so they can be compared with
plain equal
.
Two LISTS OF LISTS, however, may, for our purposes, be equal while
being nominally different.
For example, these two:
'((:id :name :age) '((:id :name :age) (1 alice 42) (2 bob 30) (2 bob 30) (1 alice 42)) (1 alice 21))
As with alists and plists, the top-most element of same ID wins,
and the order between different items doesn't matter. Each unique
ID returns the same information. The same applied when we convert
from Org Table, TSV, CSV, SSV, or JSON.
;; Row order '(("ID" "NAME" "AGE") ("id03" "Anne" "21") ("id02" "John" "20") ("id01" "Mary" "27")) L=> '(("ID" "NAME" "AGE") ("id01" "Mary" "27") ("id02" "John" "20") ("id03" "Anne" "21")) ;; Repeated keys '(("ID" "NAME" "AGE") ("id02" "John" "20") ("id01" "Mary" "27")) L=> '(("ID" "NAME" "AGE") ("id02" "John" "20") ("id01" "Mary" "27") ("id01" "Mary" "21"))
(orgtbl1 orgtbl2 &rest more-orgtbls)
Return t only if all ORG TABLES have same KEY–VALUE pairs.
It accepts any number ≥2 of ORG TABLES as input.
(The arguments ORGTBL1, ORGTBL2, and MORE-ORGTBLS are org tables.)
See h-lol= for more information on equivalence, which also
applies here.
;; Row order "| ID | NAME | AGE | |------+------+-----| | id03 | Anne | 21 | | id02 | John | 20 | | id01 | Mary | 27 |" O=> "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 | | id03 | Anne | 21 |" ;; Repeated keys "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 |" O=> "| ID | NAME | AGE | |------+------+-----| | id02 | John | 20 | | id01 | Mary | 27 | | id01 | Mary | 21 |" ;; Unformatted "| ID | NAME | AGE |------ | id01 | Mary | 27 | id02 | John | 20 |" O=> "| ID | NAME | AGE | |------+------+-----| | id02 | John | 20 | | id01 | Mary | 27 | | id01 | Mary | 21 |"
(tsv1 tsv2 &rest more-tsvs)
Return t only if all TSVs have same KEY–VALUE pairs.
It accepts any number ≥2 of TSVs as input.
(The arguments TSV1, TSV2, and MORE-TSVS are tab-separated values.)
See h-lol= for more information on equivalence, which also
applies here.
;; Row order "ID\tNAME\tAGE\nid03\tAnne\t21\nid02\tJohn\t20\nid01\tMary\t27" T=> "ID\tNAME\tAGE\nid01\tMary\t27\nid02\tJohn\t20\nid03\tAnne\t21" ;; Repeated keys "ID\tNAME\tAGE\nid01\tMary\t27\nid02\tJohn\t20" T=> "ID\tNAME\tAGE\nid02\tJohn\t20\nid01\tMary\t27"
(csv1 csv2 &rest more-csvs)
Return t only if all CSVs have same KEY–VALUE pairs.
It accepts any number ≥2 of CSVs as input.
(The arguments CSV1, CSV2, and MORE-CSVS are comma-separated values.)
See h-lol= for more information on equivalence, which also
applies here.
;; Row order "ID,NAME,AGE\nid03,Anne,21\nid02,John,20\nid01,Mary,27" C=> "ID,NAME,AGE\nid01,Mary,27\nid02,John,20\nid03,Anne,21" ;; Repeated keys "ID,NAME,AGE\nid01,Mary,27\nid02,John,20" C=> "ID,NAME,AGE\nid02,John,20\nid01,Mary,27"
(ssv1 ssv2 &rest more-ssvs)
Return t only if all SSVs have same KEY–VALUE pairs.
It accepts any number ≥2 of SSVs as input.
(The arguments SSV1, SSV2, and MORE-SSVS are space-separated values.)
See h-lol= for more information on equivalence, which also
applies here.
"ID NAME AGE\nid03 Anne 21\nid02 John 20\nid01 Mary 27" S=> "ID NAME AGE\nid01 Mary 27\nid02 John 20\nid03 Anne 21" ;; Repeated keys "ID NAME AGE\nid01 Mary 27\nid02 John 20" S=> "ID NAME AGE\nid02 John 20\nid01 Mary 27"
Functions that compare if two or more objects are equal.
Objects are unspecified.
(obj)
Detect object OBJ's type-for-conversion. Return a keyword.
This isn't the same as type-of
; it's more concerned with
types of interest for conversions and lookups.
So, for example, it'd return :tsv for a TSV, while type-of
would return 'string.
Testing is done in a selected order to best guess some potentially
ambiguous types by trying to match the most specific of them first.
- So LOL is tested for before alist and plist, which are also
tested before list. Moreover, if, while testing, the value of
h-kvl-sep-re
matches
TAB, then h-kvl? could return t if all lines have a single TAB.
Since by default this variable matches " *= *", it'll be
interpreted that it's being let-bound, and that the OBJ is a KVL
rather than a two-column TSV. Likewise in case the value of
h-kvl-sep-re
matches a comma (preference over CSV) or simply
two or more spaces (preference over SSV).
It's worth reminding that the difference between a two-column *sv
and a KVL of matching delimiter is that the latter has no headers
(one-dimensional: just key and value), whereas the former has a
header (two-dimensional: key, field, and value)
(h-type (ht ("a" 1)("b" 2))) => :ht (h-type '("a" . 1)) => :cons-pair (h-type '("a" 1 :b 2)) => :plist (h-type [1 2 3]) => :vector (h-type '((:id :name) ("01" "Alice"))) => :lol (h-type "id,name\n01,Alice") => :csv
(obj1 obj2 &rest more-objs)
Are all OBJECTS of same type?
For example, are they all hash tables? Or all org tables?
Or all lists of lists? Or all alists? Or all TSVs? JSONs?
(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.)
Return type.
This isn't the same as type-of
; it's more concerned with
types of interest for this conversion and lookup.
So, for example, it'd return :tsv for a TSV, while type-of
would return 'string.
(h-type= '("a" . 1) '("a" 1 :b 2)) => nil (h-type= (ht ("a" 1)("b" 2)) (h* :c 10)) => :ht (h-type= "id,name\n01,Alice" "id\tname\n01\tAlice") => nil
(obj1 obj2 &rest more-objs)
Are all OBJECTS, all of same type, reducible to each other?
Try to guess what they are.
(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.)
The following tests are applied, and if any of them return t for
every pair of objects, they are considered same-type equals:
equal
h-tsv= h-json=
h= h-csv= h-alist=
h-lol= h-ssv= h-plist=
h-orgtbl=.
If so, return type. Otherwise, return nil.
(h-it= '("a" . 1) '("a" 1 :b 2)) => nil (h-it= (ht ("a" 1)("b" 2)) (h* :c 10)) => nil (h-it= "id,name\n01,Alice" "id\tname\n01\tAlice") => nil (h-it= '(:a 1) '((:a . 1) (:a . 2))) => nil (h-it= '(:a 1) '(:a 1 :a 2)) => :plist (h-it= (ht ("a" 1)("b" 2)) (h* "a" 1 "b" 2)) => :ht (h-it= (h* :a (h* :b (h* :d 4 :c 3))) (h* :a (h* :b (h* :c 3 :d 4)))) => :ht (h-it= (h* :a (h* :b (h* :c 3 :d 4 :c 5))) (h* :a (h* :b (h* :c 5 :d 4)))) => :ht (h-it= '((:a (:b (:d . 4) (:c . 5)))) '((:a (:b (:c . 5) (:d . 4) (:c . 3))))) => :alist (h-it= (h* :a (h* :b (h* :c 3 :d 4 :c 5))) (h* :a (h* :b (h* :c 5 :d 4)))) => :ht ;; Must be of same type (h-it= (h* "a" (h* "b" (h* "c" 3 "d" 4))) (h->json* (h* "a" (h* "b" (h* "c" 3 "d" 4))))) => nil
(obj1 obj2 &rest more-objs)
Are all OBJECTS equivalent according to XHT tests?
They are if, and only if, either they are equal or, when converted
to a hash table, h~= returns t for the pair. This means that
repeated keys (in alists, plists, TSVs etc) are dealt with, and
that, after that, ordering is irrelevant.
(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.)
(h-it~ '("a" . 1) '("a" 1 :b 2)) => nil (h-it~ (ht ("a" 1)("b" 2)) (h* :c 10)) => nil (h-it~ "id,name\n01,Alice" "id\tname\n01\tAlice") => t (h-it~ '(:a 1) '((:a . 1) (:a . 2))) => t (h-it~ '(:a 1) '(:a 1 :a 2)) => t (h-it~ (ht ("a" 1)("b" 2)) (h* "a" 1 "b" 2)) => t (h-it~ (h* :a (h* :b (h* :d 4 :c 3))) (h* :a (h* :b (h* :c 3 :d 4)))) => t (h-it~ (h* :a (h* :b (h* :c 3 :d 4 :c 5))) (h* :a (h* :b (h* :c 5 :d 4)))) => t (h-it~ '((:a (:b (:d . 4) (:c . 5)))) '((:a (:b (:c . 5) (:d . 4) (:c . 3))))) => t (h-it~ (h* :a (h* :b (h* :c 3 :d 4 :c 5))) (h* :a (h* :b (h* :c 5 :d 4)))) => t ;; Different types (h-it~ (h* "a" (h* "b" (h* "c" 3 "d" 4))) (h->json* (h* "a" (h* "b" (h* "c" 3 "d" 4))))) => t (h-it~ (h* "a" (h* "b" (h* "c" 3 "d" 4))) "{ \"a\": { \"b\": { \"c\": 3, \"d\": 4 } } }") => t
Conversion
Functions to convert from/to hash tables.
To hash tables:
h<-kvl h<-lines
h<-vector h<-list
h<-lol h<-orgtbl
h<-alist h<-plist h<-alist* h<-plist* h<-json*
h<-tsv h<-csv
h<-ssv
From hash tables:
h->kvl h->lines
h->vector h->list
h->lol h->orgtbl
h->alist h->plist h->alist* h->plist* h->json*
h->tsv h->csv h->ssv
You can navigate functions easily with:
M-. and M-, (elisp-slime-nav-mode)
Note that when results on both sides evaluate to hash tables, we use
for equality comparison the H=>
symbol (which uses h=).
For getting (alist-get
, plist-get
, h-get
, etc.):
alist, plist: | Top-most values override bottom-most values |
lol, org-table, *sv: | Top-most values override bottom-most values |
kvl: | Bottom-most values override top-most values |
vector, list, lines: | Uniqueness guaranteed by index |
Their nature:
kvl: | no nesting, keys are given |
lines: | no nesting, keys = line number (starting at 0) |
vector, list: | may be nested¹, keys = indices |
alist, plist, json: | may be nested², keys are given |
lol, org-table, *sv: | tabular, assumed no nesting (no lols inside lols) |
¹ treatment of nested not implemented for vectors and lists.
² treatment of nested implemented for alists, plists, and jsons —
both ways.
Hash table to hash table
Functions that read a hash table and return a hash table.
For shallow copying, h-copy
is offered as an alias to
copy-hash-table
. However, this function is not at all appropriate
for dealing with nested hash tables. See h-clone* for more.
(table &optional sz ts we rs rt)
Create a deep copy of possibly-nested hash table TABLE.
Optional arguments after TABLE are SZ, TS, WE, RS, and RT — which
correspond to, respectively, size, test, weakness, rehash-size,
rehash-threshold. When nil, they match that of TABLE.
The result has the same elements and structure of TABLE, but any
nested (internal) hash tables are recursively replaced by new ones.
This allows you to modify the internal hash tables without altering
the original one.
This is not possible with copy-hash-table
, whose copy is shallow
and any internal tables that might be present in the copy will
point to the very same objects of the original one. Likewise with
ht-copy
, which uses copy-hash-table
, and h-copy
, which is an
alias to the latter.
You also shouldn't use shallow copying if you intend to apply
destructive functions such as sort
, nconc
, or nreverse
to any
list values of the copied table, as this will likely modify the
original. To avoid that, either use non-destructive alternatives
such as -sort
and reverse
; or h-clone* the original instead
of using h-copy
.
See also: h<-ht.
;;;; Like h-copy (-> (h-new 10 'eq) (h-put :a 1) (h-put :b 2) h-clone*) Hp==> (h-st* 10 'eq :a 1 :b 2) (-> (h-new 10 'eq) (h-put :a 1) (h-put* :b :c 3) h-clone*) Hp==> (h-st* 10 'eq :a 1 :b (h* :c 3)) ;;;; Unlike h-copy, it does not modify the original's nested ones ;;;;; Compare: (let* ((tbl (h-st* 10 'eq :a 1 :b (h* :c 3))) (new (h-copy tbl))) (h-put*! new :a 2) (h-put*! new :b :c 5) (h-put*! new :b :d 4) tbl) Hp==> (h-st* 10 'eq :a 1 :b (h* :c 5 :d 4)) ;<-- the original's nested is modified! (let* ((tbl (h-st* 10 'eq :a 1 :b (h* :c 3))) (new (h-clone* tbl))) (h-put*! new :a 2) (h-put*! new :b :c 5) (h-put*! new :b :d 4) tbl) Hp==> (h-st* 10 'eq :a 1 :b (h* :c 3)) ;<-- the original's nested is preserved ;;;;; I didn't use h-put* above because h-put* is non-destructive: (let* ((tbl (h-st* 10 'eq :a 1 :b (h* :c 3))) (new (h-copy tbl))) (-> new (h-put* :b :d 4) (h-put* :b :c 5))) Hp==> (h-st* 10 'eq :a 1 :b (h* :c 5 :d 4)) ;<-- new one from the operation, but... (let* ((tbl (h-st* 10 'eq :a 1 :b (h* :c 3))) (new (h-copy tbl))) (-> new (h-put* :b :d 4) (h-put* :b :c 5)) tbl) Hp==> (h-st* 10 'eq :a 1 :b (h* :c 3)) ;<-- ... the original is preserved ;;;;; In fact we don't need h-copy with h-put or h-put*. ;;;;; It uses h-clone* behind the scenes — so it'd h-clone* tbl, ;;;;; which would stay unmodified. (let* ((tbl (h-st* 10 'eq :a 1 :b (h* :c 3)))) (-> tbl (h-put* :b :d 4) (h-put* :b :c 5)) tbl) Hp==> (h-st* 10 'eq :a 1 :b (h* :c 3))
(table &optional sz ts we rs rt)
Given hash table TABLE, return it or a clone, depending on params.
Optional arguments after TABLE are SZ, TS, WE, RS, and RT — which
correspond to, respectively, size, test, weakness, rehash-size,
rehash-threshold. When nil, they match that of TABLE.
If any of the optional arguments passed differs from the TABLE's
corresponding properties, h-clone* the table with these
parameters. Otherwise just return TABLE.
This is a non-destructive function: TABLE isn't changed.
See also: h-clone*.
(h<-ht (ht-create 'eq) nil 'equal) Hp=> (h-new nil 'equal) (h<-ht (ht-create 'eq) 20 'equal) Hp=> (h-new 20 'equal) (h<-ht (ht (:a 1) (:b 2))) Hp=> (ht (:a 1) (:b 2)) (h<-ht (h* :a 1 :b 2)) Hp=> (h* :a 1 :b 2) (h<-ht (ht)) Hp=> (ht-create)
(table &optional f1 f2 size test)
Make 2D the 1D hash table TABLE by adding header fields F1, F2.
If F1 is nil, use the string "key". If F2 is nil, use "value".
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
Alias: h-1d->2d
.
(h-2d<-1d (ht (0 "alice") (1 "bob"))) H=> (ht (0 (ht ("key" 0) ("value" "alice"))) (1 (ht ("key" 1) ("value" "bob")))) (h-2d<-1d (ht (0 "alice") (1 "bob")) :idx :name) H=> (ht (0 (ht (:idx 0) (:name "alice"))) (1 (ht (:idx 1) (:name "bob"))))
(table2d &optional size test)
Make 1D (KVL-like) the 2D hash table TABLE2D.
Infer current header with h-2d-header.
If header isn't of length 2, signal error.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
Alias: h-2d->1d
.
(h-1d<-2d (ht (0 (ht ("key" 0) ("value" "alice"))) (1 (ht ("key" 1) ("value" "bob"))))) H=> (ht (0 "alice") (1 "bob")) (h-1d<-2d (ht (0 "alice") (1 "bob"))) !!> error (h-1d<-2d (ht (0 "alice"))) !!> error (h-1d<-2d (ht-create)) !!> error
1D: implicit (indices as keys)
Functions in this category convert from other formats to hash table or
vice-versa. Key–value pairs are of dimension 1: not nested. Indices serve as
keys.
(vector &optional size test)
Convert vector VECTOR to a hash table using VECTOR's indices as keys.
Keys are by default of integer type, not strings, and start at 0.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
(h<-vector ["alice" "bob"]) H=> (h* 0 "alice" 1 "bob") (h<-vector []) H=> (ht)
(table)
Convert hash table TABLE to vector.
Do the opposite of h<-vector, which see. Return vector.
Select only those keys that are natural numbers (an integer ≥0) or
strings that could be converted to a natural number, which it
automatically does.
Any holes in the number sequence from 0 to max(keys) are filled
in the vector with nil.
If the table has keys that reduce to the same integer, such as
having both 3 and "3" as keys, behavior is unpredictable.
(h->vector (h* 1 "alice" 5 "emily")) => [nil "alice" nil nil nil "emily"] (h->vector (h* 'x "bob" 0 "alice" :y "emily")) => ["alice"] (h->vector (h* -1 "alice" 1 "bob")) => [nil "bob"] (h->vector (h* "1" "alice" "2" "bob")) => [nil "alice" "bob"] (h->vector (h* 1 "alice" "2" "bob")) => [nil "alice" "bob"] (h->vector (h* "1" "alice" 2 "bob")) => [nil "alice" "bob"] (h->vector (h* 1 "alice" 2 "bob")) => [nil "alice" "bob"] (h->vector (h* "0" "alice" "1" "bob")) => ["alice" "bob"] (h->vector (h* 0 "alice" "1" "bob")) => ["alice" "bob"] (h->vector (h* "0" "alice" 1 "bob")) => ["alice" "bob"] (h->vector (h* 0 "alice" 1 "bob")) => ["alice" "bob"] (h->vector (ht (0 "alice"))) => ["alice"] (h->vector (ht)) => [] (h->vector (ht-create)) => []
(list &optional size test)
Convert LIST to a hash table using LIST's indices as keys.
Keys are by default of integer type, not strings, and start at 0.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
(h<-list '()) H=> (ht) (h<-list '("alice" "bob")) H=> (h* 0 "alice" 1 "bob")
(table)
Convert hash table TABLE to list.
Select only those keys that are natural numbers (an integer ≥0)
or strings that could be converted to a natural number, which it
automatically does.
Any holes in the number sequence from 0 to max(keys) are filled
in the list with nil.
If the table has keys that reduce to the same integer, such as
having both 3 and "3" as keys, behavior is unpredictable.
(h->list (h* 1 "alice" 5 "emily")) => '(nil "alice" nil nil nil "emily") (h->list (h* 'x "bob" 0 "alice" :y "emily")) => '("alice") (h->list (h* "1" "alice" "2" "bob")) => '(nil "alice" "bob") (h->list (h* 1 "alice" "2" "bob")) => '(nil "alice" "bob") (h->list (h* "1" "alice" 2 "bob")) => '(nil "alice" "bob") (h->list (h* 1 "alice" 2 "bob")) => '(nil "alice" "bob") (h->list (h* "0" "alice" "1" "bob")) => '("alice" "bob") (h->list (h* 0 "alice" "1" "bob")) => '("alice" "bob") (h->list (h* "0" "alice" 1 "bob")) => '("alice" "bob") (h->list (h* 0 "alice" 1 "bob")) => '("alice" "bob") (h->list (ht (0 "alice"))) => '("alice") (h->list (ht)) => '() (h->list (ht-create)) => '()
(str &optional size test)
Convert string STR to a hash table using line numbers as keys.
Keys are by default of integer type, not strings, and start at 0.
The choice of zero for the first line number (instead of 1) was due
to transitivity: a plain conversion from lines to hash table to
vector to lines should yield the initial string. The alternatives
seemed worse.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
(h<-lines "alice\nbob") H=> (h* 0 "alice" 1 "bob") (h<-lines " \n \n") H=> (h* 0 " " 1 " " 2 "") (h<-lines "") H=> (h* 0 "") (h<-lines "alice\nbob" 5) Hp=> (h-s* 5 0 "alice" 1 "bob") (h<-lines "" 5 'eq) Hp=> (h-st* 5 'eq 0 "") (h<-lines '(a b c)) !!> error (h<-lines nil) !!> error
(table)
Convert hash table TABLE whose keys are numbers to string.
Select only those keys that are natural numbers (an integer ≥0)
or strings that could be converted to a natural number, which it
automatically does.
Any holes in the number sequence from 0 to max(keys) are filled
in the string with empty lines.
If the table has keys that reduce to the same integer, such as
having both 3 and "3" as keys, behavior is unpredictable.
(h->lines (h* 1 "alice" 5 "emily")) => "\nalice\n\n\n\nemily" (h->lines (h* 'x "bob" 0 "alice" :y "emily")) => "alice" (h->lines (h* -1 "alice" 1 "bob")) => "\nbob" (h->lines (h* "1" "alice" "2" "bob")) => "\nalice\nbob" (h->lines (h* 1 "alice" "2" "bob")) => "\nalice\nbob" (h->lines (h* "1" "alice" 2 "bob")) => "\nalice\nbob" (h->lines (h* 1 "alice" 2 "bob")) => "\nalice\nbob" (h->lines (h* "0" "alice" "1" "bob")) => "alice\nbob" (h->lines (h* 0 "alice" "1" "bob")) => "alice\nbob" (h->lines (h* "0" "alice" 1 "bob")) => "alice\nbob" (h->lines (h* 0 "alice" 1 "bob")) => "alice\nbob" (h->lines (ht (0 "alice"))) => "alice" (h->lines (ht (0 ""))) => "" (h->lines (ht)) => "" (h->lines (h-new)) => ""
1D: explicit
Functions in this category convert from other formats to hash table or
vice-versa. Strings with one key–value pair per line are of dimension 1: not
nested. Likewise cons pairs.
(kvl &optional size test obsolete)
Convert key–value lines KVL to a hash table.
Optional arguments SIZE and TEST may be passed.
A KVL is the equivalent of a non-nested alist translated to a
simple flat config file as used in Unix-like systems.
It should look, for example, like this:
--- data begins --->
key1 = val1
key2 = val2
key3 = val3
<--- data ends -----
One pair per line. There is no header. If there were, it'd be taken
as another key–value line.
The field delimiter (separator) to be used is given by the value of
the regular expression in the variable h-kvl-sep-re
, which see.
By default it's equal sign surrounded or not by spaces. The
variable can be temporarily let-bound when change is desirable.
Comments are non-destructively stripped before conversion. Comments
are anything matching h-kvl-comment-re
(which see), which can
also be temporarily let-bound when some other comment regex is
expected in KVL.
Note that sections (as used in .ini files and some types of .conf)
aren't implemented or dealt with here. KVLs are to be considered
flat. So lines with [Section name] are not considered as nodes, and
no tree-like structure will be generated. These section lines, if
present, are ignored by the default h-kvl-section-re
(which see)
as if they were comments, since this may be useful if the file
you're processing has sections only for information purposes and
the keys inside them are unique. If this is not the case, then this
function is not suitable for this conversion. An option would be to
pre-convert the input to JSON using some other tool, and then apply
h<-json* to the result.
Also note that in an alist, when two elements have equal key, the
first is used. With KVL data, we'll consider that the last row
wins. This is because in an alist a new pair is pushed to the top,
whereas KVL files are usually (but not always, as there are varying
standards) read from top to bottom.
An OBSOLETE form of calling this function is
(h<-kvl kvl &optional sep size test)
where the optional arg SEP specified the field delimiter. This use
is deprecated. The delimiter is now passed by the variable
h-kvl-sep-re
, which should be let-bound when the default is not
the one expected in the KVL to be converted.
;;;; When expected separator is " *= *" (let ((kvl4 "Mary = 27\nJohn = 20\nAnne = 21\nAnne = 42\nJohn = 31\n")) (h<-kvl kvl4)) H=> (h* "Mary" "27" "John" "31" "Anne" "42") ;;;; When expected separator is different (let ((kvl4 "Mary 27\nJohn 20\nAnne 21\nAnne 42\nJohn 31\n") (h-kvl-sep-re " ")) (h<-kvl kvl4)) H=> (h* "Mary" "27" "John" "31" "Anne" "42") (let ((h-kvl-sep-re ":")) (h<-kvl "Alice:42\nBob:30")) H=> (h* "Alice" "42" "Bob" "30") (let ((h-kvl-sep-re " *: *")) (h<-kvl "\ Alice : 42 Bob : 30")) H=> (h* "Alice" "42" "Bob" "30") ;;;; Leading and trailing whitespace (h<-kvl " Alice = 42 Bob = 30 ") H=> (h* "Alice" "42" "Bob" "30") ;;;; Empty lines (h<-kvl " Alice = 42 Bob = 30 ") H=> (h* "Alice" "42" "Bob" "30") ;;;; Comments (h<-kvl "\ # Some people here Alice = 42 # Alice's real age! Bob = 30") H=> (h* "Alice" "42" "Bob" "30") ;;;; [Sections] in simple (flat) .conf files (h<-kvl "[People] Alice = 42 Bob = 30") H=> (h* "Alice" "42" "Bob" "30") ;;;; When expected section regex is different (let ((h-kvl-section-re "^[ \t]*{.*}[ \t]*$")) (h<-kvl "\ {People} Alice = 42 Bob = 30 Emily = 21")) H=> (h* "Alice" "42" "Bob" "30" "Emily" "21") ;;;; When expected comment regex is different (let ((h-kvl-comment-re " *;.*")) (h<-kvl "\ ;; Some people Alice = 42 ; Alice's real age! Bob = 30")) H=> (h* "Alice" "42" "Bob" "30") ;;;; Handling a rather unusual KVL (let ((h-kvl-sep-re " *~ *") (h-kvl-comment-re " *;.*") (h-kvl-section-re "^[ \t]*{.*}[ \t]*$")) (h<-kvl " {Some people} ;; We start with Alice: Alice ~ 42 ; Alice's real age! ;; Bob below: Bob ~ 30 ")) H=> (h* "Alice" "42" "Bob" "30") ;;;; Empty things create empty hash tables (h<-kvl "") Hp=> (h-new 1) (h<-kvl " ") Hp=> (h-new 1) (h<-kvl "\t") Hp=> (h-new 1) ;;;; Obsolete calling convention — DEPRECATED (with-no-warnings (let ((kvl4 "Mary 27\nJohn 20\nAnne 21\nAnne 42\nJohn 31\n")) (h<-kvl kvl4 " "))) H=> (h* "Mary" "27" "John" "31" "Anne" "42") (with-no-warnings (let ((kvl4 "Mary 27\nJohn 20\nAnne 21\nAnne 42\nJohn 31\n")) (h<-kvl kvl4 " " 5))) H=> (h* "Mary" "27" "John" "31" "Anne" "42") (with-no-warnings (let ((kvl4 "Mary 27\nJohn 20\nAnne 21\nAnne 42\nJohn 31\n")) (h<-kvl kvl4 " " 5 'equal))) H=> (ht ("Mary" "27") ("John" "31") ("Anne" "42"))
(table &optional sep)
Convert hash table TABLE to SEP-separated key–value lines.
The optional separator SEP is a literal string, and could be, for
example, " = " or ":" or " ". When nil, default to "=".
;;;; Regular (h->kvl (h* "Mary" "27" "John" "31" "Anne" "42")) => "Mary=27\nJohn=31\nAnne=42\n" ;; and KVLs with or without trailing \n are [[#h-kvl=][h-kvl=]] equivalent: (h-kvl= "Mary=27\nJohn=31\nAnne=42\n" "Mary=27\nJohn=31\nAnne=42") => t ;; so: (h->kvl (h* "Mary" "27" "John" "31" "Anne" "42")) K=> "Mary=27\nJohn=31\nAnne=42" ;;;; Empty hash table creates an empty string (h->kvl (h*)) => "" ;;;; Different separator (h->kvl (h* "Mary" "27" "John" "31" "Anne" "42") " : ") => "\ Mary : 27 John : 31 Anne : 42 " (h->kvl (ht ("Mary""27")("John""31")("Anne""42")) "\t") K=> "Mary\t27\nJohn\t31\nAnne\t42" ;;;; Result's type is string, regardless of origin's types (h->kvl (ht ('Mary 27) ('John 31) ('Anne 42)) " = ") => "Mary = 27\nJohn = 31\nAnne = 42\n" (h->kvl (ht ("Mary""27")("John""31")("Anne""42")) " = ") => "Mary = 27\nJohn = 31\nAnne = 42\n"
(cons-pair &optional size test)
Convert CONS-PAIR to hash table.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
(h<-cons-pair '("a" . 1)) H==> (h* "a" 1) (h<-cons-pair '(:a . 1)) H==> (h* :a 1) (h<-cons-pair '(a . 1)) H==> (h* 'a 1) (h<-cons-pair '(a . 1) 5 'eq) H==> (h-st* 5 'eq 'a 1) (h<-cons-pair '(a 1)) !!> error (h<-cons-pair '(a)) !!> error (h<-cons-pair '()) !!> error
(table)
Convert hash table TABLE to cons pair.
(h->cons-pair (h* "a" 1)) => '("a" . 1) (h->cons-pair (h* :a 1)) => '(:a . 1) (h->cons-pair (h* 'a 1)) => '(a . 1) (h->cons-pair (h* :a 1 :b 2)) !!> error (h->cons-pair (h*)) !!> error
≥1D unspecific (possibly nested)
Functions in this category convert from other formats to hash table or
vice-versa. Key–value pairs are of undetermined dimension >1 (nested).
- h<-alist
(alist &optional size test)
Convert ALIST to hash table.
Similar to
ht<-alist
, except that it preserves original order and
SIZE can also be passed as an argument. When SIZE is nil, it
defaults to the result of applyingxht--init-size
to half the
plist's length.
For the meaning of TEST, see h-new.
Like
ht<-alist
, this function is not aware of nested alists. Its
nesting-aware counterpart is h<-alist*.
;;;; Won't convert nested: values remain as alists (h<-alist '((a (b (c (d . 4)))))) H_=> (h* 'a '((b (c (d . 4))))) (h<-alist '((a . 1) (b (c . 3) (d . 4)) (e . 5) (f (g . 7) (h . 8)) (e . 2))) H_=> (h* 'a 1 'e 5 'b '((c . 3) (d . 4)) 'f '((g . 7) (h . 8))) (h<-alist '(("a" . 1) ("b" . (("c" . 3) ("d" . 4))))) H_=> #s(hash-table test equal data ("b" (("c" . 3) ("d" . 4)) "a" 1)) (h<-alist '(("a" . 1) ("b" . (("c" . 3) ("d" . 4))))) H_=> (ht<-alist '(("a" . 1) ("b" . (("c" . 3) ("d" . 4))))) (h<-alist '((a . 1) (b (c . 3)))) H_=> (h* 'a 1 'b '((c . 3))) ;;;; Basic (expected: as h<-alist) (h<-alist '((a . 1) (b . 2) (a . 3))) H=> (h* 'a 1 'b 2) (h<-alist '((a . 1) (b . 2))) H=> (h* 'a 1 'b 2) (h<-alist '((a . 1))) H=> (h* 'a 1) (h<-alist '((a . 1) (b))) H=> (h* 'a 1 'b nil) (h<-alist '()) H=> (h*) (h<-alist '(a)) !!> error (h<-alist [a 1]) !!> error (h<-alist '((a . 1) b)) !!> error (h<-alist '()) H=> (ht) (h<-alist '(())) H=> (ht (nil nil)) (h<-alist '(("a" . 1) ("b" . 2))) H=> #s(hash-table test equal data ("b" 2 "a" 1)) (h<-alist '(("a" . 1) ("b" . 2))) H=> (ht<-alist '(("a" . 1) ("b" . 2))) (h<-alist '(("a" . 1))) H=> (ht<-alist '(("a" . 1))) (h<-alist '(())) H=> (ht<-alist '(())) (h<-alist '()) H=> (ht<-alist '())
- h<-alist*
(alist &optional size test)
Convert possibly nested ALIST to hash table.
If the ALIST is nested, each alist found as value will be
recursively converted into a hash table as well.
SIZE is the nominal initial size of the table to be created.
When SIZE is nil, it defaults to the result of applying
xht--init-size
to the alist's length.
For the meaning of TEST, see h-new.
This function is aware of nested alists. Its non–nesting-aware
counterpart is h<-alist.
;;;; Typical: converts nested (h<-alist* '((a (b (c (d . 4)))))) H=> (h* 'a (h* 'b (h* 'c (h* 'd 4)))) (h<-alist* '((a . 1) (b (c . 3) (d . 4)) (e . 5) (f (g . 7) (h . 8)) (e . 2))) H=> (h* 'f (h* 'h 8 'g 7) 'b (h* 'd 4 'c 3) 'e 5 'a 1) (h<-alist* '(("a" . 1) ("b" . (("c" . 3) ("d" . 4))))) H=> #s(hash-table test equal data ("b" #s(hash-table test equal data ("d" 4 "c" 3)) "a" 1)) (h<-alist* '(("a" . 1) ("b" . (("c" . 3) ("d" . 4))))) H=> (ht<-alist `(("a" . 1) ("b" . ,(ht<-alist '(("c" . 3) ("d" . 4)))))) (h<-alist* '((a . 1) (b (c . 3)))) H=> (h* 'a 1 'b (h* 'c 3)) ;;;; Basic (expected: as h<-alist) (h<-alist* '((a . 1) (b . 2) (a . 3))) H=> (h* 'a 1 'b 2) (h<-alist* '((a . 1) (b . 2))) H=> (h* 'a 1 'b 2) (h<-alist* '((a . 1))) H=> (h* 'a 1) (h<-alist* '((a . 1) (b))) H=> (h* 'a 1 'b nil) (h<-alist* '()) H=> (h*) (h<-alist* '(a)) !!> error (h<-alist* '((a . 1) b)) !!> error (h<-alist* [a 1]) !!> error (h<-alist* '()) H=> (ht) (h<-alist* '(())) H=> (ht (nil nil)) (h<-alist* '(("a" . 1) ("b" . 2))) H=> #s(hash-table test equal data ("b" 2 "a" 1)) (h<-alist* '(("a" . 1) ("b" . 2))) H=> (ht<-alist '(("a" . 1) ("b" . 2))) (h<-alist* '(("a" . 1))) H=> (ht<-alist '(("a" . 1))) (h<-alist* '(())) H=> (ht<-alist '(())) (h<-alist* '()) H=> (ht<-alist '())
- h->alist
(table)
Convert hash table TABLE to alist.
Similar to current
ht->alist
, with the difference that it
preserves the hash table's order of keys.
Like
ht->alist
, this function is not aware of nested alists. Its
nesting-aware counterpart is h->alist*.
;;;; nested: doesn't recurse (h->alist (ht ("a" "1") ("b" (ht (:c 3) ('d "4"))))) A=> `(("a" . "1") ("b" . ,(ht (:c 3) ('d "4")))) (h->alist (h* 'id 1234 'payload (h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is..."))) A=> `((id . 1234) (payload . ,(h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is..."))) ;; this alist example ^ is from elisp-demos ;;;; not-nested (h->alist (ht ("a" 1) ("b" 2))) A=> '(("a" . 1) ("b" . 2)) (h->alist (h*)) => '() (h->alist (h-new)) => '()
- h->alist*
(table)
Convert hash table TABLE to alist, maybe recursing.
If the ALIST is nested, each alist found as value will be
recursively converted into a hash table as well.
This function is aware of nested alists. Its non–nesting-aware
counterpart is h->alist.
;;;; nested: recurses (h->alist* (ht ("a" "1") ("b" (ht (:c 3) ('d "4"))))) A=> '(("a" . "1") ("b" . ((:c . 3) (d . "4")))) (h->alist* (h* 'id 1234 'payload (h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is..."))) A=> '((id . 1234) (payload (url . "https://example.com") (title . "Example Domain") (content . "This domain is..."))) ;; this alist example ^ is from elisp-demos ;;;; not-nested (h->alist* (ht ("a" 1) ("b" 2))) A=> '(("a" . 1) ("b" . 2)) (h->alist* (h*)) => '() (h->alist* (h-new)) => '()
- h<-plist
(plist &optional size test)
Convert PLIST to hash table.
Similar to
ht<-plist
, except that it preserves original order and
SIZE can also be passed as an argument. When SIZE is nil, it
defaults to the result of applyingxht--init-size
to half the
plist's length.
For the meaning of TEST, see h-new.
This function is not aware of nested plists. Its nesting-aware
counterpart is h<-plist*.
;;;; Won't convert nested: values remain as plists (h<-plist '(:a (:b (:c (:d 4))))) H_=> (h* :a '(:b (:c (:d 4)))) (h<-plist '(:a 1 :b (:c 3 :d 4) :e 5 :f (:g 7 :h 8) :e 2)) H_=> (h* :f '(:h 8 :g 7) :b '(:d 4 :c 3) :e 5 :a 1) (h<-plist '(:a 1 :b (:c 3))) H_=> (h* :a 1 :b '(:c 3)) ;;;; Basic (h<-plist '(:a 1 :b 2 :c 3 :b 4)) H=> (h* :a 1 :b 2 :c 3) (h<-plist '(:a 1 :b 2 :a 3)) H=> (h* :a 1 :b 2) (h<-plist '(:a 1 :b 2)) H=> (h* :a 1 :b 2) (h<-plist '(:a 1)) H=> (h* :a 1) (h<-plist '()) H=> (h*) (h<-plist '(:a 1 :b)) H=> (h* :a 1) (h<-plist '(:a)) H=> (h*) (h<-plist [a 1]) !!> error
- h<-plist*
(plist &optional size test)
Convert possibly nested PLIST to hash table.
If the PLIST is nested, each plist found as value will be
recursively converted into a hash table as well.
SIZE is the nominal initial size of the table to be created.
When SIZE is nil, it defaults to the result of applying
xht--init-size
to half the plist's length.
For the meaning of TEST, see h-new.
This function is aware of nested plists. Its non–nesting-aware
counterpart is h<-plist.
;;;; Typical: converts nested (h<-plist* '(:a (:b (:c (:d 4))))) H=> (h* :a (h* :b (h* :c (h* :d 4)))) (h<-plist* '(:a 1 :b (:c 3 :d 4) :e 5 :f (:g 7 :h 8) :e 2)) H=> (h* :f (h* :h 8 :g 7) :b (h* :d 4 :c 3) :e 5 :a 1) (h<-plist* '(:a 1 :b (:c 3))) H=> (h* :a 1 :b (h* :c 3)) ;;;; Basic (expected: as h<-plist) (h<-plist* '(:a 1 :b 2 :c 3 :b 4)) H=> (h* :a 1 :b 2 :c 3) (h<-plist* '(:a 1 :b 2 :a 3)) H=> (h* :a 1 :b 2) (h<-plist* '(:a 1 :b 2)) H=> (h* :a 1 :b 2) (h<-plist* '(:a 1)) H=> (h* :a 1) (h<-plist* '()) H=> (h*) (h<-plist* '(:a 1 :b)) H=> (h* :a 1) (h<-plist* '(:a)) H=> (h*) (h<-plist* [a 1]) !!> error
- h->plist
(table)
Convert hash table TABLE to plist.
Similar to current
ht->plist
, with the difference that it
preserves the hash table's order of keys.
This function is not aware of nested plists. Its nesting-aware
counterpart is h->plist*.
;;;; nested: doesn't recurse (h->plist (ht ("a" "1") ("b" (ht (:c 3) ('d "4"))))) P=> `("a" "1" "b" ,(ht (:c 3) ('d "4"))) (h->plist (h* 'id 1234 'payload (h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is..."))) P=> `(id 1234 payload ,(h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is...")) ;; this example was adapted from elisp-demos ;;;; not-nested (h->plist (ht ("a" 1) ("b" 2))) P=> '("a" 1 "b" 2) (h->plist (h*)) => '() (h->plist (h-new)) => '()
- h->plist*
(table)
Convert hash table TABLE to plist, maybe recursing.
If the PLIST is nested, each plist found as value will be
recursively converted into a hash table as well.
This function is aware of nested plists. Its non–nesting-aware
counterpart is h->plist.
;;;; nested: recurses (h->plist* (ht ("a" "1") ("b" (ht (:c 3) ('d "4"))))) P=> '("a" "1" "b" (:c 3 d "4")) (h->plist* (h* 'id 1234 'payload (h* 'url "https://example.com" 'title "Example Domain" 'content "This domain is..."))) P=> '(id 1234 payload (url "https://example.com" title "Example Domain" content "This domain is...")) ;; this example was adapted from elisp-demos ;;;; not-nested (h->plist* (ht ("a" 1) ("b" 2))) P=> '("a" 1 "b" 2) (h->plist* (h*)) => '() (h->plist* (h-new)) => '()
(json &optional size test)
Convert possibly nested JSON to hash table.
If JSON is a string enclosed in [] instead of {}, the first step of
the conversion will output a vector instead of directly a hash
table. The vector will be inspected, and:
- If every item is a hash table, guess from it the ID for creating
a 2D hash table, and return such table. - Otherwise, treat it as vector and convert to hash table as usual:
indices as numeric keys, items as values.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
;;;; Simple, nested (h<-json* "{ \"Amy\": \"dog\", \"Bob\": { \"cat\": 2, \"fish\": 11 } }") H=> (h* "Amy" "dog" "Bob" (h* "cat" 2 "fish" 11)) ;;;; Vector (h<-json* "[\"k\",4,2,\"x\",\"e\"]") H=> (h* 0 "k" 1 4 2 2 3 "x" 4 "e") ;;;; This next JSON string is an example from p-baleine's jq.el (github) (let ((input " [ { \"name\": \"Ness\", \"age\": 12, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" } }, { \"name\": \"Paula\", \"age\": 11, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" } } ]")) (h<-json* input)) H=> (h* "Ness" (h* "name" "Ness" "age" 12 "origin" (h* "country" "Eagleland" "town" "Onett")) "Paula" (h* "name" "Paula" "age" 11 "origin" (h* "country" "Eagleland" "town" "Twoson"))) ;; p-baleine has an example of how to use jq.el to collect the towns, ;; returning: '("Onett" "Twoson") ;; ;; Solution offered: ;; (cl-loop for x iter-by (jq input ".[] | .origin.town") collect x) ;; ;; It assumes you have jq installed and a few other things. ;; ;; How could we accomplish the same with XHT? ;; ;; First, a longer solution, which uses h--lmap with Dash's -let ;; destructuring: (let ((input " [ { \"name\": \"Ness\", \"age\": 12, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" } }, { \"name\": \"Paula\", \"age\": 11, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" } } ]")) (--> input h<-json* (h--lmap (-let [(&hash key (&hash "origin" (&hash "town"))) it] town) it))) => '("Onett" "Twoson") ;; Here's a better way: (let ((input " [ { \"name\": \"Ness\", \"age\": 12, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" } }, { \"name\": \"Paula\", \"age\": 11, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" } } ]")) (--> input h<-json* (h--lmap (h-get* it key "origin" "town") it))) => '("Onett" "Twoson") ;; So h-get* is more straightforward. ;; But no need to 'it key', since 'value is let-bound. So we have a ;; one-char shorter solution with h-get* — eight, actually, since we ;; won't need the -->. (let ((input " [ { \"name\": \"Ness\", \"age\": 12, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" } }, { \"name\": \"Paula\", \"age\": 11, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" } } ]")) (h--lmap (h-get* value "origin" "town") (h<-json* input))) => '("Onett" "Twoson") ;; But there's a still shorter one, using h-let: (let ((input " [ { \"name\": \"Ness\", \"age\": 12, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" } }, { \"name\": \"Paula\", \"age\": 11, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" } } ]")) (h--lmap (h-let value .origin.town) (h<-json* input))) => '("Onett" "Twoson") ;; So with h--lmap, the value of every pair is let-bound to the variable ;; 'value — which is itself a hash-table, so we can apply h-let to it ;; and fetch .origin.town. ;; Compare: ;; (cl-loop for x iter-by (jq input ".[] | .origin.town") collect x) ;; with ;; (--> input h<-json* (h--lmap (h-get* it key "origin" "town") it)) ;; and ;; (h--lmap (h-get* value "origin" "town") (h<-json* input)) ;; and ;; (h--lmap (h-let value .origin.town) (h<-json* input)) ;;;; Note about back-and-forth conversion ;; Notice that the below is the string resulting from that conversion ;; from h->json*, and that its result is not equal to the original ;; hash table. It's NOT guaranteed that: ;; (->> htbl h->json* h<-json*) H=> htbl ;; because of translation of keys and non-numeric values into strings; ;; translation of nested values into hash tables; of list-values into ;; vector-values; etc. (h<-json* "{ \"a\": { \"b\": [ \"k\", 4, 2, \"x\", \"e\" ] }, \"c\": { \"d\": { \"y\": 2, \"z\": 3 } } }") H=> (h* "a" (h* "b" ["k" 4 2 "x" "e"]) "c" (h* "d" (h* "y" 2 "z" 3))) (h<-json* "{}") H=> (h-new)
(table &optional no-pp sort)
Convert hash table TABLE to JSON, maybe recursing.
If NO-PP is nil, return formatted (pretty-printed string).
If NO-PP is non-nil, return as is (compact string).
If SORT is non-nil, return it sorted. Otherwise don't sort it.
(h->json* (h* "Amy" "dog" "Bob" (h* "cat" 2 "fish" 11))) J=> "{ \"Amy\": \"dog\", \"Bob\": { \"cat\": 2, \"fish\": 11 } }" (h->json* (h* 'a (h* 'b '(k 4 2 x e)) :c (h* :d '((y . 2) (z . 3))))) J=> "{ \"a\": { \"b\": [ \"k\", 4, 2, \"x\", \"e\" ] }, \"c\": { \"d\": { \"y\": 2, \"z\": 3 } } }" (h->json* (ht)) J=> "{}"
2D (tabular)
Functions in this category convert from other formats to hash table or
vice-versa. Key–value pairs are of dimension 2: tabular.
(lol &optional size test)
Create a hash table with initial values according to list of lists LOL.
- The first element of the LOL is a list of keys.
- The other elements of the LOL are their respective values.
- The values of the first column of the LOL are used as unique IDs:
keys to the first hash table. - The value of each unique ID is a hash table composed of all
key–value pairs associated with this unique ID.
As with alists, in case of repeated unique IDs the one uppermost in
the list has preference for returning values.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
(h<-lol '((:a :b :c) (x 2 3) ("y" 5 6) (x 7 8))) H=> (h* 'x (h* :a 'x :b 2 :c 3) "y" (h* :a "y" :b 5 :c 6)) ;; (h<-lol '((:a :b :c) (x 2 3) ("y" 5 6) (x 7 8))) H=> (ht ('x (ht (:a 'x) (:b 2) (:c 3))) ("y" (ht (:a "y") (:b 5) (:c 6))))
(table2d &optional reverse)
Do the opposite of h<-lol, which see. Return list of lists.
Input is TABLE2D.
If REVERSE is non-nil, reverse the display order of all rows after
header. Notice that the internal order of items depends on where
the data came from and whether it has been updated.
(h->lol (ht ('x (ht (:a 'x) (:b 2) (:c 3))) ("y" (ht (:a "y") (:b 5) (:c 6))))) L=> '((:a :b :c) (x 2 3) ("y" 5 6)) (h->lol (ht ('id01 (ht ("ID" 'id01) ("NAME" "Mary") ("AGE" 27))) ('id02 (ht ("ID" 'id02) ("NAME" "John") ("AGE" 20))) ('id03 (ht ("ID" 'id03) ("NAME" "Anne") ("AGE" 21))))) L=> '(("ID" "NAME" "AGE") (id01 "Mary" 27) (id02 "John" 20) (id03 "Anne" 21)) ;; ID should be detected when not the first in the hash table's value, ;; to be the first item of the lists: (h->lol (h* "Alice" (h* "editor" "Emacs" "age" 42 "name" "Alice") "Bob" (h* "editor" "Vim" "age" 30 "name" "Bob") "Emily" (h* "editor" "Nano" "age" 21 "name" "Emily"))) L=> '(("name" "editor" "age") ("Alice" "Emacs" 42) ("Bob" "Vim" 30) ("Emily" "Nano" 21)) (h->lol (h* "Alice" (h* "editor" "Emacs" "name" "Alice" "age" 42) "Bob" (h* "editor" "Vim" "name" "Bob" "age" 30) "Emily" (h* "editor" "Nano" "name" "Emily" "age" 21))) L=> '(("name" "editor" "age") ("Alice" "Emacs" 42) ("Bob" "Vim" 30) ("Emily" "Nano" 21)) ;;;;;; Repeated keys are dealt with (h->lol (ht ('x (ht (:a 'x) (:b 2) (:c 3))) ('y (ht (:a 'y) (:b 8) (:c 9))) ('z (ht (:a 'z) (:b 0) (:c 1))) ('y (ht (:a 'y) (:b 5) (:c 6))))) L=> '((:a :b :c) ;notice: 'y is updated ^here (x 2 3) (y 5 6) (z 0 1)) ;;;;; Reversed (h->lol (ht ('x (ht (:a 'x) (:b 2) (:c 3))) ("y" (ht (:a "y") (:b 5) (:c 6)))) 'reverse) L=> '((:a :b :c) ("y" 5 6) (x 2 3)) (h->lol (ht ('id01 (ht ("ID" 'id01) ("NAME" "Mary") ("AGE" 27))) ('id02 (ht ("ID" 'id02) ("NAME" "John") ("AGE" 20))) ('id03 (ht ("ID" 'id03) ("NAME" "Anne") ("AGE" 21)))) 'reverse) L=> '(("ID" "NAME" "AGE") (id03 "Anne" 21) (id02 "John" 20) (id01 "Mary" 27))
(orgtbl &optional size test)
Create a hash table with initial values according to org table ORGTBL.
Does to an org table what h<-lol does to Lisp tables (lists of lists).
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result of xht--init-size
, which see.
For the meaning of TEST, see h-new.
(--> "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |" h<-orgtbl (-let [(&hash "01" (&hash "name" "editor")) it] (s-lex-format "${name} uses ${editor}!"))) => "Alice uses Emacs!" (h<-orgtbl "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 | | id03 | Anne | 21 | | id03 | Anne | 42 | | id02 | John | 31 |") H=> (ht ("id01" (ht ("ID" "id01") ("NAME" "Mary") ("AGE" "27"))) ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "20"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "21"))))
(table2d &optional reverse)
Do the opposite of h<-orgtbl, which see. Return org table as string.
Input is TABLE2D.
If REVERSE is non-nil, reverse the display order of all rows after
header. Notice that the internal order of items depends on where
the data came from and whether it has been updated.
(h->orgtbl (ht ("id01" (ht ("ID" "id01") ("NAME" "Mary") ("AGE" "27"))) ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "20"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "21"))))) => (replace-regexp-in-string "\n +" "\n" "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 | | id03 | Anne | 21 |") (h->orgtbl (ht ("id01" (ht ("ID" "id01") ("NAME" "Mary") ("AGE" "27"))) ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "20"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "21"))))) O=> "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 | | id03 | Anne | 21 |" ;; ID should be detected when not the first in the hash table's value, ;; to be the first column of the table: (h->orgtbl (h* "Alice" (h* "editor" "Emacs" "age" 42 "name" "Alice") "Bob" (h* "editor" "Vim" "age" 30 "name" "Bob") "Emily" (h* "editor" "Nano" "age" 21 "name" "Emily"))) O=> "| name | editor | age | |-------+--------+-----| | Alice | Emacs | 42 | | Bob | Vim | 30 | | Emily | Nano | 21 |" (h->orgtbl (h* "Alice" (h* "editor" "Emacs" "name" "Alice" "age" 42) "Bob" (h* "editor" "Vim" "name" "Bob" "age" 30) "Emily" (h* "editor" "Nano" "name" "Emily" "age" 21))) O=> "| name | editor | age | |-------+--------+-----| | Alice | Emacs | 42 | | Bob | Vim | 30 | | Emily | Nano | 21 |" ;;;;;; Repeated keys are dealt with (h->orgtbl (ht ("id01" (ht ("ID" "id01") ("NAME" "Mary") ("AGE" "27"))) ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "31"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "42"))) ;; these two update the previous: ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "20"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "21"))))) O=> "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 | | id03 | Anne | 21 |" (h->orgtbl (ht ('x (ht (:a 'x) (:b 2) (:c 3))) ('y (ht (:a 'y) (:b 8) (:c 9))) ('z (ht (:a 'z) (:b 0) (:c 1))) ;; notice: 'y is updated here: ('y (ht (:a 'y) (:b 5) (:c 6))))) O=> "| a | b | c | |---+---+---| | x | 2 | 3 | | y | 5 | 6 | | z | 0 | 1 |" ;;;;; Reversed (h->orgtbl (ht ("id01" (ht ("ID" "id01") ("NAME" "Mary") ("AGE" "27"))) ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "20"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "21")))) 'reverse) => (replace-regexp-in-string "\n +" "\n" "| ID | NAME | AGE | |------+------+-----| | id03 | Anne | 21 | | id02 | John | 20 | | id01 | Mary | 27 |") (h->orgtbl (ht ("id01" (ht ("ID" "id01") ("NAME" "Mary") ("AGE" "27"))) ("id02" (ht ("ID" "id02") ("NAME" "John") ("AGE" "20"))) ("id03" (ht ("ID" "id03") ("NAME" "Anne") ("AGE" "21")))) 'reverse) O=> "| ID | NAME | AGE | |------+------+-----| | id01 | Mary | 27 | | id02 | John | 20 | | id03 | Anne | 21 |"
- h<-tsv
(tsv &optional size test)
Create a hash table with initial values according to TSV.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result ofxht--init-size
, which see.
For the meaning of TEST, see h-new.
;;;; With TABs and newlines (h<-tsv "id\tname\tage\na1\tJane\t27\na2\tJohn\t25\n") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25")) ;;;; With TABs and literal newlines (h<-tsv "\ id\tname\tage a1\tJane\t27 a2\tJohn\t25") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25")) ;;;; With literal TABs and literal newlines (h<-tsv "\ id name age a1 Jane 27 a2 John 25") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25")) ;;;; With empty lines (h<-tsv "\ id name age a1 Jane 27 a2 John 25 ") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25"))
- h->tsv
(table2d &optional reverse)
Do the opposite of h<-tsv, which see. Return tsv.
Input is TABLE2D.
If REVERSE is non-nil, reverse the display order of all rows after
header. Notice that the internal order of items depends on where
the data came from and whether it has been updated.
(h->tsv (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25"))) => "\ id\tname\tage a1\tJane\t27 a2\tJohn\t25 " (h->tsv (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25"))) T=> "\ id\tname\tage a1\tJane\t27 a2\tJohn\t25" (h->tsv (h* "a1" (h* "id" "a1" "name" "Jane" "age" "42") ;; key updated: "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25"))) T=> "\ id\tname\tage a1\tJane\t27 a2\tJohn\t25"
- h<-ssv
(ssv &optional size test)
Create a hash table with initial values according to SSV.
SIZE is the nominal initial size of the table to be created.
If nil, it defaults to the result ofxht--init-size
, which see.
For the meaning of TEST, see h-new.
;;;; With newlines (h<-ssv "id name age\na1 Jane 27\na2 John 25\n") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25")) ;;;; With irregular spacing (h<-ssv "\ id name age a1 Jane 27 a2 John 25") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25")) ;;;; With empty lines (h<-ssv " id name age a1 Jane 27 a2 John 25 ") H=> (h* "a1" (h* "id" "a1" "name" "Jane" "age" "27") "a2" (h* "id" "a2" "name" "John" "age" "25"))
- h->ssv
(table2d &optional reverse)
Do the opposite of h<-ssv, which see. Return ssv.
Input is TABLE2D.
If REVERSE is non-nil, reverse the display order of all rows after
header. Notice that the internal order of items depends on where
the data came from and whether it has been updated.
(h->ssv (h* "a1" (h* "id" "a1" "name" "Janet" "age" "27") "a2" (h* "id" "a2" "name" "Ann" "age" "25"))) => "\ id name age a1 Janet 27 a2 Ann 25 " (h->ssv (h* "a1" (h* "id" "a1" "name" "Janet" "age" "27") "a2" (h* "id" "a2" "name" "Ann" "age" "25"))) S=> "\ id name age a1 Janet 27 a2 Ann 25" (h->ssv (h* "a1" (h* "id" "a1" "name" "Janet" "age" "42") ;; key updated: "a1" (h* "id" "a1" "name" "Janet" "age" "27") "a2" (h* "id" "a2" "name" "Ann" "age" "25"))) S=> "\ id name age a1 Janet 27 a2 Ann 25"
- h->csv
()
Do the opposite of
h<-csv
, which see. Return csv.
NOTE: This function has not yet been implemented.
Unknown thing
Functions that read an unknown object, try to guess what it is, then
convert it to a hash table.
(thing &rest rest)
Convert THING to hash table. Try to guess what it is.
When in doubt, it prefers higher dimension. So if type is alist,it
uses h<-alist*, not h<-alist. Likewise h<-plist* instead of
h-plist
. If you prefer the latter, be explicit, passing the exact
function instead.
And since it uses h-type for type-detection, it tries to match:
- lol, alist, and plist before list — so when you prefer plain
lists, be explicit, passing h<-list instead. - *sv before lines — so when you prefer plain lines, be explicit,
passing h<-lines instead.
REST are arguments that might be taken by the respective conversion
functions.
;;; 2D ;;;; list of lists (h<-it '(("ID" "Name" "Age") ("A" "Amy" "20") ("B" "Bob" "30"))) H=> (h* "A" (h* "ID" "A" "Name" "Amy" "Age" "20") "B" (h* "ID" "B" "Name" "Bob" "Age" "30")) ;;;; orgtbl (h<-it "| ID | Name | Age | |----+------+-----| | A | Amy | 20 | | B | Bob | 30 |") H=> (h* "A" (h* "ID" "A" "Name" "Amy" "Age" "20") "B" (h* "ID" "B" "Name" "Bob" "Age" "30")) ;;;; tsv (h<-it "ID\tName\tAge\nA\tAmy\t20\nB\tBob\t30") H=> (h* "A" (h* "ID" "A" "Name" "Amy" "Age" "20") "B" (h* "ID" "B" "Name" "Bob" "Age" "30")) ;;;; csv (h<-it "ID,Name,Age\nA,Amy,20\nB,Bob,30") H=> (h* "A" (h* "ID" "A" "Name" "Amy" "Age" "20") "B" (h* "ID" "B" "Name" "Bob" "Age" "30")) ;;;; ssv (h<-it "ID Name Age\nA Amy 20\nB Bob 30") H=> (h* "A" (h* "ID" "A" "Name" "Amy" "Age" "20") "B" (h* "ID" "B" "Name" "Bob" "Age" "30")) ;;; Nested (unspecified dimension) ;;;; alist (h<-it '(("Amy" . "dog") ("Bob" . (("cat" . 2) ("fish" . 11))))) H=> (h* "Amy" "dog" "Bob" (h* "cat" 2 "fish" 11)) (h<-it '(("a" . "b") ("c" . "d")) 5) Hp=> (h-st* 5 'equal "a" "b" "c" "d") ;;;; plist (h<-it '("Amy" "dog" "Bob" ("cat" 2 "fish" 11))) H=> (h* "Amy" "dog" "Bob" (h* "cat" 2 "fish" 11)) ;;;; json (h<-it "{ \"Amy\": \"dog\", \"Bob\": { \"cat\": 2, \"fish\": 11 } }") H=> (h* "Amy" "dog" "Bob" (h* "cat" 2 "fish" 11)) ;;; 1D explicit ;;;; kvl (h<-it "Amy = 20\nBob = 30") H=> (h* "Amy" "20" "Bob" "30") (h<-it "a = b\nc = d" 5) Hp=> (h-st* 5 'equal "a" "b" "c" "d") (let ((h-kvl-sep-re ":")) (h<-it "Amy:20\nBob:30")) H=> (h* "Amy" "20" "Bob" "30") ;;;; cons (h<-it '("Amy" . 20)) H=> (h* "Amy" 20) ;;; 1D implicit ;;;; list (h<-it '(a b c)) H=> (h* 0 'a 1 'b 2 'c) ;;;; vector (h<-it [a b c]) H=> (h* 0 'a 1 'b 2 'c) ;;;; lines (h<-it "a\nb\nc") H=> (h* 0 "a" 1 "b" 2 "c") ;;; Self ;;;; ht (h<-it (h* :a 1)) H=> (h* :a 1) (h<-it (ht)) H=> (h*) ;;; Empty ;;;; list (h<-it '()) H=> (h*) (h<-it () 5 'eq) Hp=> (h-st* 5 'eq) ;;;; vector (h<-it []) H=> (h*) (h<-it [] 5 'eq) Hp=> (h-st* 5 'eq) ;;;; string (h<-it "") H=> (h*) (h<-it "" 5 'eq) Hp=> (h-st* 5 'eq) ;;;; hash table (h<-it (h*)) H=> (h*) (h<-it (ht)) H=> (h*) (h<-it (h-new)) H=> (h*) (h<-it (ht-create)) H=> (h*) (h<-it (h-st* 5 'eq)) Hp=> (h-st* 5 'eq) ;;; Error ;;;; Number (h<-it 42) !!> error ;;;; Symbol (h<-it 'a) !!> error
Predicates
Miscellaneous predicates, mostly about checking type for conversion
purposes.
Type
For hash tables, use h?
, which is the same as ht?
: an alias to Emacs'
internal hash-table-p
.
(str &optional obsolete)
Could string STR be a KVL?
Only if, after stripped of any comments and section headers, and
trimmed of any leading and trailing whitespace, every line has
exactly one common separator. Comments and section headers are
matched with h-kvl-comment-re
and h-kvl-section-re
,
respectively, which see.
An OBSOLETE form is
(h-kvl? str &optional sep)
where the optional arg SEP specified the field delimiter. This use
is deprecated. The delimiter is now passed by the variable
h-kvl-sep-re
(which see), which should be let-bound when the
default value is not expected.
;;;; When expected separator is the default: " *= *" (h-kvl? "a=b\nc=d") => t (h-kvl? "a = b\nc = d") => t (h-kvl? "aa = bb\nc = d") => t ;;;; More than one separator? Then it's not KVL. (h-kvl? "a=b=c\nd=e=f") => nil (h-kvl? "a=b=c") => nil ;;;; No separator? Also not KVL. (h-kvl? "abc") => nil ;;;; Leading and trailing whitespace is ignored (h-kvl? " Alice = 42 Bob = 30 ") => t ;;;; Empty lines are ignored (h-kvl? " Alice = 42 Bob = 30 ") => t ;;;; Comments are ignored (h-kvl? "\ # Some people here Alice = 42 # Alice's real age! Bob = 30") => t ;;;; [Sections] are ignored: to deal with simple (flat) .conf files (h-kvl? "[People] Alice = 42 Bob = 30") => t ;;;; When expected separator is different, let-bind this variable (let ((h-kvl-sep-re ":")) (h-kvl? "Alice:42\nBob:30")) => t (let ((h-kvl-sep-re " *: *")) (h-kvl? "\ Alice : 42 Bob : 30")) => t (let ((h-kvl-sep-re ":")) (h-kvl? "Alice:42:Emacs\nBob:30:Vim")) ;; this one ^ looks like a headerless DSV instead. => nil ;;;; When expected comment regex is different, let-bind this variable (let ((h-kvl-comment-re " *;.*")) (h-kvl? "\ ;; Some people Alice = 42 ; Alice's real age! Bob = 30")) => t ;;;; When expected section regex is different, let-bind this variable (let ((h-kvl-section-re "^[ \t]*{.*}[ \t]*$")) (h-kvl? "\ {People} Alice = 42 Bob = 30 Emily = 21")) => t ;;;; Mixed bag for handling a rather unusual KVL (let ((h-kvl-sep-re " *~ *") (h-kvl-comment-re " *;.*") (h-kvl-section-re "^ *{.*} *$")) (h-kvl? " {Some people} ;; We start with Alice: Alice ~ 42 ; Alice's real age! ;; And then Bob: Bob ~ 30 ; (I think) ")) => t ;;;; Although the empty string can be thought of a KVL with no keys... (h-kvl? "") => t ;;;; ...and likewise other xht types... (h-tsv? "") => t (h-csv? "") => t (h-ssv? "") => t ;;;; ...note that [[#h-type][h-type]] and =type-of= will return something else: (h-type "") => :empty (type-of "") => 'string ;;;; Obsolete calling convention — DEPRECATED (with-no-warnings (h-kvl? "Alice:42\nBob:30" ":")) => t
(list)
Non-nil if and only if LIST is a non-nil alist with simple keys.
(h-alist? '((1 . 2) (3 (4 . 5) (6 . 7)))) => t ; nested (h-alist? '((1 . 2) (3 . 4))) => t ; simple (h-alist? '((1) (2 . 3))) => t ; nil element (h-alist? '(1 (2 .3))) => nil ; all elems must be lists (h-alist? '(1 2 3 4 5)) => nil ; just a list (h-alist? '(a 1 b 2)) => nil ; just a list, or a plist (h-alist? "Me, I'm an alist!") => nil ; nope, you're a string (h-alist? '()) => nil
(list)
Non-nil if and only if LIST is a non-nil plist with non-nil atom keys.
(h-plist? '(1 2 3 (4 5 6 7))) => t ; nested (h-plist? '(1 2 3 4)) => t ; simple (h-plist? '(a 1 b 2)) => t ; (h-plist? '(1 2 3 4 5)) => nil ; odd number (h-plist? "Me, I'm a plist!") => nil ; nope, you're a string (h-plist? '()) => nil
(obj)
Return t if OBJ is a list of lists.
(h-lol? '(("id" "name" "age") ("b" "bob" "30") ("e" "emily" "21"))) => t (h-lol? '((a b) (c d))) => t (h-lol? '((a . b) (c . d))) => nil ; alist (h-lol? '(a 1 b 2)) => nil ; just a list, or a plist (h-lol? "Me, I'm a lol!") => nil ; nope, you're a string (h-lol? '()) => nil
(str)
Could string STR be an Org Table?
(h-orgtbl? "| id | name | age | |----+-------+-----| | b | bob | 30 | | e | emily | 21 |") => t ;; formatting not needed (h-orgtbl? "| id | name | age | |----------| | b | bob | 30 | | e | emily | 21 |") => t ;; formatting not needed (h-orgtbl? "| id | name | age |\n|----------|\n| b | bob | 30 | | e | emily | 21 |") => t (h-orgtbl? '((a b) (c d))) !!> error (h-orgtbl? "Me, I'm an org table!") => nil ; nope, you're a string (h-orgtbl? "") => nil
(str)
Could string STR be a JSON object?
Return nil if, after trimming, it isn't enclosed in {} or [].
Return t if inside is either empty or returns non-nil when
converted with json-read-from-string
ignoring errors.
(h-json? "{\n \"c\": 30,\n \"e\": 5\n}") => t (h-json? "[\"k\",4,2,\"x\",\"e\"]") => t (h-json? "{ \"a\": 1, \"b\": { \"c\": 3 } }") => t (h-json? "{ \n }") => t (h-json? "[ \n ]") => t (h-json? "( \n )") => nil (h-json? "{ }") => t (h-json? "{}") => t (h-json? " ") => nil (h-json? "") => nil ;;;; example from p-baleine's jq.el (github) (h-json? " [ { \"name\": \"Ness\", \"age\": 12, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Onett\" } }, { \"name\": \"Paula\", \"age\": 11, \"origin\": { \"country\": \"Eagleland\", \"town\": \"Twoson\" } } ] ") => t
(str)
Could string STR be a TSV?
Only if all non-commented, non-blank lines have at least one tab
not in the beginning of the line. Commented means `^ *#` in the
line.
Note that if the TSV has two fields, and therefore exactly one tab
per line, it can't be distinguished from a KVL-with-tab using tab
as separator, since it can't be detected whether the first line is
a TSV header or a KVL entry. So functions such as h<-it, h-it=,
h-type, and others may guess wrongly.
;;;; Notice that the first two also return t for KVL ;;;; (can't be determined without context) (h-tsv? "a\tb") => t (h-tsv? "a\tb\nc\td") => t (h-tsv? "a\tb\tc\nd\te\tf") => t (h-tsv? "a = b\nc = d") => nil (h-tsv? "a\tb\tc") => t (h-tsv? "abc") => nil
(str)
Could string STR be a CSV?
Only if all non-commented, non-blank lines have at least one comma
not in the beginning of the line. Commented means `^ *#` in the
line.
(h-csv? "one,two,three\na,b,e\nf,g,h") => t (h-csv? "one,two,three\na,\"b, c, and d\",e\nf,g,h") => t (h-csv? "name,hello,bye Alice,\"Hello, there!\",Bye! Bob,\"Hi.\",\"Goodbye, again\"") => t ;;;; The next two are actually TSVs whose entries have commas: (h-csv? "one\ttwo\tthree\na\t\"b, c, and d\"\te\nf\tg\th") => nil (h-csv? "one\ttwo\tthree\na\tb, c, and d\te\nf\tg\th") => nil (h-csv? "a,b") => t (h-csv? "a,b\nc,d") => t (h-csv? "a,b,c\nd,e,f") => t (h-csv? "a b c\nd e f") => nil (h-csv? "a\tb\nc\td") => nil (h-csv? "a,b,c") => t (h-csv? "abc") => nil
(str)
Could string STR be an SSV?
Only if all non-commented, non-blank lines have at least TWO
whitespaces or a tab not in the beginning of the line. Commented
means `^ *#` in the line.
(h-ssv? "a\tb") => t (h-ssv? "a\tb\nc\td") => t (h-ssv? "a\tb\tc\nd\te\tf") => t (h-ssv? "a b c\nd e f") => t (h-ssv? "a b\nc d") => t (h-ssv? "a\tb\tc") => t (h-ssv? "abc") => nil
Properties and dimension
Functions to look up properties and dimension of hash tables (and of other
objects convertible to hash tables).
See also: xht--props=
, xht--props-and-data=
, h-props=
Hash tables
(table prop)
Return the value of property PROP of hash table TABLE.
Valid values for PROP are:
- size
- test
- weakness
- rehash-size
- rehash-threshold
PROP may be given as a symbol, string or keyword.
So any of these work: 'test "test" :test
Note that "size" is the nominal size, which defaults to 65
when created. For the actual size, use ht-size
.
(h-prop (h* :a 1) 'size) => 1 (h-prop (ht (:a 1)) 'size) => 65 (h-prop (ht (:a 1)) 'test) => 'equal (h-prop (ht-create 'eq) 'test) => 'eq (h-prop (ht-create 'eq) 'size) => 65 (h-prop (h-new 10) 'size) => 10 (h-prop (h-new 12 'eql) 'test) => 'eql (h-prop (ht) 'size) => 65 (h-prop (ht) 'weakness) => nil (h-prop (ht) 'rehash-size) => 1.5 (h-prop (ht) 'rehash-threshold) => 0.8125
(table)
Return a hash table containing properties of hash table TABLE.
The property 'data isn't included. The reason is that while all
other properties can be compared directly, the value of 'data is a
plist, and plists with different ordering can be equivalent. This
would create an exception for functions using this function. For
comparing just the data from two hash tables, use h=.
(h-props (h* :a 1)) H=> (h* 'size 1 'test 'equal 'weakness nil 'rehash-size 1.5 'rehash-threshold 0.8125) (h-props (h-st* 20 'eq :a 1)) H=> (h* 'size 20 'test 'eq 'weakness nil 'rehash-size 1.5 'rehash-threshold 0.8125) (h-props (ht (:a 1))) H=> (h* 'size 65 'test 'equal 'weakness nil 'rehash-size 1.5 'rehash-threshold 0.8125) (h-props (make-hash-table)) H=> (h* 'size 65 'test 'eql 'weakness nil 'rehash-size 1.5 'rehash-threshold 0.8125) (h-props (h-new 10 'equal t 1.625)) H=> (h* 'size 10 'test 'equal 'weakness 'key-and-value 'rehash-size 1.625 'rehash-threshold 0.8125)
(table)
Determine dimension of hash table TABLE.
Return:
- error if not a hash table.
- 0 if empty hash table.
- 1 if simple key–value: no value is itself a hash table.
- 1.5 if some values are hash tables of dimension 1, and some
values aren't hash tables. - 2 if h-2d? returns t
- 2.5 if every value is a hash table of dimension at least 2,
but some are higher. - 3 if every value is a hash table of dimension 2.
... etc.
Generally: 1+ (minimum common dimension of hash table values) +
(if any ht has dim > mcd(dims), then 0.5, else 0)
Regarding conversions to hash table, when:
- from KVL, vector, list: should be of dim = 1.
- from lol, orgtbl, *sv: should be of dim = 2.
- from alist, plist: 1 when simple, and usually 1.5 when nested.
(but could be 2+ with the same logic)
(h-dim 42) !!> error (h-dim (ht)) => 0 (h-dim (ht (0 "alice"))) => 1 (h-dim (ht (0 "alice") (1 "bob"))) => 1 (h-dim (ht (0 "alice") (1 (ht (:a "A") (:b "B"))))) => 1.5 (h-dim (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => 2 (h-dim (h* "a" (h* :id "a" :cap "A") "b" (h* :id "b" :cap (h* "c" "C" "d" "D")))) => 2.5
(obj)
Return t if OBJ is a 1D hash table, nil otherwise.
It's 1D if, and only if:
- it's a hash table AND
- none of the values are hash tables.
(h-1d? (ht)) => nil (h-1d? (ht ("a" nil))) => t (h-1d? (ht ("a" 1) ("b" 2))) => t (h-1d? (ht ("a" (h* :id "a" :cap "A")))) => nil (h-1d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => nil (h-1d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-1d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => nil (h-1d? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => nil
(obj)
Return t if OBJ is a 2D hash table, nil otherwise.
Return t if, and only if:
- it's a hash table AND
- all values are regular hash tables AND
- none of the keys of these values (the "header labels") are
themselves hash tables AND - at least one of these keys has as property: its value is equal
to OBJ's key for all of OBJ's keys. In other words, an id ID
exists if, and only if, (h-get* key id) returns key for all
keys.
(h-2d? (ht)) => nil (h-2d? (ht ("a" nil))) => nil (h-2d? (ht ("a" 1) ("b" 2))) => nil (h-2d? (ht ("a" (h* :id "a" :cap "A")))) => t (h-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => t (h-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => t (h-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C")))) => nil (h-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c")))) => nil (h-2d? (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c")))) => t (h-2d? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => t
(obj)
Return t if OBJ is a nested hash table, nil otherwise.
It's nested if, and only if:
- it's a hash table AND
- at least one value is a hash table.
(h-nested? (ht)) => nil (h-nested? (ht ("a" nil))) => nil (h-nested? (ht ("a" 1) ("b" 2))) => nil (h-nested? (ht ("a" (h* :id "a" :cap "A")))) => t (h-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => t (h-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => t (h-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => t (h-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C")))) => t (h-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c")))) => t (h-nested? (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c")))) => t (h-nested? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => t
Generalization to other types
(obj)
Determine dimension of object OBJ.
This is a generalization of h-dim (which see) to other objects.
;;;; non-hash tables (h-it-dim '((0 . "alice") (1 . "bob"))) => 1 ;alist (h-it-dim '(0 "alice" 1 "bob")) => 1 ;plist (h-it-dim "{\n \"a\": 1,\n \"b\": 2\n}") => 1 ;json (h-it-dim "alice = emacs\nbob = vim\n") => 1 ;kvl (h-it-dim '("alice" "bob")) => 1 ;list (h-it-dim ["alice" "bob"]) => 1 ;vector (h-it-dim '((0 . "alice") (1 (2 . "bob")))) => 1.5 ;alist (h-it-dim '(0 "alice" 1 (2 "bob"))) => 1.5 ;plist (h-it-dim "{ \"0\": \"alice\", \"1\": { \"a\": \"A\", \"b\": \"B\" } }") => 1.5 ;json (h-it-dim "name\teditor\nalice\temacs\nbob\tvim\n") => 2 ;tsv (h-it-dim "name editor\nalice emacs\nbob vim\n") => 2 ;ssv (h-it-dim "name,editor\nalice,emacs\nbob,vim\n") => 2 ;csv (h-it-dim "| name | editor | |-------+--------| | alice | emacs | | bob | vim |") => 2 ;orgtbl (h-it-dim '(("name" "editor") ("alice" "emacs") ("bob" "vim"))) => 2 ;lol (h-it-dim '(("bob" ("editor" . "vim") ("name" . "bob")) ("alice" ("editor" . "emacs") ("name" . "alice")))) => 2 ;alist (h-it-dim '("bob" ("editor" "vim" "name" "bob") "alice" ("editor" "emacs" "name" "alice"))) => 2 ;plist (h-it-dim "{ \"alice\": { \"editor\": \"emacs\", \"name\": \"alice\" }, \"bob\": { \"editor\": \"vim\", \"name\": \"bob\" } }") => 2 ;json ;;;; hash tables (same as h-dim) (h-it-dim (ht (0 "alice") (1 "bob"))) => 1 ;hash-table (h-it-dim (ht (0 "alice") (1 (ht (:a "A") (:b "B"))))) => 1.5 (h-it-dim (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => 2 (h-it-dim (h* "a" (h* :id "a" :cap "A") "b" (h* :id "b" :cap (h* "c" "C" "d" "D")))) => 2.5 ;;;; error (h-it-dim 42) !!> error
(obj)
Return t if OBJ is empty, nil otherwise.
This is a generalization of h-empty?
(which see) to other objects.
;;;; non-hash tables (h-it-empty? '()) => t ;list, alist, plist, lol (h-it-empty? []) => t ;vector (h-it-empty? "") => t ;tsv, csv, ssv, kvl, lines (h-it-empty? "| |") => t ;orgtbl (h-it-empty? "{ }") => t ;json (h-it-empty? '((0 . "alice") (1 . "bob"))) => nil ;alist (h-it-empty? '(0 "alice" 1 "bob")) => nil ;plist (h-it-empty? "{\n \"a\": 1,\n \"b\": 2\n}") => nil ;json (h-it-empty? "alice = emacs\nbob = vim\n") => nil ;kvl (h-it-empty? '("alice" "bob")) => nil ;list (h-it-empty? ["alice" "bob"]) => nil ;vector (h-it-empty? '((0 . "alice") (1 (2 . "bob")))) => nil ;alist (h-it-empty? '(0 "alice" 1 (2 "bob"))) => nil ;plist (h-it-empty? "{ \"0\": \"alice\", \"1\": { \"a\": \"A\", \"b\": \"B\" } }") => nil ;json (h-it-empty? "name\teditor\nalice\temacs\nbob\tvim\n") => nil ;tsv (h-it-empty? "name editor\nalice emacs\nbob vim\n") => nil ;ssv (h-it-empty? "name,editor\nalice,emacs\nbob,vim\n") => nil ;csv (h-it-empty? "| name | editor | |-------+--------| | alice | emacs | | bob | vim |") => nil ;orgtbl (h-it-empty? '(("name" "editor") ("alice" "emacs") ("bob" "vim"))) => nil ;lol (h-it-empty? '(("bob" ("editor" . "vim") ("name" . "bob")) ("alice" ("editor" . "emacs") ("name" . "alice")))) => nil ;alist (h-it-empty? '("bob" ("editor" "vim" "name" "bob") "alice" ("editor" "emacs" "name" "alice"))) => nil ;plist (h-it-empty? "{ \"alice\": { \"editor\": \"emacs\", \"name\": \"alice\" }, \"bob\": { \"editor\": \"vim\", \"name\": \"bob\" } }") => nil ;json ;;;; hash tables (same as h-empty?) (h-it-empty? (h*)) => t ;hash table (h-it-empty? (ht)) => t ;hash table (h-it-empty? (h-new)) => t ;hash table (h-it-empty? (ht ("a" nil))) => nil (h-it-empty? (ht ("a" 1) ("b" 2))) => nil (h-it-empty? (ht ("a" (h* :id "a" :cap "A")))) => nil (h-it-empty? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => nil (h-it-empty? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-it-empty? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => nil (h-it-empty? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => nil ;;;; error (h-it-empty? 42) !!> error
(obj)
Return t if OBJ is of dimension 1, nil otherwise.
This is a generalization of h-1d? (which see) to other objects.
;;;; non-hash tables (h-it-1d? '((0 . "alice") (1 . "bob"))) => t ;alist (h-it-1d? '(0 "alice" 1 "bob")) => t ;plist (h-it-1d? "{\n \"a\": 1,\n \"b\": 2\n}") => t ;json (h-it-1d? "alice = emacs\nbob = vim\n") => t ;kvl (h-it-1d? '("alice" "bob")) => t ;list (h-it-1d? ["alice" "bob"]) => t ;vector (h-it-1d? '((0 . "alice") (1 (2 . "bob")))) => nil ;alist (h-it-1d? '(0 "alice" 1 (2 "bob"))) => nil ;plist (h-it-1d? "{ \"0\": \"alice\", \"1\": { \"a\": \"A\", \"b\": \"B\" } }") => nil ;json (h-it-1d? "name\teditor\nalice\temacs\nbob\tvim\n") => nil ;tsv (h-it-1d? "name editor\nalice emacs\nbob vim\n") => nil ;ssv (h-it-1d? "name,editor\nalice,emacs\nbob,vim\n") => nil ;csv (h-it-1d? "| name | editor | |-------+--------| | alice | emacs | | bob | vim |") => nil ;orgtbl (h-it-1d? '(("name" "editor") ("alice" "emacs") ("bob" "vim"))) => nil ;lol (h-it-1d? '(("bob" ("editor" . "vim") ("name" . "bob")) ("alice" ("editor" . "emacs") ("name" . "alice")))) => nil ;alist (h-it-1d? '("bob" ("editor" "vim" "name" "bob") "alice" ("editor" "emacs" "name" "alice"))) => nil ;plist (h-it-1d? "{ \"alice\": { \"editor\": \"emacs\", \"name\": \"alice\" }, \"bob\": { \"editor\": \"vim\", \"name\": \"bob\" } }") => nil ;json ;;;; hash tables (same as h-1d?) (h-it-1d? (ht)) => nil (h-it-1d? (ht ("a" nil))) => t (h-it-1d? (ht ("a" 1) ("b" 2))) => t (h-it-1d? (ht ("a" (h* :id "a" :cap "A")))) => nil (h-it-1d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => nil (h-it-1d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-it-1d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => nil (h-it-1d? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => nil ;;;; error (h-it-1d? 42) !!> error
(obj)
Return t if OBJ is of dimension 2, nil otherwise.
This is a generalization of h-2d? (which see) to other objects.
;;;; non-hash tables (h-it-2d? '((0 . "alice") (1 . "bob"))) => nil ;alist (h-it-2d? '(0 "alice" 1 "bob")) => nil ;plist (h-it-2d? "{\n \"a\": 1,\n \"b\": 2\n}") => nil ;json (h-it-2d? "alice = emacs\nbob = vim\n") => nil ;kvl (h-it-2d? '("alice" "bob")) => nil ;list (h-it-2d? ["alice" "bob"]) => nil ;vector (h-it-2d? '((0 . "alice") (1 (2 . "bob")))) => nil ;alist (h-it-2d? '(0 "alice" 1 (2 "bob"))) => nil ;plist (h-it-2d? "{ \"0\": \"alice\", \"1\": { \"a\": \"A\", \"b\": \"B\" } }") => nil ;json (h-it-2d? "name\teditor\nalice\temacs\nbob\tvim\n") => t ;tsv (h-it-2d? "name editor\nalice emacs\nbob vim\n") => t ;ssv (h-it-2d? "name,editor\nalice,emacs\nbob,vim\n") => t ;csv (h-it-2d? "| name | editor | |-------+--------| | alice | emacs | | bob | vim |") => t ;orgtbl (h-it-2d? '(("name" "editor") ("alice" "emacs") ("bob" "vim"))) => t ;lol (h-it-2d? '(("bob" ("editor" . "vim") ("name" . "bob")) ("alice" ("editor" . "emacs") ("name" . "alice")))) => t ;alist (h-it-2d? '("bob" ("editor" "vim" "name" "bob") "alice" ("editor" "emacs" "name" "alice"))) => t ;plist (h-it-2d? "{ \"alice\": { \"editor\": \"emacs\", \"name\": \"alice\" }, \"bob\": { \"editor\": \"vim\", \"name\": \"bob\" } }") => t ;json ;;;; hash tables (same as h-2d?) (h-it-2d? (ht)) => nil (h-it-2d? (ht ("a" nil))) => nil (h-it-2d? (ht ("a" 1) ("b" 2))) => nil (h-it-2d? (ht ("a" (h* :id "a" :cap "A")))) => t (h-it-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => t (h-it-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-it-2d? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => t (h-it-2d? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => t ;;;; error (h-it-2d? 42) !!> error
(obj)
Return t if OBJ is nested, nil otherwise.
This is a generalization of h-nested? (which see) to other
objects.
;;;; non-hash tables (h-it-nested? '((0 . "alice") (1 . "bob"))) => nil ;alist (h-it-nested? '(0 "alice" 1 "bob")) => nil ;plist (h-it-nested? "{\n \"a\": 1,\n \"b\": 2\n}") => nil ;json (h-it-nested? "alice = emacs\nbob = vim\n") => nil ;kvl (h-it-nested? '("alice" "bob")) => nil ;list (h-it-nested? ["alice" "bob"]) => nil ;vector (h-it-nested? '((0 . "alice") (1 (2 . "bob")))) => t ;alist (h-it-nested? '(0 "alice" 1 (2 "bob"))) => t ;plist (h-it-nested? "{ \"0\": \"alice\", \"1\": { \"a\": \"A\", \"b\": \"B\" } }") => t ;json (h-it-nested? "name\teditor\nalice\temacs\nbob\tvim\n") => t ;tsv (h-it-nested? "name editor\nalice emacs\nbob vim\n") => t ;ssv (h-it-nested? "name,editor\nalice,emacs\nbob,vim\n") => t ;csv (h-it-nested? "| name | editor | |-------+--------| | alice | emacs | | bob | vim |") => t ;orgtbl (h-it-nested? '(("name" "editor") ("alice" "emacs") ("bob" "vim"))) => t ;lol (h-it-nested? '(("bob" ("editor" . "vim") ("name" . "bob")) ("alice" ("editor" . "emacs") ("name" . "alice")))) => t ;alist (h-it-nested? '("bob" ("editor" "vim" "name" "bob") "alice" ("editor" "emacs" "name" "alice"))) => t ;plist (h-it-nested? "{ \"alice\": { \"editor\": \"emacs\", \"name\": \"alice\" }, \"bob\": { \"editor\": \"vim\", \"name\": \"bob\" } }") => t ;json ;;;; hash tables (same as h-nested?) (h-it-nested? (ht)) => nil (h-it-nested? (ht ("a" nil))) => nil (h-it-nested? (ht ("a" 1) ("b" 2))) => nil (h-it-nested? (ht ("a" (h* :id "a" :cap "A")))) => t (h-it-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => t (h-it-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => t (h-it-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => t (h-it-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C")))) => t (h-it-nested? (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c")))) => t (h-it-nested? (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c")))) => t (h-it-nested? (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => t ;;;; error (h-it-nested? 42) !!> error
Writing and reading
Write to file
Functions that write to file.
(filename obj &optional mustbenew)
Write OBJ to file FILENAME.
OBJ may represent any of the input and output data structures of
the conversion functions.
- If they are of string type (as kvl, *sv, org tables), they are
written as they are — strings. - Anything else (numbers, lists, vectors, hash tables) is written
as it looks like — as an s-expression.
Filename is created if nonexistent, and overwritten if existent —
but the parent directory, if it doesn't exist, is only created if
the 'f library is installed and loaded.
The optional arg MUSTBENEW, if non-nil, insists on a check for an
existing file with the same name. If MUSTBENEW is excl
, that
means you get an error if the file already exists; never overwrite.
If MUSTBENEW is neither nil nor excl
, that means ask for
confirmation before overwriting, but do go ahead and overwrite the
file if you confirm.
Note that hash table objects are written using their
#s(hash-table…) raw representation. For finer control of how hash
tables are written, including the option of using (h*…) or (ht…)
representations, see the specialized h-write-ht-like!.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp (h* "a" 1 "b" 2)) (with-temp-buffer (insert-file-contents temp) (read (buffer-string))) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '("a" 1 "b" 2)) (with-temp-buffer (insert-file-contents temp) (read (buffer-string))) (delete-file temp))) => '("a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '(("a" . 1) ("b" . 2))) (with-temp-buffer (insert-file-contents temp) (read (buffer-string))) (delete-file temp))) => '(("a" . 1) ("b" . 2)) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "| Name | Other |\n|-------+-------|\n| Alice | Emacs |") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "| Name | Other |\n|-------+-------|\n| Alice | Emacs |" ;; Writing returns nil (let ((temp (make-temp-file "h-ert-test--"))) (prog1 (h-write! temp '(("a" . 1) ("b" . 2))) (delete-file temp))) => nil
(filename format ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as FORMAT.
For more about FILENAME and MUSTBENEW, see h-write!.
HT-LIKE may come in any of the following autodetected formats:
As Lisp objects:
- #s(hash-table…) = regular representation
- (ht…) form
- (h*…), (h-s*…), (h-t*…), or (h-st*…) form
or any of the above formatted as string (presumably with %S)
FORMAT is how you want your hash table to be written to FILENAME,
and may be any one of the following:
- htbl-cm :: #s(hash-table…) = compact representation
- h*-cm :: (h*…) in compact format
- ht-cm :: (ht…) in compact format
- htbl-pp :: #s(hash-table…) = in pretty-printed format
- h*-pp :: (h*…) in pretty-printed format
- ht-pp :: (ht…) in pretty-printed format
For convenience, you may drop the -pp and it will be implied:
- htbl :: #s(hash-table…) = in pretty-printed format
- h* :: (h*…) in pretty-printed format
- ht :: (ht…) in pretty-printed format
(It's implicit that these are strings, since we're writing to a
file, so we can do without -str suffixes)
Any of these FORMAT options may be passed as either a string, a
keyword, or a symbol — as you prefer. So, for example, any among
"h*-pp", :h*-pp, or 'h*-pp works.
When the table being read has an equality test different than
'equal and either h*-cm or h*-pp is chosen, the output form will be
made into (h-st*…), for which size and test are explicit, thereby
preserving the original test equality information. (This can't be
done with the (ht…) form, for which the equality test, if not
'equal, would be implicitly converted to 'equal.)
Which of the five should you choose?
- Either of the pretty-printed h-formats are much more readable by
humans and can be easily browsed in the command line with the
"less" command line utility. Disadvantages:
- conversion to and from h-formats costs a bit of time, which
might become noticeable when dealing with larger tables. - less portable, depends on this library.
- occupies a tiny bit more of disk space (about which you
probably don't care) because of the added whitespace.
- conversion to and from h-formats costs a bit of time, which
- The compact regular form and the compact h-forms often occupy a
single line (unless their keys or values have line breaks), and
as such aren't suitable for using with "less" — it'll flow
straight to the end. And if you read any of them into a buffer,
you may run into Emacs problem of displaying buffers with very
long lines. - The regular form has the advantage of being universal, which may
be preferable if the file is shared or if for some reason you
don't have access to this and/or the 'ht libraries. But it's
harder for human reading, especially when nested, because it's
displayed unformatted in one line, and because of its metadata. - The regular form is also faster. If you're dealing with huge hash
tables, it'll be faster to write and read regular compact forms
than converting them. The h-forms may have more security issues when reading files from
untrustworthy sources. Consider that this:
#s(hash-table test equal data (:a (+ 2 3)))
has as value for :a the quoted expression '(+ 2 3), whereas any
of these:
(h* :a (+ 2 3)) (ht (:a (+ 2 3)))
will see it evaluated to 5 if you read with
h-read-h
a file
containing it. This seems unlikely to be a problem if you control
the reading and writing. In any case, you can inspect it by using
h-read-list instead — which will assume it's just a plain list
and return it to you quoted, without evaluating it. If it's a
huge expression you may want to apply pp to it. Compare the
results of:
(h-read-h "(ht (:a (+ 2 3)))") (h-read-list "(ht (:a (+ 2 3)))") (pp (h-read-list "(ht (:a (+ 2 3)))"))
Any of the h-forms can be easily read and re-converted to any
hash-table–like format with h-format, which see.
For convenience, partial-application functions are available for
the different formats:
The pp ones are aliased, respectively, to the shorter:
h-write-htbl!
,h-write-h*!
andh-write-ht!
.
See also: h-read-h
and h-read.
;;;; Hash tables stored in any form: regular, or as h-form ;;;;; Regular — should work just like h-read-htbl (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-ht-like! temp :htbl-cm (h* "a" 1 "b" 2)) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))" ;;;;; As h-forms (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — different write format (h-write-ht-like! temp :ht-cm (h* "a" 1 "b" 2)) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(ht (\"a\" 1) (\"b\" 2))" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — different write format (h-write-ht-like! temp :h*-cm (h* "a" 1 "b" 2)) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)"
(filename ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as 'htbl-cm.
For more about this output format, see h-write-ht!
.
For more about FILENAME and MUSTBENEW, see h-write!.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as =h-write-ht!= example — but writes as hash table (h-write-htbl-cm! temp "(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 65 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — but reads hash-table as string (h-write-htbl-cm! temp "#s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — but reads hash-table proper (h-write-htbl-cm! temp #s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1 "b" 2))) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 2 test equal\ rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))"
(filename ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as 'htbl-pp.
For more about this output format, see h-write-ht!
.
For more about FILENAME and MUSTBENEW, see h-write!.
;; To make sure the string is EXACTLY the same when pretty-printing, ;; since different Emacsen may have different configs for indentation: (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line) (temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-htbl-pp! temp "(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"a\" 1 \"b\" 2))\n" (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line) (temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — but reads hash-table as string (h-write-htbl-pp! temp "#s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"a\" 1 \"b\" 2))\n" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — but reads hash-table proper (h-write-htbl-pp! temp #s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data ("a" 1 "b" 2))) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "#s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data\n (\"a\" 1 \"b\" 2))\n"
(filename ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as 'h*-cm.
For more about this output format, see h-write-ht!
.
For more about FILENAME and MUSTBENEW, see h-write!.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — shorter (h-write-h*-cm! temp (h* "a" 1 "b" 2)) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — table eval'd with (ht…) (h-write-h*-cm! temp (ht ("a" 1) ("b" 2))) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — different input format (quoted) (h-write-h*-cm! temp '(ht ("a" 1) ("b" 2))) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — more quotes (h-write-h*-cm! temp ''''''(ht ("a" 1) ("b" 2))) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — different input format (string) (h-write-h*-cm! temp "''''''(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)" (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; same as the previous — no quotes (h-write-h*-cm! temp "(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h* \"a\" 1 \"b\" 2)" ;;;;; With a different equality test for the hash table (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-h*-cm! temp '(h-t* 'eq "a" 1)) (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp))) => "(h-st* 1 'eq \"a\" 1)"
(filename ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as 'ht-cm.
For more about this output format, see h-write-ht!
.
For more about FILENAME and MUSTBENEW, see h-write!.
(filename ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as 'h*-pp.
For more about this output format, see h-write-ht!
.
For more about FILENAME and MUSTBENEW, see h-write!.
Alias: h-write-h*!
.
(let ((temp (make-temp-file "h-ert-test--"))) ;; To make sure the string is EXACTLY the same when pretty-printing, ;; since different Emacsen may have different configs for indentation: (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (prog2 (h-write-h*-pp! temp "(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp)))) => "(h* \"a\" 1\n \"b\" 2)" (let ((temp (make-temp-file "h-ert-test--"))) (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) ;; same as previous example — pp implicit in the function's name (prog2 (h-write-h*! temp "(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp)))) => "(h* \"a\" 1\n \"b\" 2)"
(filename ht-like &optional mustbenew)
Write hash table HT-LIKE to file FILENAME formatted as 'ht-pp.
For more about this output format, see h-write-ht!
.
For more about FILENAME and MUSTBENEW, see h-write!.
Alias: h-write-ht!
.
(let ((temp (make-temp-file "h-ert-test--"))) (let* (fill-prefix indent-region-function (indent-line-function #'lisp-indent-line)) (prog2 ;; same as =h-write-ht!= example — but writes as (ht…) (h-write-ht-pp! temp "(ht (\"a\" 1) (\"b\" 2))") (with-temp-buffer (insert-file-contents temp) (buffer-string)) (delete-file temp)))) => "(ht (\"a\" 1)\n (\"b\" 2))"
Read
Functions that read objects, possible from a file or a buffer.
The generic function is h-read, with others that take one argument offered
for convenience — so, for example:
(h-read-alist obj)
is the same as
(h-read 'alist obj)
Note that h-read-htbl expects OBJ to be of regular hash table type:
#s(hash-table…). But we also have a h-read-h
that also accepts, quoted,
the h-forms: '(ht…), '(h*…), '(h-s*…), '(h-t*…), or '(h-st*…).
It also accepts any of these passed as a string:
"#s(hash-table…)", "(h*…)", etc.
This makes it flexible to read from files, and to have hash tables stored in
files as h-forms.
(type obj)
Deal with object OBJ, expected to be of type TYPE.
- If OBJ, a string, is a valid path of an existing file,
read as string the contents of file. - If OBJ is a buffer, read as string the contents of buffer.
- Otherwise, take OBJ as it is.
Then read it and test it according to xht--to-type
, which see.
TYPE is a symbol, and can be one of:
- 's OR 'str OR 'string
- 'vec OR 'vector
- 'alist
- 'plist
- 'list
- 'cons-pair
- 'lol
- 'htbl
- 'ht-like
When dealing with TSV, CSV, SSV, Org Table, or JSON:
use 'str (or 's, or 'string)
or the respective associated functions — same thing.
When dealing with hash tables, you have two options:
- 'htbl expects something looking strictly like #s(hash-table…),
and signals error otherwise. - 'ht-like is flexible and accepts any ht-like object — which is a
Lisp object or string that looks like or contains one among:
#s(hash-table…), (ht…), (h*…), (h-s*…), (h-t*…), or (h-st*…).
TYPE represents the expectation of what type the string input
should be, so it signals error when mismatched. So if, for example,
you expect your input to be a hash table #s(hash-table…), this:
(h->lol (h-read 'htbl "/path/to/hash-table.el"))
would read this file looking for a hash table, and return it as
such (as Lisp object, no longer a string), to be converted to a
list of lists as specified. If it found, say, an alist in the file
it would signal error.
You could also use the equivalent:
(-> "/path/to/hash-table.el" h-read-htbl h->lol)
and this also works:
(-> (ht (:a 1) (:b 2)) h-read-htbl h->lol)
In this case, h-read-htbl returns the very hash table read, like
identity
. No problem, but unnecessary — this would suffice:
(-> (ht (:a 1) (:b 2)) h->lol)
For convenience, partial-application functions are available which
receive only OBJ as argument:
plus
h-read-kvl, h-read-tsv, h-read-csv, h-read-ssv,
h-read-json, h-read-orgtbl, h-lines
(all are h-read-s),
plus
h-read-lol, h-read-alist, h-read-plist, h-read-list,
h-read-cons-pair, h-read-vector
and for hash tables:
- h-read-htbl for (h-read 'htbl…) = #s(hash-table…) only
h-read-h
for (h-read 'ht-like…) = any ht-like form.
See also: h-write! and h-write-ht-like!.
;;;; Not hash tables (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '(("a" . 1) ("b" . 2))) (h-read 'alist temp) (delete-file temp))) => '(("a" . 1) ("b" . 2)) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '("a" 1 "b" 2)) (h-read 'plist temp) (delete-file temp))) => '("a" 1 "b" 2) ;;;; Hash tables stored as such (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp (h* "a" 1 "b" 2)) (h-read 'htbl temp) (delete-file temp))) H=> (h* "a" 1 "b" 2)
(obj)
Deal with object OBJ, expected to be ht-like.
For more, see h-read.
Alias: h-read-h
.
;; h-read-h is an alias to h-read-ht-like. ;;;; This fails with h-read-htbl, but here they are ;;;; interpreted as hash tables (h-read-h '(h* "a" 1 "b" 2)) H=> (h* "a" 1 "b" 2) (h-read-h "(h* :a 1 :b 2)") H=> (h* :a 1 :b 2) ;;;; Direct reading ;;;;; Regular — should work just like h-read-htbl (h-read-h (h* "a" 1 "b" 2)) H=> (h* "a" 1 "b" 2) ;;;;; As h-forms ;; Notice: these are quoted lists (h-read-h '(ht ("a" 1) ("b" 2))) H=> (h* "a" 1 "b" 2) (h-read-h '(h* "a" 1 "b" 2)) H=> (h* "a" 1 "b" 2) (h-read-h ''''(h* "a" 1 "b" 2)) H=> (h* "a" 1 "b" 2) (h-read-h '(h-t* 'eq :a 1)) Hp==> (h-st* 1 'eq :a 1) ;;;;; As strings (h-read-h "#s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))") H=> (h* "a" 1 "b" 2) (h-read-h "(h* \"a\" 1 \"b\" 2)") H=> (h* "a" 1 "b" 2) (h-read-h "'''(h* \"a\" 1 \"b\" 2)") H=> (h* "a" 1 "b" 2) (h-read-h "(ht (\"a\" 1) (\"b\" 2))") H=> (h* "a" 1 "b" 2) (h-read-h "") H=> (h-new 1) ;; ... and if it works with the strings above, then ... ;;;; Hash tables stored in any form: regular, or as h-form ;;;;; Regular — should work just like h-read-htbl (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp (h* "a" 1 "b" 2)) (h-read-h temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "#s(hash-table size 2 test equal rehash-size 1.5 rehash-threshold 0.8125 data (\"a\" 1 \"b\" 2))") (h-read-h temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp (h-new)) (h-read-h temp) (delete-file temp))) H=> (h-new) ;;;;; As h-forms (let ((temp (make-temp-file "h-ert-test--"))) (prog2 ;; Reading a quoted (ht…) form and writing a compact (h*…) form — ;; which is not obvious from this example, because h-read-h ;; interprets any h form and outputs regular hash table. For that, ;; see the h-write-ht-like! examples, where it's clearer. (h-write-h*-cm! temp '(ht ("a" 1) ("b" 2))) (h-read-h temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-h*! temp (h* "a" 1 "b" 2)) ;; evaluated (h-read-h temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-h*! temp ''''(h* "a" 1 "b" 2)) ;; multiple quotes (h-read-h temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-h*! temp "(h* \"a\" 1 \"b\" 2)") ;; string (h-read-h temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write-h*! temp '(h-t* 'eq :a 1)) ;; changed test (h-read-h temp) (delete-file temp))) Hp==> (h-st* 1 'eq :a 1)
(obj)
Deal with object OBJ, expected to be a hash table.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp (h* "a" 1 "b" 2)) (h-read-htbl temp) (delete-file temp))) H=> (h* "a" 1 "b" 2) (h-read-htbl (h* "a" 1 "b" 2)) H=> (h* "a" 1 "b" 2) ;;;; These are not interpreted as hash tables — use ;;;; h-read-h (alias to h-read-htbl-like) instead (h-read-htbl '(h* "a" 1 "b" 2)) => '(h* "a" 1 "b" 2) (h-read-htbl "(h* :a 1 :b 2)") !!> error
(obj)
Deal with object OBJ, expected to be a string.
For more, see h-read.
;; [[#h-read-s][h-read-s]] can be used for many string formats ;; here's an org table example (let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "| Name | Editor |\n|-------+-------|\n| Alice | Emacs |") (h-read-s temp) ;; or the same : (h-read-orgtbl temp) (delete-file temp))) => "| Name | Editor |\n|-------+-------|\n| Alice | Emacs |"
(obj)
Deal with object OBJ, expected to be a KVL.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "alice = emacs\nbob = vim") (h-read-kvl temp) (delete-file temp))) => "alice = emacs\nbob = vim"
(obj)
Deal with object OBJ, expected to be a TSV.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "Name\tEditor\nAlice\tEmacs\nBob\tVim") (h-read-tsv temp) (delete-file temp))) => "Name\tEditor\nAlice\tEmacs\nBob\tVim"
(obj)
Deal with object OBJ, expected to be a CSV.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "Name,Editor\nAlice,Emacs\nBob,Vim") (h-read-csv temp) (delete-file temp))) => "Name,Editor\nAlice,Emacs\nBob,Vim"
(obj)
Deal with object OBJ, expected to be a SSV.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "Name Editor\nAlice Emacs\nBob Vim") (h-read-ssv temp) (delete-file temp))) => "Name Editor\nAlice Emacs\nBob Vim"
(obj)
Deal with object OBJ, expected to be JSON.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "\ {\"Alice\":{\"Name\":\"Alice\",\"Editor\":\"Emacs\"}, \"Bob\":{\"Name\":\"Bob\",\"Editor\":\"Vim\"}}") (h-read-json temp) (delete-file temp))) => "\ {\"Alice\":{\"Name\":\"Alice\",\"Editor\":\"Emacs\"}, \"Bob\":{\"Name\":\"Bob\",\"Editor\":\"Vim\"}}"
(obj)
Deal with object OBJ, expected to be lines of text.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "Hello\nthere,\nWorld!") (h-read-lines temp) (delete-file temp))) => "Hello\nthere,\nWorld!"
(obj)
Deal with object OBJ, expected to be an org table.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp "| Name | Editor |\n|-------+-------|\n| Alice | Emacs |") (h-read-orgtbl temp) (delete-file temp))) => "| Name | Editor |\n|-------+-------|\n| Alice | Emacs |"
(obj)
Deal with object OBJ, expected to be a list of lists.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '((letter number) ("a" 1) ("b" 2))) (h-read-lol temp) (delete-file temp))) L=> '((letter number) ("a" 1) ("b" 2))
(obj)
Deal with object OBJ, expected to be an association list.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '(("a" . 1) ("b" . 2))) (h-read-alist temp) (delete-file temp))) A=> '(("a" . 1) ("b" . 2))
(obj)
Deal with object OBJ, expected to be a property list.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '("a" 1 "b" 2)) (h-read-plist temp) (delete-file temp))) P=> '("a" 1 "b" 2)
(obj)
Deal with object OBJ, expected to be a list.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '(1 2 3)) (h-read-list temp) (delete-file temp))) => '(1 2 3)
(obj)
Deal with object OBJ, expected to be a cons pair.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp '("a" . 1)) (h-read-cons-pair temp) (delete-file temp))) => '("a" . 1)
(obj)
Deal with object OBJ, expected to be a vector.
For more, see h-read.
(let ((temp (make-temp-file "h-ert-test--"))) (prog2 (h-write! temp [1 2 3]) (h-read-vector temp) (delete-file temp))) => [1 2 3]
Miscellaneous
Format object as symbol, string, keyword, or number
Functions to turn object into a desirable format.
These functions were initially internal only (and therefore named
as "xht--as-string", etc), since they weren't directly about hash
tables.
But they are useful for formatting keys and values, which is a
common enough operation — so I renamed them and made them public.
The alternative would have been to have you either write the code
yourself every time you wanted to do these operations, or else use
the longer-named internal functions, both of which seemed worse.
(obj)
Return OBJ as a string.
(h--hmap (h-as-string key) (h-as-string value) (h* :a 1 :b 2 :c 3)) H=> (h* "a" "1" "b" "2" "c" "3") (h-as-string "Emacs") => "Emacs" (h-as-string :Emacs) => "Emacs" (h-as-string 'Emacs) => "Emacs" (h-as-string 42) => "42"
(obj)
Return OBJ as a keyword.
(h--hmap (h-as-keyword key) (h-as-keyword value) (h* 'a 1 'b 2 'c 3)) H=> (h* :a :1 :b :2 :c :3) (h-as-keyword "Emacs") => :Emacs (h-as-keyword :Emacs) => :Emacs (h-as-keyword 'Emacs) => :Emacs (h-as-keyword 42) => :42
(obj)
Return OBJ as an interned symbol.
(h--hmap (h-as-symbol key) (h-as-symbol value) (h* :a 1 :b 2 :c 3)) H=> (h* 'a '\1 'b '\2 'c '\3) (h-as-symbol "Emacs") => 'Emacs (h-as-symbol :Emacs) => 'Emacs (h-as-symbol 'Emacs) => 'Emacs (h-as-symbol 42) => '\42
(obj)
Return OBJ as a number, if convertible.
If not convertible, signal error.
(h--hmap (h-as-string key) (h-as-number value) (h* :a "1" :b "2" :c "3")) H=> (h* "a" 1 "b" 2 "c" 3) (h-as-number "Emacs") !!> error (h-as-number :Emacs) !!> error (h-as-number 'Emacs) !!> error (h-as-number 042) => 42 (h-as-number "042") => 42 (h-as-number '\042) => 42 (h-as-number :042) => 42 (h-as-number 42) => 42 (h-as-number "42") => 42 (h-as-number '\42) => 42 (h-as-number :42) => 42 (h-as-number 4.2) => 4.2 (h-as-number "4.2") => 4.2 (h-as-number '\4.2) => 4.2 (h-as-number :4.2) => 4.2 (h-as-number 0) => 0 (h-as-number "0") => 0 (h-as-number '\0) => 0 (h-as-number :0) => 0 (h-as-number -0.0) => -0.0 (h-as-number "-0.0") => -0.0 (h-as-number '\-0.0) => -0.0 (h-as-number :-0.0) => -0.0 (h-as-number -4.2) => -4.2 (h-as-number "-4.2") => -4.2 (h-as-number '\-4.2) => -4.2 (h-as-number :-4.2) => -4.2 (h-as-number -42) => -42 (h-as-number "-42") => -42 (h-as-number '\-42) => -42 (h-as-number :-42) => -42 (h-as-number #b101010) => 42 (h-as-number #o52) => 42 (h-as-number #x2a) => 42 (h-as-number #24r1i) => 42 (h-as-number "#b101010") => 42 (h-as-number "#o52") => 42 (h-as-number "#x2a") => 42 (h-as-number "#24r1i") => 42 (h-as-number '#b101010) => 42 (h-as-number '#o52) => 42 (h-as-number '#x2a) => 42 (h-as-number '#24r1i) => 42 (h-as-number :\#b101010) => 42 (h-as-number :\#o52) => 42 (h-as-number :\#x2a) => 42 (h-as-number :\#24r1i) => 42
Specialized hash table operations
“Hash tabular”: 2D-specific operations
Functions to operate on two-dimensional hash tables.
A 2D hash table looks like this:
(h* "n01" (h* "n" "n01" "name" "Alice") "age" "42" "editor" "Emacs" "n02" (h* "n" "n02" "name" "Emily" "age" "21" "editor" "Nano"))
It looks like this when converted to an org table:
| n | name | age | editor | |-----+-------+-----+--------| | n01 | Alice | 42 | Emacs | | n02 | Emily | 21 | Nano |
or to a list of lists:
'(("n" "name" "age" "editor") ("n01" "Alice" "42" "Emacs") ("n02" "Emily" "21" "Nano"))
or to SSV:
n name age editor n01 Alice 42 Emacs n02 Emily 21 Nano
or to JSON:
{ "n01": { "n": "n01", "name": "Alice" "age": "42", "editor": "Emacs", }, "n02": { "n": "n02", "name": "Emily" "age": "21", "editor": "Nano", } }
Name of its elements (I'm removing the string quotes here):
- header: '(n name age editor)
- idcol: '(n n01 n02)
- id: n
Either of these three triads of labels could be given in reference
to "Alice" in the example above:
n01 | name | Alice |
---|---|---|
key | key of value | value of value |
key | field | value |
row | column | cell |
- The first is exact but awkward.
- In code I often opted for the second.
- The third is also nice, but row and column most often alludes to the entire
row and column, not to the first item of either — to which we want to refer
here. But it's understood if we say that the "intersection of row 'n01' and
column 'name' is 'Alice'". This is "field 'name' of key 'n01'" (second)
or the awkward "the value of the key 'name' of the value of key 'n01'"
(first).
See also: h-1d<-2d and h-2d<-1d under
General hash table operations > Conversion > 2D > Hash table to hash table.
Creation
Functions for creating 2D hash tables.
(keys header &optional size test)
Return a 2D hash table from KEYS and HEADER.
Both must be lists.
If either is nil, or not a list, return empty hash table.
All field values are set to nil, where field is
an element of header.
For the meaning of SIZE and TEST, see h-new.
(h-2d-new '("Alice" "Bob" "Emily") '("name" "age" "editor")) H=> (h* "Alice" (h* "name" "Alice" "age" nil "editor" nil) "Bob" (h* "name" "Bob" "age" nil "editor" nil) "Emily" (h* "name" "Emily" "age" nil "editor" nil)) (h-2d-new '("Alice" "Bob") '("name")) H=> (h* "Alice" (h* "name" "Alice") "Bob" (h* "name" "Bob")) (h-2d-new nil nil) H=> (h-new) (h-2d-new nil 3) H=> (h-new) (h-2d-new 3 nil) H=> (h-new) (h-2d-new '(3) nil) H=> (h-new) (h-2d-new nil '(3)) H=> (h-new)
Information
Functions for retrieving information about 2D hash tables.
Note: h-2d-keys
is an alias to h-lkeys (for consistency).
(table2d)
Return the header of 2D hash table TABLE2d.
The header is the union of all keys-of-keys of TABLE2D.
This function does the heavy work to correctly guess what the
table's ID would be — which h-2d-id then uses.
(h-2d-header (ht)) => nil (h-2d-header (ht ("a" nil))) => nil (h-2d-header (ht ("a" 1) ("b" 2))) => nil (h-2d-header (ht ("a" (h* :id "a" :cap "A")))) => '(:id :cap) (h-2d-header (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => '(:id :cap) (h-2d-header (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-2d-header (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => '(:id :cap) (h-2d-header (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C")))) => nil (h-2d-header (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c")))) => nil (h-2d-header (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c")))) => '(:id :cap :double) (h-2d-header (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => '(:id :same)
(table2d)
The ID column of 2D hash table TABLE2D, as list. It's ID + keys.
(h-2d-idcol (ht)) => nil (h-2d-idcol (ht ("a" nil))) => nil (h-2d-idcol (ht ("a" 1) ("b" 2))) => nil (h-2d-idcol (ht ("a" (h* :id "a" :cap "A"))) ) => '(:id "a") (h-2d-idcol (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => '(:id "a" "b") (h-2d-idcol (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-2d-idcol (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => '(:id "a" "b" "c") (h-2d-idcol (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C")))) => nil (h-2d-idcol (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c")))) => nil (h-2d-idcol (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c")))) => '(:id "a" "b" "c") (h-2d-idcol (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => '(:id "a" "b")
(table2d)
Find the ID-like element of the header of 2D hash table TABLE2D.
It's the one associated with the table's keys.
If none is found, return nil, in which case it's not a 2D hash table.
If more than one is found (unlikely), use the first.
(h-2d-id (ht)) => nil (h-2d-id (ht ("a" nil))) => nil (h-2d-id (ht ("a" 1) ("b" 2))) => nil (h-2d-id (ht ("a" (h* :id "a" :cap "A")))) => :id (h-2d-id (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B")))) => :id (h-2d-id (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B")))) => nil (h-2d-id (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c")))) => :id (h-2d-id (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C")))) => nil (h-2d-id (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c")))) => nil (h-2d-id (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c")))) => :id (h-2d-id (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b")))) => :id
(table2d key &optional header)
List row from 2D hash table TABLE2D given KEY and HEADER.
Example: (h-2d-row tb2 "id01" '(:id :name :age)) could
return: '("id01" "Mary" 27).
HEADER can be given in some desirable permutation.
If HEADER is nil, it will be inferred with h-2d-header.
(h-2d-row (ht) "a") => nil (h-2d-row (ht ("a" nil)) "a") => nil (h-2d-row (ht ("a" nil)) "b") => nil (h-2d-row (ht ("a" 1) ("b" 2)) "a") => nil (h-2d-row (ht ("a" (h* :id "a" :cap "A"))) "a") => '("a" "A") (h-2d-row (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B"))) "b") => '("b" "B") (h-2d-row (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B"))) "a") => nil (h-2d-row (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c"))) "b") => '("b" "B") (h-2d-row (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c" :double "cc"))) "c") => '("c" nil "cc") (h-2d-row (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C"))) "a") => nil (h-2d-row (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c"))) "a") => nil (h-2d-row (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c"))) "a") => '("a" "A" nil) (h-2d-row (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c"))) "c") => '("c" nil "cc") (h-2d-row (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b"))) "b") => '("b" "b")
(table2d col &optional idcol)
List column from 2D hash table TABLE2D given COL and IDCOL.
Example: (h-2d-col tb2 :name '(:id id01 id03 id02)) could
return: '(:name "Mary" "John" "Jane").
IDCOL is the ID plus keys.
It can be given in some desirable permutation.
If IDCOL is nil, it will be inferred with h-2d-idcol.
(h-2d-col (ht) :cap) => nil (h-2d-col (ht ("a" nil)) :cap) => nil (h-2d-col (ht ("a" nil)) :cap) => nil (h-2d-col (ht ("a" 1) ("b" 2)) :cap) => nil (h-2d-col (ht ("a" (h* :id "a" :cap "A"))) :cap) => '(:cap "A") (h-2d-col (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id "b" :cap "B"))) :cap) => '(:cap "A" "B") (h-2d-col (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :id 'b :cap "B"))) "a") => nil (h-2d-col (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c"))) :cap) => '(:cap "A" "B" nil) (h-2d-col (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "c" :double "cc"))) :double) => '(:double nil nil "cc") (h-2d-col (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :id "C"))) :cap) => nil (h-2d-col (ht ("a" (h* :id "a" :cap "A")) ("b" (h* :cap "B" :id "b")) ("c" (h* :low "c"))) :cap) => nil (h-2d-col (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c"))) :cap) => '(:cap "A" "B" nil) (h-2d-col (ht ("a" (h* :cap "A" :id "a" )) ("b" (h* :cap "B" :id "b")) ("c" (h* :double "cc" :id "c"))) :id) => '(:id "a" "b" "c") (h-2d-col (ht ("a" (h* :id "a" :same "a")) ("b" (h* :id "b" :same "b"))) :same) => '(:same "a" "b")
Mapping
Functions for mapping 2D hash tables.
Functions that apply three functions or forms to every key–field–value
triad of a 2D hash table and write the results to a hash table.
You can think of the key as the label of the row of the equivalent
org table it would produce; of the field as the label of the
column; and of the value as the contents of the cell.
For example, if you want to produce a fresh table2d whose keys are
upcased, fields are downcased, and the values (currently integers)
are increased by 1, you could run:
(h-2d-hmap (lambda (key _field _value) (upcase key)) (lambda (_key field _value) (downcase field)) (lambda (_key _field value) (1+ value)) table2d)
The underlines above are so that there's no warnings about unused
variables.
The fact that in each function the only variable used was that
which it changes needed not be so. You could have well
had (upcase (concat key "-" field)) in the first one, for
example. But it's unlikely you'd want that. Most often you'd want
to only use key for the first one, only field for the second one,
and the third one could have any combination.
Finally, note that when the field is the table's ID (the table's
"first column"), the value will be the newly calculated key
instead of applying the value function to it. This is almost
certainly what you wanted to do: apply the value functions to all
columns but the first one, which is what you'd do in a spreadsheet
with labeled rows and columns.
Note that the item is only added if both KEY-FUN and FIE-FUN return
non-nil. So no spurious (nil, something) triad results from some
reasonable conditional KEY-FUN such as:
(lambda (key, field, value) (when (> value 2) key))
In the rare case where for some reason you chose to have nil as a
key, you'll have to treat it specially.
IMPORTANT: unlike mapping to a list, mapping to a hash table
demands that the results of the keys be unique. So you must pay
attention to possible collisions. If, for example, in the case
above the original table had both "a" and "A" as original keys,
one of them would end up overwritten, because both return "A"
when upcased. Which one will depend on the key order. So:
actual size of resulting table ≤ size of original table.
In mathematical terms, excepting the rare case of a nil key in the
original, the number of keys in the resulting hash table will match
the number of keys in the original one if, and only if:
- there's a bijection between the set of keys and the set of
key-fun(key,field,value). - there's no (key,field,value) for which either key-fun(key,field,value) or
fie-fun(key,field,value) returns nil.
IMPORTANT: Just because you can pass three arbitrary functions
using all the three variables in each of them doesn't mean you
should. If you, for example, selectively change a field for some
keys but not others, you'll likely split the column, so one of them
will have empty values where the other has something, and
vice-versa. And other such odd operations. It seems unlikely that
you'd want that.
Guideline for the '2d-hmap family' of functions:
- the added 'h' means 'result is a hash table'
- an added '-' means 'anaphoric: enter 3 forms, not 3 lambdas'
- an added '!' means 'destructive: modify TABLE instead of
creating a fresh one'
In the anaphoric versions, if you don't use any of the let-bound
variables key, field, or value in any of the forms, it's ok — no
warnings.
Summary:
3 lambdas | 3 forms (anaphoric) | |
---|---|---|
non-destructive | h-2d-hmap | h--2d-hmap |
destructive | h-2d-hmap! | h--2d-hmap! |
(key-fun fie-fun val-fun table2d)
Return a 2D table from applying functions to each triad of TABLE2D.
KEY-FUN, FIE-FUN, and VAL-FUN are each called with three arguments:
key, field, and value.
- The result of KEY-FUN is the new key.
- The result of FIE-FUN is the new field.
- The result of VAL-FUN is the new value.
Its anaphoric counterpart is h--2d-hmap.
(h-2d-hmap (lambda (key _field _value) key) (lambda (_key field _value) (intern field)) (lambda (_key field value) (pcase field ;; Don't add an "id" pcase like here: ;; it'll be ignored. The id column (here ;; labeled "id") will always return key. ("id" :will-be-ignored) ("name" (capitalize value)) ("age" (read value)))) (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21"))) H=> (h* "id01" (h* 'id "id01" 'name "Alice" 'age 41) "id02" (h* 'id "id02" 'name "Bob" 'age 30) "id03" (h* 'id "id03" 'name "Emily" 'age 21)) (->> (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")) (h-2d-hmap (lambda (key _ _) key) (lambda (_ field _) field) (lambda (key field _) (downcase (format "%s-%s" key field)))) h->orgtbl) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | alice-nakayama | alice-smith | | Bob | bob-nakayama | bob-smith |" (->> (h-2d-new '(10 21 42) '("n" 2 3 4)) (h-2d-hmap (lambda (key _ _) key) (lambda (_ field _) field) (lambda (key field _) (expt key field))) h->orgtbl) O=> "| n | 2 | 3 | 4 | |----+------+-------+---------| | 10 | 100 | 1000 | 10000 | | 21 | 441 | 9261 | 194481 | | 42 | 1764 | 74088 | 3111696 |" ;;;; Example of filtering ;;;;; Return only names, and only for entries whose "name" has a letter "i" (h-2d-hmap (lambda (key field value) (and (equal field "name") (s-contains? "i" value) key)) (lambda (_ field _ ) field) (lambda (_ _ value) value) (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21"))) H=> (h* "id01" (h* "name" "alice") "id03" (h* "name" "emily")) ;;;; Doesn't always need to be a lambda: variadic functions work too (->> (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")) (h-2d-hmap (lambda (key _ _) key) (lambda (_ field _) field) #'concat) h->orgtbl) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | AliceNakayama | AliceSmith | | Bob | BobNakayama | BobSmith |" ;;;; Careful: depending on what you pass, the result may split fields ;;;;; Here's an example of that: (h-2d-hmap (lambda (key _ _) (upcase key)) (lambda (_ field value) (pcase value ("bob" nil) ("emily" (upcase field)) (_ (intern field)))) (lambda (_ field value) (pcase field ("id" value) ("name" (capitalize value)) ("age" (read value)))) (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21"))) ;;;;;; Some odd thing... H=> (h* "ID01" (h* 'id "ID01" 'name "Alice" 'age 41) "ID02" (h* 'id "ID02" 'age 30) "ID03" (h* 'id "ID03" "NAME" "Emily" 'age 21)) ;;;;;; ...that has split fields (h->orgtbl (h* "ID01" (h* 'id "ID01" 'name "Alice" 'age 41) "ID02" (h* 'id "ID02" 'age 30) "ID03" (h* 'id "ID03" "NAME" "Emily" 'age 21))) O=> "| id | name | age | NAME | |------+-------+-----+-------| | ID01 | Alice | 41 | nil | | ID02 | nil | 30 | nil | | ID03 | nil | 21 | Emily |" ;;;;;; ^ This applies to the other functions of this family as well.
(key-form fie-form val-form table2d)
Anaphoric version of h-2d-hmap.
For every key–value pair in TABLE2D, evaluate KEY-FORM, FIE-FORM,
VAL-FORM with the variables key, field, and value bound.
(h--2d-hmap key (intern field) (pcase field ;; Don't add an "id" pcase like here: ;; it'll be ignored. The id column (here ;; labeled "id") will always return key. ("id" :will-be-ignored) ("name" (capitalize value)) ("age" (read value))) (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21"))) H=> (h* "id01" (h* 'id "id01" 'name "Alice" 'age 41) "id02" (h* 'id "id02" 'name "Bob" 'age 30) "id03" (h* 'id "id03" 'name "Emily" 'age 21)) (let ((tbl2d (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21")))) (h--2d-hmap (h-as-keyword key) (h-as-symbol field) (if (s-numeric? value) (h-as-number value) value) tbl2d)) H=> (h* :id01 (h* 'id :id01 'name "alice" 'age 41) :id02 (h* 'id :id02 'name "bob" 'age 30) :id03 (h* 'id :id03 'name "emily" 'age 21)) (->> (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")) (h--2d-hmap key field (downcase (format "%s-%s" key field))) h->orgtbl) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | alice-nakayama | alice-smith | | Bob | bob-nakayama | bob-smith |" (->> (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")) (h--2d-hmap key field (concat key field)) h->orgtbl) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | AliceNakayama | AliceSmith | | Bob | BobNakayama | BobSmith |" (->> (h-2d-new '(10 21 42) '("n" 2 3 4)) (h--2d-hmap key field (expt key field)) h->orgtbl) O=> "| n | 2 | 3 | 4 | |----+------+-------+---------| | 10 | 100 | 1000 | 10000 | | 21 | 441 | 9261 | 194481 | | 42 | 1764 | 74088 | 3111696 |"
(key-fun fie-fun val-fun table2d)
Update TABLE2D by applying functions to each of its triads.
Functions are KEY-FUN, FIE-FUN, VAL-FUN.
Just like h-2d-hmap (which see), but modifies TABLE2D instead of
producing a fresh one.
Its anaphoric counterpart is h--2d-hmap!.
(let ((tbl2d (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21")))) (h-2d-hmap! (lambda (key _field _value) key) (lambda (_key field _value) (intern field)) (lambda (_key field value) (pcase field ;; Don't add an "id" pcase like here: ;; it'll be ignored. The id column (here ;; labeled "id") will always return key. ("id" :will-be-ignored) ("name" (capitalize value)) ("age" (read value)))) tbl2d) tbl2d) H=> (h* "id01" (h* 'id "id01" 'name "Alice" 'age 41) "id02" (h* 'id "id02" 'name "Bob" 'age 30) "id03" (h* 'id "id03" 'name "Emily" 'age 21)) (let ((tbl2d (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")))) (h-2d-hmap! (lambda (key _ _) key) (lambda (_ field _) field) (lambda (key field _) (downcase (format "%s-%s" key field))) tbl2d) (h->orgtbl tbl2d)) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | alice-nakayama | alice-smith | | Bob | bob-nakayama | bob-smith |" (let ((tbl2d (h-2d-new '(10 21 42) '("n" 2 3 4)))) (h-2d-hmap! (lambda (key _ _) key) (lambda (_ field _) field) (lambda (key field _) (expt key field)) tbl2d) (h->orgtbl tbl2d)) O=> "| n | 2 | 3 | 4 | |----+------+-------+---------| | 10 | 100 | 1000 | 10000 | | 21 | 441 | 9261 | 194481 | | 42 | 1764 | 74088 | 3111696 |" ;;;; Doesn't always need to be a lambda: variadic functions work too (let ((tbl2d (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")))) (h-2d-hmap! (lambda (key _ _) key) ;↑ (lambda (_ field _) field) ;| #'concat ;; initial value=nil^, so (concat k f nil) tbl2d) (h->orgtbl tbl2d)) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | AliceNakayama | AliceSmith | | Bob | BobNakayama | BobSmith |"
(key-form fie-form val-form table2d)
Anaphoric version of h-2d-hmap!.
For every key–value pair in TABLE2D, evaluate KEY-FORM, FIE-FORM,
VAL-FORM with the variables key, field, and value bound.
(let ((messy-tbl2d (h* "id01" (h* 'id "id01" "name" :alice "age" '\41) 'id02 (h* 'id 'id02 "name" 'BOB "age" :30) "id03" (h* 'id "id03" "name" "EmIlY" "age" "21")))) (h--2d-hmap! (h-as-keyword key) (h-as-symbol field) (if (equal field "age") (h-as-number value) (-> value h-as-string capitalize intern)) messy-tbl2d) (h->lol messy-tbl2d)) L=> '((id name age) (:id01 Alice 41) (:id02 Bob 30) (:id03 Emily 21)) (let ((tbl2d (h* "id01" (h* "id" "id01" "name" "alice" "age" "41") "id02" (h* "id" "id02" "name" "bob" "age" "30") "id03" (h* "id" "id03" "name" "emily" "age" "21")))) (h--2d-hmap! key (intern field) (pcase field ;; Don't add an "id" pcase like here: ;; it'll be ignored. The id column (here ;; labeled "id") will always return key. ("id" :will-be-ignored) ("name" (capitalize value)) ("age" (read value))) tbl2d) tbl2d) H=> (h* "id01" (h* 'id "id01" 'name "Alice" 'age 41) "id02" (h* 'id "id02" 'name "Bob" 'age 30) "id03" (h* 'id "id03" 'name "Emily" 'age 21)) (let ((tbl2d (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")))) (h--2d-hmap! key field (downcase (format "%s-%s" key field)) tbl2d) (h->orgtbl tbl2d)) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | alice-nakayama | alice-smith | | Bob | bob-nakayama | bob-smith |" (let ((tbl2d (h-2d-new '("Alice" "Bob") '("name" "Nakayama" "Smith")))) (h--2d-hmap! key field (concat key field) tbl2d) (h->orgtbl tbl2d)) O=> "| name | Nakayama | Smith | |-------+----------------+-------------| | Alice | AliceNakayama | AliceSmith | | Bob | BobNakayama | BobSmith |" (let ((tbl2d (h-2d-new '(10 21 42) '("n" 2 3 4)))) (h--2d-hmap! key field (expt key field) tbl2d) (h->orgtbl tbl2d)) O=> "| n | 2 | 3 | 4 | |----+------+-------+---------| | 10 | 100 | 1000 | 10000 | | 21 | 441 | 9261 | 194481 | | 42 | 1764 | 74088 | 3111696 |"
Selection or rejection of columns
Functions for selecting or rejecting columns from 2D hash tables.
(table2d cols)
Return a table with only selected named columns COLS of TABLE2D.
Any column of COLS that isn't in TABLE2D is ignored.
The first column (IDCOL), containing the keys, is always implicitly
selected: otherwise non-repeatability of keys wouldn't be
guaranteed.
This function doesn't modify TABLE2D. Its destructive counterpart
is h-2d-sel-cols!.
(let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols htbl '("name" "age"))) H=> (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols htbl '("editor"))) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs") "id02" (h* "id" "id02" "editor" "Vim") "id03" (h* "id" "id03" "editor" "Nano")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols htbl "editor")) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs") "id02" (h* "id" "id02" "editor" "Vim") "id03" (h* "id" "id03" "editor" "Nano")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols htbl nil)) H=> (h* "id01" (h* "id" "id01") "id02" (h* "id" "id02") "id03" (h* "id" "id03")) (let* ((otbl "| id | editor | name | age | |------+--------+-------+-----| | id01 | Emacs | Alice | 42 | | id02 | Vim | Bob | 30 | | id03 | Nano | Emily | 21 |")) (-> otbl h<-orgtbl (h-2d-sel-cols '("name" "age")) h->orgtbl)) O=> "| id | name | age | |------+-------+-----| | id01 | Alice | 42 | | id02 | Bob | 30 | | id03 | Emily | 21 |" (let* ((otbl "| id | editor | name | age | |------+--------+-------+-----| | id01 | Emacs | Alice | 42 | | id02 | Vim | Bob | 30 | | id03 | Nano | Emily | 21 |")) (-> otbl h<-orgtbl (h-2d-sel-cols "editor") h->orgtbl)) O=> "| id | editor | |------+--------| | id01 | Emacs | | id02 | Vim | | id03 | Nano |" (let* ((htbl (h-new))) (h-2d-sel-cols htbl '("name"))) H=> (h-new) (let* ((htbl (h-new))) (h-2d-sel-cols htbl nil)) H=> (h-new) (let* ((htbl (h-new))) (h-2d-sel-cols htbl "name")) H=> (h-new)
(table2d cols)
Remove all but named columns COLS from 2D hash table TABLE2D.
The first column (IDCOL), containing the keys, is always implicitly
selected: otherwise non-repeatability of keys wouldn't be
guaranteed.
This function modifies TABLE2D. Its non-destructive counterpart
is h-2d-sel-cols.
(let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols! htbl '("name" "age")) htbl) H=> (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols! htbl '("editor")) htbl) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs") "id02" (h* "id" "id02" "editor" "Vim") "id03" (h* "id" "id03" "editor" "Nano")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols! htbl "editor") htbl) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs") "id02" (h* "id" "id02" "editor" "Vim") "id03" (h* "id" "id03" "editor" "Nano")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-sel-cols! htbl nil) htbl) H=> (h* "id01" (h* "id" "id01") "id02" (h* "id" "id02") "id03" (h* "id" "id03")) (let* ((htbl (h-new))) (h-2d-sel-cols! htbl '("name")) htbl) H=> (h-new) (let* ((htbl (h-new))) (h-2d-sel-cols! htbl nil) htbl) H=> (h-new) (let* ((htbl (h-new))) (h-2d-sel-cols! htbl "name") htbl) H=> (h-new)
(table2d cols)
Return a table without the named columns COLS of TABLE2D.
Any column of COLS that isn't in TABLE2D is ignored.
The first column (IDCOL), containing the keys, is never rejected:
otherwise non-repeatability of keys wouldn't be guaranteed.
This function doesn't modify TABLE2D. Its destructive counterpart
is h-2d-rej-cols!.
(let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols htbl '("name" "age"))) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs") "id02" (h* "id" "id02" "editor" "Vim") "id03" (h* "id" "id03" "editor" "Nano")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols htbl '("editor"))) H=> (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols htbl "editor")) H=> (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols htbl nil)) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols htbl '("id" "editor" "name" "age"))) H=> (h* "id01" (h* "id" "id01") "id02" (h* "id" "id02") "id03" (h* "id" "id03")) (let* ((otbl "| id | editor | name | age | |------+--------+-------+-----| | id01 | Emacs | Alice | 42 | | id02 | Vim | Bob | 30 | | id03 | Nano | Emily | 21 |")) (-> otbl h<-orgtbl (h-2d-rej-cols "editor") h->orgtbl)) O=> "| id | name | age | |------+-------+-----| | id01 | Alice | 42 | | id02 | Bob | 30 | | id03 | Emily | 21 |" (let* ((otbl "| id | editor | name | age | |------+--------+-------+-----| | id01 | Emacs | Alice | 42 | | id02 | Vim | Bob | 30 | | id03 | Nano | Emily | 21 |")) (-> otbl h<-orgtbl (h-2d-rej-cols '("name" "age")) h->orgtbl)) O=> "| id | editor | |------+--------| | id01 | Emacs | | id02 | Vim | | id03 | Nano |" (let* ((htbl (h-new))) (h-2d-rej-cols htbl '("name"))) H=> (h-new) (let* ((htbl (h-new))) (h-2d-rej-cols htbl nil)) H=> (h-new) (let* ((htbl (h-new))) (h-2d-rej-cols htbl "name")) H=> (h-new)
(table2d cols)
Remove named columns COLS from 2D hash table TABLE2D.
Any column of COLS that isn't in TABLE2D is ignored.
The first column (IDCOL), containing the keys, is never rejected:
otherwise non-repeatability of keys wouldn't be guaranteed.
This function modifies TABLE2D. Its non-destructive counterpart
is h-2d-rej-cols.
(let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols! htbl '("name" "age")) htbl) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs") "id02" (h* "id" "id02" "editor" "Vim") "id03" (h* "id" "id03" "editor" "Nano")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols! htbl '("editor")) htbl) H=> (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols! htbl "editor") htbl) H=> (h* "id01" (h* "id" "id01" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols! htbl nil) htbl) H=> (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")) (let* ((htbl (h* "id01" (h* "id" "id01" "editor" "Emacs" "name" "Alice" "age" "42") "id02" (h* "id" "id02" "editor" "Vim" "name" "Bob" "age" "30") "id03" (h* "id" "id03" "editor" "Nano" "name" "Emily" "age" "21")))) (h-2d-rej-cols! htbl '("id" "editor" "name" "age")) htbl) H=> (h* "id01" (h* "id" "id01") "id02" (h* "id" "id02") "id03" (h* "id" "id03")) (let* ((htbl (h-new))) (h-2d-rej-cols! htbl '("name")) htbl) H=> (h-new) (let* ((htbl (h-new))) (h-2d-rej-cols! htbl nil) htbl) H=> (h-new) (let* ((htbl (h-new))) (h-2d-rej-cols! htbl "name") htbl) H=> (h-new)
Transposition
Functions for transposing 2D hash tables.
(table2d)
Return a hash table that is TABLE2D transposed.
(h-2d-transpose (h* "Alice" (h* "editor" "Emacs" "name" "Alice" "age" 42) "Bob" (h* "editor" "Vim" "name" "Bob" "age" 30) "Emily" (h* "editor" "Nano" "name" "Emily" "age" 21))) H=> (h* "editor" (h* "name" "editor" "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") "age" (h* "name" "age" "Alice" 42 "Bob" 30 "Emily" 21)) (h-2d-transpose (h* "editor" (h* "name" "editor" "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") "age" (h* "name" "age" "Alice" 42 "Bob" 30 "Emily" 21))) H=> (h* "Alice" (h* "editor" "Emacs" "name" "Alice" "age" 42) "Bob" (h* "editor" "Vim" "name" "Bob" "age" 30) "Emily" (h* "editor" "Nano" "name" "Emily" "age" 21)) (-> "| name | editor | age | |-------+--------+-----| | Alice | Emacs | 42 | | Bob | Vim | 30 | | Emily | Nano | 21 |" h<-orgtbl h-2d-transpose h->orgtbl) O=> "| name | Alice | Bob | Emily | |--------+-------+-----+-------| | editor | Emacs | Vim | Nano | | age | 42 | 30 | 21 |" (h-2d-transpose (h-new)) H=> (h-new)
(table2d)
Transpose TABLE2D.
(let ((htbl (h* "Alice" (h* "editor" "Emacs" "name" "Alice" "age" 42) "Bob" (h* "editor" "Vim" "name" "Bob" "age" 30) "Emily" (h* "editor" "Nano" "name" "Emily" "age" 21)))) (h-2d-transpose! htbl) htbl) H=> (h* "editor" (h* "name" "editor" "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") "age" (h* "name" "age" "Alice" 42 "Bob" 30 "Emily" 21)) (let ((htbl (h* "editor" (h* "name" "editor" "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") "age" (h* "name" "age" "Alice" 42 "Bob" 30 "Emily" 21)))) (h-2d-transpose! htbl) htbl) H=> (h* "Alice" (h* "editor" "Emacs" "name" "Alice" "age" 42) "Bob" (h* "editor" "Vim" "name" "Bob" "age" 30) "Emily" (h* "editor" "Nano" "name" "Emily" "age" 21)) (let ((htbl (h-new))) (h-2d-transpose! htbl) htbl) H=> (h-new)
Hash tables as sets
Functions for creating and manipulating hash tables as sets.
Values are all set to t, and all that matters is the presence of keys.
Note: once created, hash-tables-as-sets can be manipulated with the
same functions for regular ones, including union (h-mix),
difference (h-dif), and intersection (h-cmn) and their
destructive counterparts.
h-as-set<-vector (vector &optional size test)
Create a hash table as set from VECTOR.
The KEYS will be the elements of the vector, and all VALUES will be t.
Optional arguments SIZE and TEST may be passed.
(h-as-set<-vector [:a :e :a :a :r :v :a :v :w :e :a]) H=> (h* :a t :e t :r t :v t :w t) (h-as-set<-vector []) H=> (h-new) (h-as-set<-vector '(:a :e)) !!> error
h-as-set<-list (list &optional size test)
Create a hash table as set from LIST.
The KEYS will be the elements of the list, and all VALUES will be t.
Optional arguments SIZE and TEST may be passed.
(h-as-set<-list '(:a :e :a :a :r :v :a :v :w :e :a)) H=> (h* :a t :e t :r t :v t :w t) ;;;; Adding to the set: use h-mix, either binding and destructively... (let ((htbl (h-new))) (h-mix! htbl (h-as-set<-list '(:a :e :a :a :r :v :a :v :w :e :a))) (h-mix! htbl (h-as-set<-vector [:x :y :z])) htbl) H=> (h* :a t :e t :r t :v t :w t :x t :y t :z t) ;;;; ...or thread it non-destructively (-> '(:a :e :a :a :r :v :a :v :w :e :a) h-as-set<-list (h-mix (h-as-set<-vector [:x :y :z])) (h-mix (h-as-set<-list '(5 42)))) H=> (h* :a t :e t :r t :v t :w t :x t :y t :z t 5 t 42 t) ;;;; empty, error (h-as-set<-list '()) H=> (h-new) (h-as-set<-list [:a :e]) !!> error
h-as-set<-lines (str &optional size test)
Create a hash table as set from the lines of string STR.
The KEYS will be the lines, and all VALUES will be t.
Optional arguments SIZE and TEST may be passed.
(h-as-set<-lines "These here\nare 5\nlines\nand not 2\nlines") H=> (h* "These here" t "are 5" t "lines" t "and not 2" t) (h-as-set<-lines "") H=> (h* "" t) (h-as-set<-lines nil) !!> error
h-as-set<-words (str &optional size test)
Create a hash table as set from the words of string STR.
The KEYS will be the words, and all VALUES will be t.
Optional arguments SIZE and TEST may be passed.
(h-as-set<-words "These are words, and words,\nand more words.") H=> (h* "These" t "are" t "words" t "and" t "more" t) (h-as-set<-words "") H=> (h-new) (h-as-set<-words nil) !!> error
Hash tables for counting
Functions for using hash tables for counting.
h-count<-vector (vector &optional size test)
Create a hash table with the elements of VECTOR and their count.
Optional arguments SIZE and TEST may be passed.
(h-count<-vector [:a :e :a :a :r :v :a :v :w :e :a]) H=> (h* :a 5 :e 2 :r 1 :v 2 :w 1) (h-count<-vector ["Emacs" "Nano" "Emacs" "Emacs" "Nano"]) H=> (h* "Emacs" 3 "Nano" 2) (h-count<-vector []) H=> (h-new) (h-count<-vector '(:a :e)) !!> error
h-count<-list (list &optional size test)
Create a hash table with the elements of LIST and their count.
Optional arguments SIZE and TEST may be passed.
(h-count<-list '(:a :e :a :a :r :v :a :v :w :e :a)) H=> (h* :a 5 :e 2 :r 1 :v 2 :w 1) (h-count<-list '("Emacs" "Nano" "Emacs" "Emacs" "Nano")) H=> (h* "Emacs" 3 "Nano" 2) (h-count<-list '()) H=> (h-new) (h-count<-list [:a :e]) !!> error
h-count<-lines (str &optional size test)
Create a hash table with the lines of string STR and their count.
Optional arguments SIZE and TEST may be passed.
(h-count<-lines "These here\nare 5\nlines\nand not 2\nlines") H=> (h* "These here" 1 "are 5" 1 "lines" 2 "and not 2" 1) (h-count<-lines "") H=> (h* "" 1) (h-count<-lines nil) !!> error
h-count<-words (str &optional size test)
Create a hash table with the words of string STR and their count.
Optional arguments SIZE and TEST may be passed.
(h-count<-words "These are words, and words,\nand more words.") H=> (h* "These" 1 "are" 1 "words" 3 "and" 2 "more" 1) (h-count<-words "") H=> (h-new) (h-count<-words nil) !!> error
h-count-put (table &rest objs)
Create a hash table with objects OBJS with the counts as values.
Each OBJ might be:
- a hash table with counts for values: will be added to the count
of TABLE. - a vector: each element will add 1 to the count of TABLE.
- a list: each element will add 1 to the count of TABLE.
- anything else: added as element: if new, with count 1, or
otherwise added to the count of TABLE.
This function is free of side-effects. Its destructive counterpart
is h-count-put!.
;;;; It autodetects the input type: hash table, list, or vector (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put htbl (h* "Emacs" 4 "Vim" 1 "Nano" 1))) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put htbl '("Emacs" "Vim" "Nano" "Emacs" "Emacs" "Emacs"))) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put htbl ["Emacs" "Vim" "Nano" "Emacs" "Emacs" "Emacs"])) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) ;;;; It can read different number-like formats in the target and source. ;;;;; Matches the target format (let ((htbl (h* "Emacs" "3" "Nano" "1"))) (h-count-put htbl '("Emacs" "Nano" "Emacs" "Emacs" "Emacs"))) H=> (h* "Emacs" "7" "Nano" "2") (let ((htbl (h* "Emacs" 3 "Nano" "1"))) (h-count-put htbl ["Emacs" "Nano" "Emacs" "Emacs" "Emacs"])) H=> (h* "Emacs" 7 "Nano" "2") ;;;;; Note that new counts default to regular number (let ((htbl (h* "Emacs" "3" "Nano" "1"))) (h-count-put htbl (h* "Emacs" :4 "Vim" '1 "Pico" "2" "Nano" 1))) H=> (h* "Emacs" "7" "Nano" "2" "Vim" 1 ;<-- as number because the count was nil "Pico" 2) ;<-- as number because the count was nil ;;;; For convertibles-to-ht, convert it yourself: (let ((htbl (h* "Emacs" 3 "Nano" 1))) (->> '(("Emacs" . 4) ("Vim" . 1) ("Nano" . 1)) h<-alist* (h-count-put htbl))) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) ;;;; Other types are added as they are (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put htbl "Emacs")) H=> (h* "Emacs" 4 "Nano" 1) (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put htbl 42)) H=> (h* "Emacs" 3 "Nano" 1 42 1)
h-count-put! (table &rest objs)
Add counts of objects OBJS to the counts of hash table TABLE.
This function modifies TABLE. Its side-effects–free counterpart is
h-count-put.
;;;; It autodetects the input type: hash table, list, or vector (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put! htbl (h* "Emacs" 4 "Vim" 1 "Nano" 1)) htbl) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put! htbl '("Emacs" "Vim" "Nano" "Emacs" "Emacs" "Emacs")) htbl) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put! htbl ["Emacs" "Vim" "Nano" "Emacs" "Emacs" "Emacs"]) htbl) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) ;;;; It can read different number-like formats in the target and source. ;;;;; Matches the target format (let ((htbl (h* "Emacs" "3" "Nano" "1"))) (h-count-put! htbl '("Emacs" "Nano" "Emacs" "Emacs" "Emacs")) htbl) H=> (h* "Emacs" "7" "Nano" "2") (let ((htbl (h* "Emacs" 3 "Nano" "1"))) (h-count-put! htbl ["Emacs" "Nano" "Emacs" "Emacs" "Emacs"]) htbl) H=> (h* "Emacs" 7 "Nano" "2") ;;;;; Note that new counts default to regular number (let ((htbl (h* "Emacs" "3" "Nano" "1"))) (h-count-put! htbl (h* "Emacs" :4 "Vim" '1 "Pico" "2" "Nano" 1)) htbl) H=> (h* "Emacs" "7" "Nano" "2" "Vim" 1 ;<-- as number because the count was nil "Pico" 2) ;<-- as number because the count was nil ;;;; For convertibles-to-ht, convert it yourself: (let ((htbl (h* "Emacs" 3 "Nano" 1))) (->> '(("Emacs" . 4) ("Vim" . 1) ("Nano" . 1)) h<-alist* (h-count-put! htbl)) htbl) H=> (h* "Emacs" 7 "Nano" 2 "Vim" 1) ;;;; Other types are added as they are (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put! htbl "Emacs") htbl) H=> (h* "Emacs" 4 "Nano" 1) (let ((htbl (h* "Emacs" 3 "Nano" 1))) (h-count-put! htbl 42) htbl) H=> (h* "Emacs" 3 "Nano" 1 42 1)
Miscellaneous
Additional examples
Some additional examples that may be useful and don't fit any
particular function.
xht--misc-examples ()
This function does nothing.
It's just a hack to add to examples.el (and also to the README) a
few miscellaneous tests and examples that aren't attached to any
particular function.
;;;; Importing DSVs: how to deal with other delimiters? ;;;;; Easiest way is to replace to TAB and import as TSV. Example: (let ((colon-sv "id:name:age\n1:alice:42\n2:bob:30")) (->> colon-sv (s-replace ":" "\t") h<-tsv)) H=> (h* "1" (h* "id" "1" "name" "alice" "age" "42") "2" (h* "id" "2" "name" "bob" "age" "30")) ;;;; When doing many operations in a table, Dash's -doto can be handy. ;;;;; It avoids repetitions… (let ((my-small-table (h* :a 1 :b 2))) (-doto my-small-table (h-put! :c 3) (h-put! :d 4) (h-rem! :b) (h-put! :e (h* :f 6 :g 7)))) H=> (let ((my-small-table (h* :a 1 :b 2))) (h-put! my-small-table :c 3) (h-put! my-small-table :d 4) (h-rem! my-small-table :b) (h-put! my-small-table :e (h* :f 6 :g 7)) my-small-table) ;;;;; …or the creation of new hash table copies at every step (let ((my-small-table (h* :a 1 :b 2))) (-doto my-small-table (h-put! :c 3) (h-put! :d 4) (h-rem! :b) (h-put! :e (h* :f 6 :g 7)))) H=> (-> (h* :a 1 :b 2) (h-put :c 3) (h-put :d 4) (h-rem :b) (h-put :e (h* :f 6 :g 7)))
Commands
XHT dispatcher
With the dispatcher you can interactively convert, display, insert, or
write to disk whatever looks like a hash table or could be converted
from/into one. There're also some specialized functions that offer you
"speed dial" access to the dispatcher options.
Main dispatcher
xht-do (&optional ht-opts)
The main command for you to do all sorts of hash table things.
This is XHT's main dispatcher.
- Launch it to convert, display, insert, write to disk.
- It reads and write from (almost) everything that can be
transformed from or into a hash table. - Input can be the last sexp, some thing at point, region
selection, a file, your clipboard, or the whole buffer you're in.
Try it.
You can also make your own function to call it non-interactively.
You must then provide HT-OPTS, a tiny hash table with options to
bypass the input of any specific questions. This hash table may
have any number of the up to six variables holding answers to the
questions asked, namely:
'(do-what where-from where-to what-to format-to file-to)
For example, if you frequently convert the last sexp to hash table,
or want to visualize the hash table before point as (h*…), and then
have this inserted at point. In fact, this one should be
particularly frequent, so we already have a function for it:
xht-do-h*-ify-last-sexp-to-point, which is essentially this:
(xht-do (h* :do-what ?c :where-from ?x :where-to ?i :what-to ?h :format-to ?1 :file-to nil))
That is: [c]onvert, from last se[x]p, [i]nsert at point, to [h]ash
table, format #[1] (which is pretty-printed h* form).
Try that command when point is after a #s(hash-table…) or alist.
With that in mind you can create a similar one adapted to your
needs. You can then key-bind it for quick access.
See also these related specialized commands:
Specialized quicker access
These commands pass pre-defined choices to the main dispatcher so you can
more quickly do the things you do more often without having to answer all of
the main dispatcher's questions.
xht-do-convert-dwim ()
Convert region, thing at point or last-sexp — try to guess it.
- If you have a region selected, use that.
- Otherwise, if you're inside a string, a vector, some sort of
list, or #s(hash-table…), use that. - Otherwise, use last sexp
Then interactively select where to, what to, and format.
See also xht-do for other specialized commands listed there.
xht-do-h*-ify-dwim-to-clipboard ()
First h*-ify region, thing at point or last-sexp; send it to clipboard.
Like xht-do-h*-ify-dwim-to-point, but to clipboard instead of at point.
xht-do-h*-ify-dwim-to-file ()
First h*-ify region, thing at point or last-sexp; write to file.
Like xht-do-h*-ify-dwim-to-point, but to new file instead of at point.
xht-do-h*-ify-dwim-to-replace ()
First h*-ify region, thing at point or last-sexp; replace source.
Like xht-do-h*-ify-dwim-to-point, but replace source instead of
inserting at point.
xht-do-h*-ify-dwim-to-new-buffer ()
First h*-ify region, thing at point or last-sexp; insert at new buffer.
Like xht-do-h*-ify-dwim-to-point, but to new buffer instead of at point.
xht-do-h*-ify-dwim-to-point ()
First h*-ify region, thing at point or last-sexp; insert at point.
Equivalent to calling xht-do and then answering
with ?c N ?i ?h 1 — where N represents the following do-what-I-mean
autodetection:
- If you have a region selected, ?r
- Otherwise, if you're inside a string, a vector, some sort of
list, or #s(hash-table…), ?t - Otherwise, ?x
See also xht-do for other specialized commands listed there.
xht-do-h*-ify-selected-region-to-point ()
xht-do-h*-ify-thing-at-point-to-point ()
Minor modes
xht-do minor mode
A minor mode for turning on keys for xht-do actions.
xht-do-mode (&optional arg)
Enable keys for xht-do actions.
See also xht-do-mode-lighter
and global-xht-do-mode.
This is a minor mode. If called interactively, toggle the Xht-Do
mode
mode. If the prefix argument is positive, enable the mode,
and if it is zero or negative, disable the mode.
If called from Lisp, toggle the mode if ARG is toggle
. Enable
the mode if ARG is nil, omitted, or is a positive number. Disable
the mode if ARG is a negative number.
To check whether the minor mode is enabled in the current buffer,
evaluate xht-do-mode.
The mode's hook is called both when the mode is enabled and when it
is disabled.
\{xht-do-mode-map}
global-xht-do-mode (&optional arg)
Toggle Xht-Do mode in all buffers.
With prefix ARG, enable Global Xht-Do mode if ARG is positive; otherwise,
disable it.
If called from Lisp, toggle the mode if ARG is toggle
.
Enable the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.
Xht-Do mode is enabled in all buffers where xht--turn-on-do-mode
would do
it.
See xht-do-mode for more information on Xht-Do mode.
Font lock minor mode
A minor mode for fontifying XHT's functions and equality operators.
xht-fontify-mode (&optional arg)
Toggle fontification of XHT/HT special variables.
This is a buffer-local minor mode intended for Emacs Lisp buffers.
Enabling it causes syntax highlighting of:
- xht's callables (functions, macros, aliases)
- ht's callables (functions, macros, aliases)
- anaphoric variables such as
key
andvalue
. - exemplify-ert's macros and equality operators
(for testing and examples — see ./dev folder)
They are all turned on when global-xht-fontify-mode is on.
If you wish, you can selectively disable any of them, through the
customize interface: \[customize-group] xht RET.
See there the variables starting with "xht-fontlock-add-".
See also xht-fontify-mode-lighter
and global-xht-fontify-mode.
This is a minor mode. If called interactively, toggle the
`Xht-Fontify mode' mode. If the prefix argument is positive,
enable the mode, and if it is zero or negative, disable the mode.
If called from Lisp, toggle the mode if ARG is toggle
. Enable
the mode if ARG is nil, omitted, or is a positive number. Disable
the mode if ARG is a negative number.
To check whether the minor mode is enabled in the current buffer,
evaluate xht-fontify-mode.
The mode's hook is called both when the mode is enabled and when it
is disabled.
global-xht-fontify-mode (&optional arg)
Toggle Xht-Fontify mode in all buffers.
With prefix ARG, enable Global Xht-Fontify mode if ARG is positive; otherwise,
disable it.
If called from Lisp, toggle the mode if ARG is toggle
.
Enable the mode if ARG is nil, omitted, or is a positive number.
Disable the mode if ARG is a negative number.
Xht-Fontify mode is enabled in all buffers where xht--turn-on-fontify-mode
would do it.
See xht-fontify-mode for more information on Xht-Fontify mode.
See README
Commands to open xht's README.org. Optionally, find things in it.
xht-see-readme (&optional heading narrow)
Open xht's README.org file.
Search for the file in xht.el's directory.
If found, open it read-only.
If optional argument HEADING is passed, try to navigate to the
heading after opening it. HEADING should be a string. If prefixed
by "cid:", search it by CUSTOMID.
If optional argument NARROW is non-nil, narrow to that heading.
This argument has no effect if HEADING is nil or not found.
xht-see-function-in-readme (&optional function)
See FUNCTION in xht's README.org.
When called interactively, select function from minibuffer.
xht-see-news ()
See the News in xht's README.org file.
Appendix
Have you already had a look at the additional examples above? Great!
Here's yet some extra material.
Still more examples of usage
Obtaining hash table values by selecting the key through completing read (from minibuffer)
Try it!
When keys are strings:
(let ((htbl (h* "car" 3 "dice" 4 "elephant" 8))) (h-get htbl (completing-read "Which key? " htbl))) ;<-- C-x C-e
When keys are symbols:
(let ((htbl (h* 'car 3 'dice 4 'elephant 8))) (h-get htbl (read (completing-read "Which key? " htbl)))) ;<-- C-x C-e
Converting a headerless list-of-lists to hash table
Whenever you see lol
in th name of an xht
function, it's meant to be a list-of-lists with a header. These are of dimension 2 and are easy to convert:
(h<-lol '((label count day) (:a 10 "Mon") (:b 20 "Tue") (:c 30 "Wed"))) H=> (h* :a (h* 'label :a 'count 10 'day "Mon") :b (h* 'label :b 'count 20 'day "Tue") :c (h* 'label :c 'count 30 'day "Wed"))
If these come without a header, you can put one yourself before converting:
(->> '((:a 10 "Mon") (:b 20 "Tue") (:c 30 "Wed")) (cons '(label count day)) h<-lol) H=> (h* :a (h* 'label :a 'count 10 'day "Mon") :b (h* 'label :b 'count 20 'day "Tue") :c (h* 'label :c 'count 30 'day "Wed"))
These are two-dimensional and can't be further simplified. They're equivalent to an org table with header:
| label | count | day | |-------+-------+-----| | a | 10 | Mon | | b | 20 | Tue | | c | 30 | Wed |
But sometimes you meet those that have only two "columns":
(h<-lol '((label day) (:a "Mon") (:b "Tue") (:c "Wed"))) H=> (h* :a (h* 'label :a 'day "Mon") :b (h* 'label :b 'day "Tue") :c (h* 'label :c 'day "Wed"))
These can be reduced to 1D:
(h-1d<-2d (h<-lol '((label day) (:a "Mon") (:b "Tue") (:c "Wed")))) H=> (h* :a "Mon" :b "Tue" :c "Wed") ;; same as (h-1d<-2d (h* :a (h* 'label :a 'day "Mon") :b (h* 'label :b 'day "Tue") :c (h* 'label :c 'day "Wed"))) H=> (h* :a "Mon" :b "Tue" :c "Wed")
Ok. But sometimes we meet these without a header, in which case they're not a lol as xht
defines it. We can't convert it directly or the first value will be presumed to be a header, which is not what we want:
(h-1d<-2d (h<-lol '((:a "Mon") (:b "Tue") (:c "Wed")))) H=> (h* :b "Tue" :c "Wed")
What to do? Here are two options:
Option 1: insert a dummy header yourself, to be removed when converting to 1D
(->> '((:a "Mon") (:b "Tue") (:c "Wed")) (cons '(x y)) h<-lol h-1d<-2d) H=> (h* :a "Mon" :b "Tue" :c "Wed")
Option 2: "use ht"
(->> '((:a "Mon") (:b "Tue") (:c "Wed")) (cons 'ht) h-lambdify funcall) H=> (h* :a "Mon" :b "Tue" :c "Wed")
What?!
See, the ht
macro accepts as input two-item lists, so these are equivalent:
(ht (:a 1) (:b 2) (:c 3)) H=> (h* :a 1 :b 2 :c 3)
This is very convenient here. We can cons
the symbol 'ht
to that headerless list-of-lists, then h-lambdify
it (which is to say: cons
a lambda()
to it), then funcall
it, and the output is the 1D hash table we wanted.
Now, if we're doing this stuff interactively and just want a hash table expression inserted at point, you don't even need to h-lambdify
it. If we stop at the cons
, the result is a quoted list:
(->> '((:a "Mon") (:b "Tue") (:c "Wed")) (cons 'ht)) => '(ht (:a "Mon") (:b "Tue") (:c "Wed"))
Thankfully, this quoted list is recognized by xht-do
as what it seems to be, so you can just:
(->> '((:a "Mon") (:b "Tue") (:c "Wed")) (cons 'ht)) ;<-- C-x x x here
to have the h*
expression inserted as indicated below:
(->> '((:a "Mon") (:b "Tue") (:c "Wed")) (cons 'ht)) (h* :a "Mon" :b "Tue" :c "Wed")
(which, by the way, is exactly how I have inserted it above).
Above I assume that you have the handy xht-do-h*-ify-dwim-to-point
bound to C-x x x
as I do.
So you want to destructure some hash tables, huh?
There are a few ways.
All the examples below return the exact same string: "Alice uses Emacs!"
;;; 1D ;;;; Longer (let* ((htbl (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano")) (Alice (h-get htbl "Alice"))) (format "%s uses %s!" "Alice" Alice)) ;;;; Simpler: using XHT's h-let (let ((htbl (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano"))) (h-let htbl (format "%s uses %s!" "Alice" .Alice))) ;;;; Or: using s-format (let ((htbl (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano"))) (s-format "Alice uses ${Alice}!" 'gethash htbl)) ;;;; Or: using Dash's -let ;;;;; Autobinding (-let* ((htbl (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano")) ((&hash "Alice") htbl)) (format "%s uses %s!" "Alice" Alice)) ;;;;; Binding 'editor ;;;;;; using format (-let* ((htbl (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano")) ((&hash "Alice" editor) htbl)) (format "%s uses %s!" "Alice" editor)) ;;;;;; using s-lex-format (-let* ((htbl (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano")) ((&hash "Alice" editor) htbl)) (s-lex-format "Alice uses ${editor}!")) ;; or (-let [(&hash "Alice" editor) (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano")] (s-lex-format "Alice uses ${editor}!")) ;; or (--> (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") (-let [(&hash "Alice" editor) it] (s-lex-format "Alice uses ${editor}!"))) ;; or (--> (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") (-let [editor (h-get it "Alice")] (s-lex-format "Alice uses ${editor}!"))) ;; or (--> (h* "Alice" "Emacs" "Bob" "Vim" "Emily" "Nano") (let ((editor (h-get it "Alice"))) (s-lex-format "Alice uses ${editor}!"))) ;;; 2D ;;;; Longer (let* ((orgtbl "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |") (htbl (h<-orgtbl orgtbl)) (name (h-get* htbl "01" "name")) (editor (h-get* htbl "01" "editor"))) (format "%s uses %s!" name editor)) ;;;; Simpler: using XHT's h-let (let* ((orgtbl "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |") (htbl (h<-orgtbl orgtbl))) (h-let htbl (format "%s uses %s!" .01.name .01.editor))) ;;;; Or: using s-format and h-get (let* ((orgtbl "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |") (htbl (h<-orgtbl orgtbl))) (s-format "${name} uses ${editor}!" 'gethash (h-get htbl "01"))) ;;;; Or: using Dash's -let ;;;;; Binding 'name and 'editor ;;;;;; using format (-let* ((orgtbl "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |") (htbl (h<-orgtbl orgtbl)) ((&hash "01" (&hash "name" "editor")) htbl)) (format "%s uses %s!" name editor)) ;;;;;; using s-lex-format (--> "| id | name | age | editor | |----+-------+-----+--------| | 01 | Alice | 42 | Emacs | | 02 | Bob | 30 | Vim | | 03 | Emily | 21 | Nano |" h<-orgtbl (-let [(&hash "01" (&hash "name" "editor")) it] (s-lex-format "${name} uses ${editor}!")))
(h*…) can be multiple times faster than (ht…) for creating smaller tables
Below I'm using bench
and bench-multi
from Adam Porter's emacs-package-dev-handbook.
These functions garbage-collect before running.
Compare:
(bench (ht (:a 1) (:b 2)))
Total runtime | # of GCs | Total GC runtime |
---|---|---|
7.827e-06 | 0 | 0.0 |
(bench (h* :a 1 :b 2))
Total runtime | # of GCs | Total GC runtime |
---|---|---|
1.753e-06 | 0 | 0.0 |
Or together:
(bench-multi :forms (("ht" (ht (:a 1) (:b 2))) ("h*" (h* :a 1 :b 2))))
Form | x fastest | Total runtime | # of GCs | Total GC runtime |
---|---|---|---|---|
h* | fastest | 0.000002 | 0 | 0 |
ht | 5.19 | 0.000009 | 0 | 0 |
Why?
Because h* sets the hash table nominal size to its actual size — the number of items it receives (in the case above: 2), whereas ht
defaults to Elisp's default of 65.
(h-prop (ht (:a 1) (:b 2)) 'size) => 65 (h-prop (h* :a 1 :b 2) 'size) => 2
Likewise if you're using nested hash tables. Let's try something: extending an example (alphabet) from ht
's README to compare times.
(bench-multi :forms (("ht" (ht ("English" (ht (1 (ht ('letter "a") ('name "A"))) (2 (ht ('letter "b") ('name "B"))) (3 (ht ('letter "c") ('name "C"))) (4 (ht ('letter "d") ('name "D"))) (5 (ht ('letter "e") ('name "E"))) (6 (ht ('letter "f") ('name "F"))) (7 (ht ('letter "g") ('name "G"))) (8 (ht ('letter "h") ('name "H"))) (9 (ht ('letter "i") ('name "I"))) (10 (ht ('letter "j") ('name "J"))) (11 (ht ('letter "k") ('name "K"))) (12 (ht ('letter "l") ('name "L"))) (13 (ht ('letter "m") ('name "M"))) (14 (ht ('letter "n") ('name "N"))) (15 (ht ('letter "o") ('name "O"))) (16 (ht ('letter "p") ('name "P"))) (17 (ht ('letter "q") ('name "Q"))) (18 (ht ('letter "r") ('name "R"))) (19 (ht ('letter "s") ('name "S"))) (20 (ht ('letter "t") ('name "T"))) (21 (ht ('letter "u") ('name "U"))) (22 (ht ('letter "v") ('name "V"))) (23 (ht ('letter "w") ('name "W"))) (24 (ht ('letter "x") ('name "X"))) (25 (ht ('letter "y") ('name "Y"))) (26 (ht ('letter "z") ('name "Z"))))))) ("h*" (h* "English" (h* 1 (h* 'letter "a" 'name "A") 2 (h* 'letter "b" 'name "B") 3 (h* 'letter "c" 'name "C") 4 (h* 'letter "d" 'name "D") 5 (h* 'letter "e" 'name "E") 6 (h* 'letter "f" 'name "F") 7 (h* 'letter "g" 'name "G") 8 (h* 'letter "h" 'name "H") 9 (h* 'letter "i" 'name "I") 10 (h* 'letter "j" 'name "J") 11 (h* 'letter "k" 'name "K") 12 (h* 'letter "l" 'name "L") 13 (h* 'letter "m" 'name "M") 14 (h* 'letter "n" 'name "N") 15 (h* 'letter "o" 'name "O") 16 (h* 'letter "p" 'name "P") 17 (h* 'letter "q" 'name "Q") 18 (h* 'letter "r" 'name "R") 19 (h* 'letter "s" 'name "S") 20 (h* 'letter "t" 'name "T") 21 (h* 'letter "u" 'name "U") 22 (h* 'letter "v" 'name "V") 23 (h* 'letter "w" 'name "W") 24 (h* 'letter "x" 'name "X") 25 (h* 'letter "y" 'name "Y") 26 (h* 'letter "z" 'name "Z")))) ("plist" (list "English" (list 1 '(letter "a" name "A") 2 '(letter "b" name "B") 3 '(letter "c" name "C") 4 '(letter "d" name "D") 5 '(letter "e" name "E") 6 '(letter "f" name "F") 7 '(letter "g" name "G") 8 '(letter "h" name "H") 9 '(letter "i" name "I") 10 '(letter "j" name "J") 11 '(letter "k" name "K") 12 '(letter "l" name "L") 13 '(letter "m" name "M") 14 '(letter "n" name "N") 15 '(letter "o" name "O") 16 '(letter "p" name "P") 17 '(letter "q" name "Q") 18 '(letter "r" name "R") 19 '(letter "s" name "S") 20 '(letter "t" name "T") 21 '(letter "u" name "U") 22 '(letter "v" name "V") 23 '(letter "w" name "W") 24 '(letter "x" name "X") 25 '(letter "y" name "Y") 26 '(letter "z" name "Z")))) ("alist" '(("English" (1 (letter . "a") (name . "A")) (2 (letter . "b") (name . "B")) (3 (letter . "c") (name . "C")) (4 (letter . "d") (name . "D")) (5 (letter . "e") (name . "E")) (6 (letter . "f") (name . "F")) (7 (letter . "g") (name . "G")) (8 (letter . "h") (name . "H")) (9 (letter . "i") (name . "I")) (10 (letter . "j") (name . "J")) (11 (letter . "k") (name . "K")) (12 (letter . "l") (name . "L")) (13 (letter . "m") (name . "M")) (14 (letter . "n") (name . "N")) (15 (letter . "o") (name . "O")) (16 (letter . "p") (name . "P")) (17 (letter . "q") (name . "Q")) (18 (letter . "r") (name . "R")) (19 (letter . "s") (name . "S")) (20 (letter . "t") (name . "T")) (21 (letter . "u") (name . "U")) (22 (letter . "v") (name . "V")) (23 (letter . "w") (name . "W")) (24 (letter . "x") (name . "X")) (25 (letter . "y") (name . "Y")) (26 (letter . "z") (name . "Z")))))))
Form | x fastest | Total runtime | # of GCs | Total GC runtime |
---|---|---|---|---|
h* | fastest | 0.000002 | 0 | 0 |
alist | 1.31 | 0.000002 | 0 | 0 |
plist | 4.34 | 0.000007 | 0 | 0 |
ht | 50.37 | 0.000080 | 0 | 0 |
On the one hand, let's have in mind that we're talking about microseconds here. So this difference is only likely to matter if you're doing this operation repeatedly (say, inside a loop). For one-off creations, you're unlikely to feel much of a difference.
On the other hand, microseconds can add up. Are we going to get into the habit of throwing hash tables at tiny sets of key–value pairs as if there's no tomorrow? As you can't possibly have failed to notice, I'm totally for it. In this case, then, we might as well give 'em alists and plists a good run for their money.
xht for those who already use ht
XHT has by now more than 200 public functions. That's a lot to digest.
If you already use ht
and want to start using xht
, you may wonder which functions of the latter do what you're used to doing with the former. So below is a summary mapping ht
→ xht
based on what the ht
functions do. This should help you get started.
If you currently use… | have a look at… |
---|---|
ht | h*, h-s*, h-t*, h-st* |
ht-create | h-new, h-empty-clone |
ht-copy | h-clone*, h<-ht |
ht-get, ht-get* | h-get , h-get* (both are just aliases) |
ht-find | h-pop, h-pop!, h-first, h--first, h-first!, h--first! |
ht-merge | h-mix*, h-mix |
ht-update! == ht-update | h-mix*!, h-mix! |
ht-set! == ht-set | h-put*!, h-put! (or side-effect-free: h-put*, h-put) |
ht-remove! == ht-remove | h-rem*!, h-rem! (or side-effect-free: h-rem*, h-rem) |
ht-clear! == ht-clear | h-clr! (or side-effect-free: h-clr) |
ht->alist | h->alist*, h->alist |
ht<-alist | h<-alist*, h<-alist |
ht->plist | h->plist*, h->plist |
ht<-plist | h<-plist*, h<-plist |
ht-select | h-sel!, h--sel!, h-sel*!, h--sel*! |
h-sel, h--sel, h-sel*, h--sel* | |
ht-reject! | h-rej!, h--rej!, h-rej*!, h--rej*! |
ht-reject | h-rej, h--rej, h-rej*, h--rej* |
ht-select-keys | h-sel-keys, h-sel-keys!, h-rej-keys, h-rej-keys! |
ht-contains? | h-has-key?, h-has-key*?, h-has-keys? |
ht-equal? | h= (and also: h-pr==, h-pr=, h==, h_=, h~=) |
ht-map | h-lmap, h-lkeep, h-vmap (see also: h-hmap family) |
ht-amap | h--lmap, h--lkeep, h--vmap (see also: h--hmap family) |
ht-each | h-each |
ht-aeach | h--each |
ht-keys | h-lkeys (alias: h-keys ), h-vkeys |
ht-values | h-lvalues (alias: h-values ), h-vvalues |
ht-items | h-litems (alias: h-items ), h-vitems |
Mind you that there isn't (always) an exact correspondence — so check the functions' docstrings for the differences and details.
Thank you
I'd particularly like to thank Adam Porter and Wilfred Hughes, whose writings brought Emacs Lisp hash tables to my attention. It's probable that I wouldn't have given it much thought otherwise.
To that I add that ht
's existence and my use of it led me to eventually wonder how much farther the idea could still be developed. This in turn led me to actually coding much of what would become the first release of XHT. And my repeated use of his helpful during development was very... helpful!
Thank you Artur Malabarba for let-alist
. Studying it gave me the idea for what became h-let
, which adapts let-alist
's code and works like a charm. And how can anyone not use your aggressive-indent-mode?
Dash's code inspired xht-fontify-mode
, which I extended with defcustom
options, and thanks to which you can now make all your hash table operations stand out in bright colors if you want to. Dash also influenced my choice of names for functions (such as h-lkeep
, h-first
, the use of a double dash for anaphoric macros, etc), ideas for functions themselves (ditto), and choice of argument order (such as with h--each
).
Contributing
See my page Software for information about how to contribute to any of my Emacs packages.
Version 1.0 of this package was released with more than 1800 tests/examples; I wrote them myself, and tried to be fairly comprehensive, but coverage might still be missing here and there. So you can also help by suggesting more examples for examples.el
. These can be those that illustrate well some usage, or those that cover some combination of arguments (or odd inputs) not yet covered there.
News
2.0.0 (unreleased — not yet tagged)
XHT Breaking News
This release brings new functions, new variables, and more examples.
It also brings some potentially breaking changes in some KVL-related functions.
KVLs are now better handled in both detection and conversion, as detailed below. Moving the optional non-default delimiter from a positional argument to a let-bindable variable made its conversion signature the same of all other convertible types. This made possible the elimination of some exceptional handling that KVLs demanded in a few places, which in turn made the creation of functions such as h-let-it straightforward.
Changes
Potentially breaking
It now returns either :kvl or :lines (depending on the case) instead of :kvl-or-lines.
This may cause breakage if you were using its :kvl-or-lines result somewhere (e.g. in a cond
or pcase
statement).
If you were using it somewhere without passing a SEP argument, the output will be different.
To keep outputting it using TAB as delimiter (if that's what you want), pass it explicitly:
(h->kvl (h* 'Alice 42 'Bob 30) "\t") => "\ Alice 42 Bob 30 "
whereas without the SEP it now outputs:
(h->kvl (h* 'Alice 42 'Bob 30)) => "\ Alice=42 Bob=30 "
It used to also match TAB — but this separator is rarer for such files, and made it indistinguishable from two-column TSVs for detection: it made it hard to tell whether the first line would have been a TSV header or a key–value line.
The delimiter is now stored in the variable h-kvl-sep-re
, so the default value comes from there instead of being harcoded in the functions. See "Deprecations" further below.
New features
New regular variables
h-kvl-sep-re
, h-kvl-comment-re
, and h-kvl-section-re
whose default values now regulate KVL-related operations and can be temporarily let-bound as needed.
New non-interactive functions
because although a cons-pair is rather trivial, it was a missing key–value store to convert from and to; used in h<-it's autodetection.
were added for completeness, as they were missing elements of the h-read-X functions set.
New commands
to open README.org, narrow into the News heading, skip to the latest, recenter, and show branches.
to pick an xht function from minibuffer, find it in README.org, recenter, and show it.
New arguments to existing commands
New usage examples
(we now have more than 2000 examples, covering pretty much every public function of the package!)
Deprecations
Fixes
h-json? no longer throws an error in some cases where it should have returned nil
h<-lines no longer treats blank strings exceptionally, nor does it accept nil
This is more consistent and expected. So:
(h<-lines " \n \n") H=> (h* 0 " " 1 " " 2 "") (h<-lines "") H=> (h* 0 "") (h<-lines "" 5 'eq) Hp=> (h-st* 5 'eq 0 "") (h<-lines nil) !!> error
h-new's default rehash-threshold has changed from 0.8 to 0.8125
This matches a change that happened way back in Emacs 26 in ‘make-hash-table’ to avoid rounding glitches, so this fix was long overdue. There should be no noticeable changes for those of you who are running Emacs 25.
Many fixes in requires and dependencies
Many small bugfixes
Many small docstring improvements
1.0.0
Major public release
See also
Other libraries
This README was generated with the enormous help of Exemplify-ERT and OrgReadme-fy, two libraries of the same author. If you liked how it was organized and you're an Emacs developer, you may want to give them a look.
OrgReadme-fy itself uses XHT to create some half dozen hash tables that it uses to piece together a README.org file. This means you can have some further idea of how XHT looks like in action by having a look at orgreadme-fy.el
's code. In particular, check orgreadme-fy-libfile->orgstr
, which at the moment makes good use of h--first, h--sel, h<-plist, h-mix, h--each, and h-let. For more, see the other functions there ending in -ht
— say, orgreadme-fy-funs-ht
.
License
This project follows the REUSE Specification (FAQ), which in turn is built upon SPDX.
Therefore, license and copyright information can be found in:
- each file's comment header, or
- an adjacent file of the same name with the additional extension
.license
, or - the
.reuse/dep5
file
The full text of the licenses can be found in the LICENSES
subdirectory.
xht.el
Structure
;;; xht.el --- The extensive hash table library -*- lexical-binding: t -*- ;;; Commentary: ;;;; For all the details, please do see the README ;;; Acknowledgments: ;;; Code: ;;;; Libraries ;;;; Symbols from other packages ;;;; Package metadata ;;;; Customizable variables ;;;; Other variables ;;;; Description macro ;;;; Naming conventions ;;;; Functions ;;;;; General hash table operations ;;;;;; Creation ;;;;;;; Empty one ;;;;;;; Cloning and copying ;;;;;;; From keys and values ;;;;;;;; passed directly ;;;;;;;; passed as vectors ;;;;;;;; passed as lists ;;;;;;;; helper: determine initial size ;;;;;; Binding ;;;;;;; Dot-binding (h-let and h-let-it) ;;;;;; Canonical representations ;;;;;;; Changing canonical representation — from table ;;;;;;;; #s(hash-table…) ;;;;;;;; (ht…) ;;;;;;;; (h*…) and (h-st*…) ;;;;;;; Changing canonical representation — from any ht-like form ;;;;;;;; helper ;;;;;; Mapping from hash table ;;;;;;; to vector ;;;;;;; to list ;;;;;;; to hash table ;;;;;;;; non-recursive ;;;;;;;; recursive ;;;;;;; for side-effects only ;;;;;; Keys operations ;;;;;;; Retrieval (getting) ;;;;;;;; Return value given key ;;;;;;;; Return the very first pair (pop) ;;;;;;;; Return a random pair ;;;;;;;; Return the first pair matching a predicate ;;;;;;; Addition (setting) ;;;;;;;; put ;;;;;;;; put-add ;;;;;;; Removal ;;;;;;;; rem ;;;;;;; Selection ;;;;;;;; sel-keys ;;;;;;;; sel ;;;;;;; Rejection ;;;;;;;; rej-keys ;;;;;;;; rej ;;;;;;; Reverse order ;;;;;;; Change numerical value, including #'1+ and #'1- ;;;;;;;; helper ;;;;;;; Miscellaneous key, value, pair lookups ;;;;;;;; Has this key, this value, or this pair at some level? ;;;;;;;; Has all these keys, these values, or these pairs? ;;;;;; Multi-table operations ;;;;;;; Union ;;;;;;;; mix ;;;;;;;;; helper ;;;;;;; Difference ;;;;;;;; dif ;;;;;;;;; helper ;;;;;;; Intersection ;;;;;;;; cmn ;;;;;;;;; helper ;;;;;;; Comparison / Equality / Equivalence ;;;;;;;; 1D: implicit (Vector, List, Lines) (indices as keys) ;;;;;;;; 1D: explicit (KVL, Cons Cell) ;;;;;;;; ≥1D unspecified (Hash table) (possibly nested) ;;;;;;;;; helper ;;;;;;;; ≥1D unspecified (Alist, Plist, JSON) (possibly nested) ;;;;;;;; 2D (LOL, Org table, TSV, CSV, SSV) ;;;;;;;; Unknown thing ;;;;;;;;; helper ;;;;;;;; Helper ;;;;;; Conversion ;;;;;;; Hash table to hash table ;;;;;;; 1D: implicit (indices as keys) ;;;;;;;; Vectors ;;;;;;;; Lists ;;;;;;;; Lines ;;;;;;; 1D: explicit ;;;;;;;; Key–Value Lines ;;;;;;;; Cons Pairs ;;;;;;; ≥1D unspecific (possibly nested) ;;;;;;;; Association lists ;;;;;;;;; from alist ;;;;;;;;; to alist ;;;;;;;; Property lists ;;;;;;;;; from plist ;;;;;;;;; to plist ;;;;;;;; JSON ;;;;;;; 2D (tabular) ;;;;;;;; Lisp tables (lists of lists) ;;;;;;;; Org tables ;;;;;;;; TSV, CSV, SSV ;;;;;;;;; Without using org (preferred) ;;;;;;;;; Using org ;;;;;;; Unknown thing ;;;;;; Predicates ;;;;;;; Type ;;;;;;; Dimension ;;;;;; Properties and dimension ;;;;;;; Hash tables ;;;;;;; Generalization to other types ;;;;;; Writing and reading ;;;;;;; Write to file ;;;;;;; Read ;;;;;; Miscellaneous ;;;;;;; Format object as symbol, string, keyword, or number ;;;;; Specialized hash table operations ;;;;;; “Hash tabular”: 2D-specific operations ;;;;;;; Creation ;;;;;;; Information ;;;;;;; Mapping ;;;;;;;; to hash table 2D ;;;;;;; Selection or rejection of columns ;;;;;;; Transposition ;;;;;;; helper ;;;;;; Hash tables as sets ;;;;;; Hash tables for counting ;;;;;;; helper ;;;;; Helper ;;;;;; keyword <-> symbol <-> string <-> number ;;;;;; Convert from string to types + types predicates ;;;;;;; string to type (selector) ;;;;;;; string to hash table ;;;;;;; string to alist ;;;;;;; string to plist ;;;;;;; string to lol (list of lists) ;;;;;;; string to list ;;;;;;; string to cons pair ;;;;;;; string to vector ;;;;;; Miscellaneous ;;;;; Miscellaneous ;;;;;; Additional examples ;;;; Commands ;;;;; XHT dispatcher ;;;;;; Main dispatcher ;;;;;;; Helper functions ;;;;;;;; Non-interactive user input: cleanup ;;;;;;;; Interactive user input ;;;;;;;; String-maker ;;;;;;;; Reading ;;;;;;;;; Thing at point ;;;;;;;;;; Hash table ;;;;;;;;; Boundaries ;;;;;;;; String formatting (cm or pp) ;;;;;;;;; JSON ;;;;;;;;; Other strings ;;;;;;;; Insertion ;;;;;;;; Display ;;;;;;;; Last sexp ;;;;;; Specialized quicker access ;;;;;;; Helper functions ;;;;; Minor modes ;;;;;; xht-do minor mode ;;;;;; Font lock minor mode ;;;;; See README ;;;;;; Helper functions ;;;;;;; old helpers ;;;; Aliases ;;;;; For those who prefer to use a single namespace ;;;;;; To ht functions ;;;;;; To native functions ;;;;; Preds: users of 'p' for y/n questions will be pleased — amiritep ;;;;;; To h functions ;;;;;; To ht functions ;;;;;; To native functions ;;;;; DWIM ;;;;;; because usually lists is what you want ;;;;;; because you probably want it in pretty-printed format ;;;;; Just shorter ;;;;; Consistency ;;;;; Directional: make any order work ;;;;; Provisional: until other functions have been implemented ;;;; Wrapping up ;;; xht.el ends here
Contents
;;; xht.el --- The extensive hash table library -*- lexical-binding: t -*- ;; SPDX-FileCopyrightText: © flandrew <https://keyoxide.org/191F5B3E212EF1E515C19918AF32B9A5C1CCCB2D> ;; SPDX-License-Identifier: GPL-3.0-or-later ;;--------------------------------------------------------------------------- ;; Author: flandrew ;; Created: 2022-04-10 ;; Version: 2.0.0-git2024.07.31 ;; Homepage: <https://flandrew.srht.site/listful/software.html> ;; Keywords: extensions, lisp ;; Package-Requires: ((emacs "25.1") (dash "2.15") (s "1.12") (ht "2.3")) ;;--------------------------------------------------------------------------- ;; This file is part of XHT, which is NOT part of GNU Emacs. ;; 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. For more details, see the full license at ;; either LICENSES/GPL-3.0-or-later.txt or <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; XHT is a hash table library. It's extensive and systematically-structured. ;; ;; My intention with it is that: ;; ;; 1. dealing with hash tables in Emacs Lisp be pleasant, ;; ;; 2. hash tables become your go-to choice for most key–value operations in ;; the language, and ;; ;; 3. for almost everything hash-table–related that you might want to do in ;; Emacs Lisp, this library will have functions for it — or at least ;; close enough that you can do it by composing a few of them. ;; ;; Let me know if I succeeded. ;; ;;;; For all the details, please do see the README ;; ;; Open it easily with any of the below: ;; (find-file-read-only "README.org") <--- C-x C-e here¹, or ;; (find-file-read-only-other-frame "README.org") <--- C-x C-e here¹, or ;; (find-file-read-only-other-window "README.org") <--- C-x C-e here¹ ;; ;; or from any buffer: ;; M-x xht-see-readme ;; ;; or read it online: ;; <https://flandrew.srht.site/listful/sw-emacs-xht.html> ;; ;; ¹ or the key that ‘eval-last-sexp’ is bound to, if not C-x C-e. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Acknowledgments: ;; ;; A few of this library's functions adapted code from ht.el. ;; This is usually indicated in the functions themselves with a link to the ;; corresponding ht function. (See, e.g., ‘h-put!’ and ‘h-rem!’.) ;; ;; Thank you, Wilfred Hughes (and other ht contributors)! ;; ;; About ht: ;; SPDX-FileCopyrightText: © 2013 Wilfred Hughes ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Wilfred Hughes ;; Homepage: <https://github.com/Wilfred/ht.el> ;; ;; ------------------------------------------------------------------------ ;; ;; A few parts of this library were inspired by dash.el. ;; The following functions borrowed and adapted code from dash: ;; - much of xht's fontlock/fontification (from dash's fontification) ;; - part of the h-zip and h-unzip family of functions ;; ;; My thanks to Magnar Sveen and the other dash contributors. ;; ;; About dash: ;; SPDX-FileCopyrightText: © 2012 Free Software Foundation, Inc. ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Magnar Sveen ;; Homepage: <https://github.com/magnars/dash.el> ;; ;; ------------------------------------------------------------------------ ;; ;; The main and the helper functions of ‘h-let’ were inspired by, and ;; adapted from, ‘let-alist’. ;; ;; I thank Artur Malabarba for this handy macro. ;; ;; About let-alist: ;; SPDX-FileCopyrightText: © 2014 Free Software Foundation, Inc. ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Artur Malabarba ;; ;; ------------------------------------------------------------------------ ;; ;; The functions ‘xht--do-string-cm-ify’, ‘xht--do-string-escape’, and ;; ‘xht--do-string-escape-ws’ were adapted from string-edit.el. ;; ;; About string-edit: ;; SPDX-FileCopyrightText: © 2013 Magnar Sveen ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Magnar Sveen ;; Homepage: <https://github.com/magnars/string-edit.el> ;; ;; ------------------------------------------------------------------------ ;; ;; The function ‘h-alist?’ was adapted from json.el's ‘json-alist-p’. ;; ;; About json: ;; SPDX-FileCopyrightText: © 2006 Free Software Foundation, Inc. ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Theresa O'Connor ;; ;; ------------------------------------------------------------------------ ;; ;; Helper ‘xht--str-nw?’ was adapted from org-macs.el's ‘org-string-nw-p’. ;; ;; About org: ;; SPDX-FileCopyrightText: © 2004 Free Software Foundation, Inc. ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Carsten Dominik ;; Homepage: <https://orgmode.org> ;; ;; ------------------------------------------------------------------------ ;; ;; Macro ‘xht--describe’ was borrowed from OrgReadme-fy. ;; ;; Functions ‘xht-see-readme’, ‘xht--see-library-readme’, and ;; ‘xht--libname->libfile’ were adapted from ‘orgreadme-fy-see-readme’, ;; ‘orgreadme-fy-see-library-readme’, and ‘orgreadme-fy-libname->libfile’, ;; respectively. ;; ;; About orgreadme-fy: ;; SPDX-FileCopyrightText: © flandrew ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: flandrew ;; Homepage: <https://sr.ht/~flandrew/orgreadme-fy> ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Code: ;;;; Libraries (require 's) (require 'ht) (require 'pp) (require 'rx) (require 'dash) (require 'json) (require 'pcase) (require 'subr-x) ; ‘if-let’, ‘when-let’ (require 'lisp-mnt) ; ‘lm-summary’, ‘lm-version’, ‘lm-homepage’ (eval-when-compile (require 'inline)) ;;;; Symbols from other packages ;; Silence "not known to be defined" compiler warnings (declare-function org-mode "ext:org" ()) (declare-function org-at-table-p "ext:org" (&optional table-type)) (declare-function org-ctrl-c-ctrl-c "ext:org" (&optional arg)) (declare-function orgtbl-mode "ext:org-table" (&optional arg)) (declare-function org-table-begin "ext:org-table" (&optional table-type)) (declare-function org-table-end "ext:org-table" (&optional table-type)) (declare-function org-table-to-lisp "ext:org-table" (&optional txt)) (declare-function org-table-convert-region "ext:org-table" (beg0 end0 &optional separator)) (declare-function aggressive-indent-mode "ext:aggressive-indent" (&optional arg)) ;;;; Package metadata (defconst xht--name "XHT") (defconst xht--dot-el (format "%s.el" (file-name-sans-extension (eval-and-compile (or load-file-name buffer-file-name))))) (defconst xht--readme-org (expand-file-name "README.org" (file-name-directory xht--dot-el))) (defconst xht--summary (lm-summary xht--dot-el)) (defconst xht--version (lm-version xht--dot-el)) (defconst xht--homepage (lm-homepage xht--dot-el)) ;;;; Customizable variables (defgroup xht nil (format "%s." xht--summary) :group 'extensions :group 'lisp :link '(emacs-library-link :tag "Lisp file" "xht.el") :link `(file-link :tag "README.org" ,xht--readme-org) :link `(url-link :tag "Homepage" ,xht--homepage)) ;; See also ‘xht-do-mode-lighter’ under xht-do minor mode. ;; See also ‘xht-fontlock-add-anaphoric-variables’ and other defcustom options ;; under Font lock minor mode. ;;;; Other variables (defvar h-kvl-sep-re " *= *" "Regular expression to match KVL separator (field delimiter). So if a candidate KVL string has in every non-blank, non-commented line a single equal sign surrounded or not by spaces, this will be taken as a valid KVL, and the regex will be taken as the field delimiter to parse the key–value lines. This variable should not be directly changed. Rather, it should be let-bound around KVL-related expressions when the default delimiter is not applicable. Here are two examples for when colon is used as delimiter (the first strictly with no spaces, the second accepting it whether surrounded by spaces or not): (let ((h-kvl-sep-re \":\")) (h-kvl? \"Alice:42\\nBob:30\")) (let ((h-kvl-sep-re \" *: *\")) (h<-kvl \"\ Alice : 42 Bob : 30\"))") (defvar h-kvl-section-re "^[ \t]*\\[.*\\][ \t]*$" "Regular expression to match sections in KVLs. This is used to non-destructively strip sections in key–value lines in KVL–related functions performing conversion and type-testing. It by default matches lines with [some text] surrounded or not by spaces or TABs. This is useful to deal with config files that have sections only for information purposes but which don't otherwise modify or restrict the keys under it. This variable should not be directly changed. Rather, it should be let-bound around KVL-related expressions when the default section regex is not applicable. Here is an example for a hypothetical KVL that uses braces to mark sections: (let ((h-kvl-section-re \"^[ \\t]*{.*}[ \\t]*$\")) (h-kvl? \"\\ {People} Alice = 42 Bob = 30 Emily = 21\"))") (defvar h-kvl-comment-re "^#.*\\|[ \t]#.*" "Regular expression to match comments in KVLs. This is used to non-destructively strip comments in key–value lines in KVL–related functions performing conversion and type-testing. It by default matches: - BOL followed by # followed by anything until EOL - One space or TAB followed by # followed by anything until EOL where BOL and EOL mean beginning and end of line, respectively. This variable should not be directly changed. Rather, it should be let-bound around KVL-related expressions when the default comment regex is not applicable. Here is an example for a hypothetical KVL that uses semicolons for comments: (let ((h-kvl-comment-re \"^;.*\\\\| +;.*\")) (h-kvl? \"\\ ;; These are some people Alice=42 ; Alice's real age! Bob=30 Emily=21\"))") ;; See also ‘xht-do-mode-map’ under xht-do minor mode. ;;;; Description macro (defmacro xht--describe (str) "Describe with string STR the contents under this heading. Think of it as a docstring for the headings of your elisp files. For the full docstring, look for ‘orgreadme-fy-describe’ in the package ‘orgreadme-fy’." (declare (indent 0)) (unless (stringp str) (user-error "‘xht--describe’ must receive a string"))) ;;;; Naming conventions (xht--describe "This library adopts the following naming conventions: | These functions | look like | |-----------------+-----------| | Regular | h-foo | | Anaphoric | h--foo | | Interactive | xht-foo | | Private | xht--foo | and | These variables | look like | |-----------------+-----------| | Regular | h-foo | | Customizable | xht-foo | | Private | xht--foo | The two tables above represent almost all of the more than 500 symbols defined by ‘xht’. These also happen to be the same familiar conventions that have long been adopted by ‘dash’. There are some *functions that convert from and to hash tables*: | These functions | look like | |----------------------+----------------------| | Regular (conversion) | h->foo, h<-foo | | Private (conversion) | xht-->foo, xht<--foo | This intuitive use of '->' and '<-' to represent conversion will certainly look familiar to those who have used ‘ht’. That's pretty much it. The only symbols that don't perfectly match the tables above are: - The commands ‘global-xht-do-mode’ and ‘global-xht-fontify-mode’. - Half a dozen regular functions: - The type-checking predicate ‘h?’. - The ubiquitous hash-table creator ‘h*’. - The hash-table equality predicates ‘h=’, ‘h==’, ‘h_=’, ‘h~=’. Now, complementing the previous conventions, you'll notice that some suffixes are consistently employed: - ? :: Functions that have an ? as suffix are *predicates* (they \"ask\" a yes-or-no question and return t or nil). They have a corresponding -p alias. - = :: Functions that have an = as suffix are *equality predicates* and therefore check whether two or more things can be considered equivalent according to some standard. - * :: Functions that act on *nested hash tables* have an * as suffix, and vice-versa. (With the notable exception of ‘h*’, whose * has a slightly different, but hopefully obvious enough, connotation.) - ! :: Functions that *destructively modify* the table that is passed as argument have an ! as suffix, and vice-versa. *There are no !-less aliases for destructive functions*: wherever the ! is dropped, it means it's side-effect free: the value of the result of the operations is returned, and TABLE remains unmodified. If you find it happening otherwise, it's most likely unintentional and a bug — please report. Note that this differs from ht's naming convention, in which ‘ht-clear’, ‘ht-remove’, ‘ht-set’, and ‘ht-update’ remain as aliases to, respectively, ‘ht-clear!’, ‘ht-remove!’, ‘ht-set!’, and ‘ht-update!’ — all of which destructive functions. (But ‘ht-reject’ is not an alias to ‘ht-reject!’: the former is non-destructive, the latter is destructive.) On the other hand, xht follows this convention consistently. For example: | Non-destructive | Destructive | |-----------------+-------------| | ‘h-rej’ | ‘h-rej!’ | | ‘h-sel’ | ‘h-sel!’ | | ‘h-put’ | ‘h-put!’ | | ‘h-mix’ | ‘h-mix!’ | | ‘h-mix*’ | ‘h-mix*!’ | etc. The library itself may be called either XHT or xht. Both are fine. Mixed casing is better avoided. Yes, you'll see it titleized in the docstrings of ‘global-xht-do-mode’ and ‘global-xht-fontify-mode’ — but only because these docstrings happen to be automatically generated by ‘define-globalized-minor-mode’, which calls ‘easy-mmode-pretty-mode-name’, which titleizes names.") ;;;; Functions ;;;;; General hash table operations ;;;;;; Creation (xht--describe "Functions that create a hash table.") ;;;;;;; Empty one (xht--describe "Functions that create an empty hash table.") (define-inline h-new (&optional sz ts we rs rt) "Create empty hash table. Arguments passed correspond to, respectively, size, test, weakness, rehash-size, rehash-threshold. When nil, they match that of TABLE. SZ is the initial assigned size. When nil, default to 65. TS is the function used to compare the hash keys. It can be ‘eq’, ‘eql’, ‘equal’ or a user-supplied test created via ‘define-hash-table-test’. When nil, default to ‘equal’. The other three default to Emacs' defaults: - WE defaults to nil. - RS defaults to 1.5. - RT defaults to 0.8125. This is similar to ‘ht-create’. The differences are: - size as a first optional argument, test as second; - possibility of passing other parameters accepted by ‘make-hash-table’, which see. See also: ‘h-clr’ and ‘h-clr!’." (inline-quote (make-hash-table :size (or ,sz 65) :test (or ,ts 'equal) :weakness ,we :rehash-size (or ,rs 1.5) :rehash-threshold (or ,rt 0.8125)))) (define-inline h-empty-clone (table &optional sz ts we rs rt) "Create empty hash table with some or all properties from TABLE. Arguments passed correspond to, respectively, size, test, weakness, rehash-size, rehash-threshold. When nil, they match that of TABLE. This is a non-destructive function. See also: ‘h-clr’ and ‘h-clr!’." (inline-quote (make-hash-table :size (or ,sz (hash-table-size ,table)) :test (or ,ts (hash-table-test ,table)) :weakness (or ,we (hash-table-weakness ,table)) :rehash-size (or ,rs (hash-table-rehash-size ,table)) :rehash-threshold (or ,rt (hash-table-rehash-threshold ,table))))) (define-inline h-clr (table) "Create empty hash table whose properties match those of TABLE. It preserves all of the properties of the original table — except data, which is empty. It's therefore particularly useful as a basis for other hash table functions (such as ‘h-hmap*’) that perform non-destructive operations — for which a fresh empty table must be created, and for which the use of ‘h-new’ or ‘ht-create’ would result in a loss of these properties. This is a non-destructive function. Its destructive counterpart is ‘h-clr!’." (declare (pure t) (side-effect-free t)) (inline-quote (make-hash-table :size (hash-table-size ,table) :test (hash-table-test ,table) :weakness (hash-table-weakness ,table) :rehash-size (hash-table-rehash-size ,table) :rehash-threshold (hash-table-rehash-threshold ,table)))) ;; ^ We could just (defsubst h-clr (table) (h-empty-clone table)) ;; but this function is a bit too fundamental, so I'm optimizing it. (define-inline h-clr! (table) "Remove all keys from TABLE. This is equivalent to current ‘ht-clear!’. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-clr’." (inline-quote (ignore (clrhash ,table)))) ;;;;;;; Cloning and copying (xht--describe "See ‘h-clone*’ and ‘h<-ht’.") ;;;;;;; From keys and values (xht--describe "Functions to create a hash table from keys and values.") ;;;;;;;; passed directly (defun h* (&rest pairs) "Create a hash table with the key–value PAIRS. Keys are compared with ‘equal’. This function is similar to ‘ht’, with the difference that no parentheses are used around each key–value pair: so its syntax is identical to that used to create a plist, but replacing ‘list’ with ‘h*’. Also, caution: with plists and alists, repeated items that come first have priority, whereas when passing pairs to ‘ht’ or ‘h*’. it's the opposite — the repeated item's last value wins. Compare: #+begin_src emacs-lisp (plist-get \\='(:a 1 :b 2 :a 5) :a) => 1 (ht-get (h* :a 1 :b 2 :a 5) :a) => 5 (ht-get (ht (:a 1) (:b 2) (:a 5)) :a) => 5 #+end_src Another difference is that, instead of 65, its initial nominal size defaults to the result of ‘xht--init-size’, which determines initial nominal size as a function of the number of pairs. Currently, ‘xht--init-size’ uses ‘xht--init-size-exact’, which returns the exact number of pairs. \(fn KEY-1 VALUE-1 KEY-2 VALUE-2 ...)" (declare (pure t) (side-effect-free t)) (let ((len (length pairs))) (if (= 0 (% len 2)) (let ((tbl (h-new (xht--init-size (/ len 2))))) (while pairs (h-put! tbl (pop pairs) (pop pairs))) tbl) (signal 'wrong-number-of-arguments '((3 . 3) 2))))) (defun h-s* (size &rest pairs) "Create a hash table of size SIZE with the key–value PAIRS. This is exactly like ‘h*’ (which see), with the difference that its nominal size is explicitly passed as first argument instead of being inferred. When SIZE is nil, it defaults to h*'s default. \(fn SIZE KEY-1 VALUE-1 KEY-2 VALUE-2 ...)" (declare (pure t) (side-effect-free t)) (let ((len (length pairs))) (if (= 0 (% len 2)) (let ((tbl (h-new (or size (xht--init-size (/ len 2))) nil))) (while pairs (h-put! tbl (pop pairs) (pop pairs))) tbl) (signal 'wrong-number-of-arguments '((3 . 3) 2))))) (defun h-t* (test &rest pairs) "Create a hash table of function TEST with the key–value PAIRS. This is exactly like ‘h*’ (which see), with the difference that its test function is explicitly passed as first argument instead of being inferred. When TEST is nil, it defaults to h*'s default, which is h-new's default: \\='equal. \(fn TEST KEY-1 VALUE-1 KEY-2 VALUE-2 ...)" (declare (pure t) (side-effect-free t)) (let ((len (length pairs))) (if (= 0 (% len 2)) (let ((tbl (h-new (xht--init-size (/ len 2)) test))) (while pairs (h-put! tbl (pop pairs) (pop pairs))) tbl) (signal 'wrong-number-of-arguments '((3 . 3) 2))))) (defun h-st* (size test &rest pairs) "Create a hash table of SIZE and TEST with the key–value PAIRS. This is exactly like ‘h*’ (which see), with the difference that its nominal size and its test function are explicitly passed as first arguments instead of being inferred. When SIZE is nil, it defaults to h*'s default. When TEST is nil, it defaults to h*'s default, which is h-new's default: \\='equal. \(fn SIZE TEST KEY-1 VALUE-1 KEY-2 VALUE-2 ...)" (declare (pure t) (side-effect-free t)) (let ((len (length pairs))) (if (= 0 (% len 2)) (let ((tbl (h-new (or size (xht--init-size (/ len 2))) test))) (while pairs (h-put! tbl (pop pairs) (pop pairs))) tbl) (signal 'wrong-number-of-arguments '((3 . 3) 2))))) (defun h-reduce-r (&rest keys-value) "Return a one-key nested table with the KEYS-VALUE sequence. So, for example, (h-reduce-r :a :b :c 2) is equivalent to (h* :a (h* :b (h* :c 2))), which is the same as (ht (:a (ht (:b (ht (:c 2))))))." (declare (pure t) (side-effect-free t)) (pcase (length keys-value) ((pred (<= 2)) (--reduce-r (h* it acc) keys-value)) (1 (h* keys-value)) ;; wrong-number-of-arguments error (0 (h-new 1)))) ;;;;;;;; passed as vectors (defmacro h--zip-vectors-with (key-form val-form keys values &optional size test) "Anaphoric version of ‘h-zip-vectors-with’. The form KEY-FORM modifies each key. The form VAL-FORM modifies each value. Each element of KEYS is bound to the symbol key. Each element of VALUES is bound to the symbol value. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. The (roughly) inverse of this function is ‘h--vunzip-with’." (let ((lk (make-symbol "length-of-keys")) (lv (make-symbol "length-of-values")) (i (make-symbol "i")) (r (make-symbol "results"))) `(if ,keys (and (vectorp ,keys) (vectorp ,values) (let* ((,lk (length ,keys)) (,lv (length ,values)) (,i 0) (,r (h-new (or ,size (xht--init-size ,lk)) ,test)) key value) (ignore key value) (while (< ,i ,lk) (setq key (aref ,keys ,i) value (when (< ,i ,lv) (aref ,values ,i)) ,i (1+ ,i)) (h-put! ,r ,key-form ,val-form)) ,r)) (h-new 1)))) (defun h-zip-vectors-with (key-fun val-fun keys values &optional size test) "Create a hash table with KEYS and VALUES modified by functions. The function KEY-FUN modifies each key. The function VAL-FUN modifies each value. Both functions take two variables: key and value, respectively. Each KEY and VALUE is taken one by one from their corresponding positions at the vectors KEYS and VALUES. If KEYS is nil, return empty hash table. If KEYS is shorter than VALUES, ignore additional elements of VALUES. If VALUES is shorter than KEYS, complete VALUES with nils. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. The anaphoric version of this function is ‘h--zip-vectors-with’. The (roughly) inverse of this function is ‘h-vunzip-with’." (declare (pure t) (side-effect-free t)) (h--zip-vectors-with (funcall key-fun key value) (funcall val-fun key value) keys values size test)) (defun h-zip-vectors (keys values &optional size test) "Create a hash table with KEYS and VALUES. Both must be vectors. This is just like ‘h-zip-vectors-with’, but without modifying any of the keys and values before putting them on the table. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. The (roughly) inverse of this function is ‘h-vunzip’." (declare (pure t) (side-effect-free t)) (h--zip-vectors-with key value keys values size test)) ;;;;;;;; passed as lists (defmacro h--zip-lists-with (key-form val-form keys values &optional size test) "Anaphoric version of ‘h-zip-lists-with’. The form KEY-FORM modifies each key. The form VAL-FORM modifies each value. Each element of KEYS is bound to the symbol key. Each element of VALUES is bound to the symbol value. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. The (roughly) inverse of this function is ‘h--lunzip-with’." (let ((ks (make-symbol "keys")) (vs (make-symbol "values")) (lk (make-symbol "length-of-keys")) (r (make-symbol "results"))) `(if ,keys (and (listp ,keys) (listp ,values) (let* ((,ks (-copy ,keys)) (,vs (-copy ,values)) (,lk (length ,ks)) (,r (h-new (or ,size (xht--init-size ,lk)) ,test)) key value) (ignore key value) (while ,ks (setq key (car ,ks) value (car ,vs)) (h-put! ,r ,key-form ,val-form) (!cdr ,ks) (!cdr ,vs)) ,r)) (h-new 1)))) (defun h-zip-lists-with (key-fun val-fun keys values &optional size test) "Create a hash table with KEYS and VALUES modified by functions. The function KEY-FUN modifies each key. The function VAL-FUN modifies each value. Both functions take two variables: key and value, respectively. Each KEY and VALUE is taken one by one from their corresponding positions at the lists KEYS and VALUES. If KEYS is nil, return empty hash table. If KEYS is shorter than VALUES, ignore additional elements of VALUES. If VALUES is shorter than KEYS, complete VALUES with nils. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. The anaphoric version of this function is ‘h--zip-lists-with’. The (roughly) inverse of this function is ‘h-lunzip-with’." (declare (pure t) (side-effect-free t)) (h--zip-lists-with (funcall key-fun key value) (funcall val-fun key value) keys values size test)) (defun h-zip-lists (keys values &optional size test) "Create a hash table with KEYS and VALUES. Both must be lists. This is just like ‘h-zip-lists-with’, but without modifying any of the keys and values before putting them on the table. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. The (roughly) inverse of this function is ‘h-lunzip’." (declare (pure t) (side-effect-free t)) (h--zip-lists-with key value keys values size test)) ;;;;;;;; helper: determine initial size (defalias 'xht--init-size 'xht--init-size-exact "Function used to determine the initial size of a hash table. If at some point we decide that a function exists which is more appropriate for the initial nominal size of a hash table to be created from a given number of pairs, refactoring is made easier by simply changing this alias to point to that new, supposedly better, xht--init-size-<something> function.") (defsubst xht--init-size-exact (n-pairs) "Return N-PAIRS, the exact number of pairs expected. The rationale is that the initial number of pairs will be, on average, a much better estimate of how much space the table needs. For tables (sometimes much) smaller than 65, this leads to better performance, especially when the table won't be updated with more items (or when only a few)." n-pairs) (defsubst xht--init-size-fixed (&optional n-pairs) "Return 65, Emacs' default nominal creation size for hash tables. Ignore N-PAIRS." (ignore n-pairs) 65) ;;;;;; Binding (xht--describe "Functions to bind the values of a hash table to variables. The main and the helper functions of ‘h-let’ were inspired by, and adapted from, ‘let-alist’.") ;;;;;;; Dot-binding (h-let and h-let-it) ;; ‘xht-do’ uses ‘h-let’, and this compiler error happened at the former: ;; Error: Symbol’s function definition is void: ‘xht--let-deep-dot-search’ ;; Adding an autoload to this private function solved it, but so did wrapping ;; all four in ‘eval-and-compile’, which seems preferable. (eval-and-compile (defun xht--let-deep-dot-search (data) "Return alist of symbols inside DATA that start with a '.'. Perform a deep search and return an alist where each car is the symbol, and each cdr is the same symbol without the '.'." (declare (pure t) (side-effect-free t)) (cond ((symbolp data) (let ((name (symbol-name data))) (when (string-match "\\`\\." name) ;; Return the cons cell inside a list, so it can be appended ;; with other results in the clause below. (list (cons data (intern (replace-match "" nil nil name))))))) ((not (consp data)) nil) (t (append (xht--let-deep-dot-search (car data)) (xht--let-deep-dot-search (cdr data)))))) (defun xht--let-access-sexp (symbol variable) "Return a sexp used to access SYMBOL inside VARIABLE." (declare (pure t) (side-effect-free t)) (let* ((clean (xht--let-remove-dot symbol)) (name (symbol-name clean))) (if (string-match "\\`\\." name) clean (xht--let-list-to-sexp (mapcar #'intern (nreverse (split-string name "\\."))) variable)))) (defun xht--let-list-to-sexp (list var) "Turn symbols LIST into recursive calls to ‘gethash’ on VAR. The symbol will be successively searched also formatted as string, keyword, number." (declare (pure t) (side-effect-free t)) (let* ((tbl (if (cdr list) (xht--let-list-to-sexp (cdr list) var) var)) (cl (car list)) ;symbol (scl (format "%s" cl)) ;string (kcl (intern (concat ":" scl))) ;keyword (rcl (read scl)) (ncl (when (numberp rcl) rcl))) ;maybe number `(or (gethash ',cl ,tbl) (gethash ,scl ,tbl) (gethash ,kcl ,tbl) (gethash ,ncl ,tbl)))) (defun xht--let-remove-dot (symbol) "Return SYMBOL without the initial dot." (declare (pure t) (side-effect-free t)) (let ((name (symbol-name symbol))) (if (string-match "\\`\\." name) (intern (replace-match "" nil nil name)) symbol)))) ;;;###autoload (defmacro h-let (table &rest body) "Let-bind dotted symbols to their value in TABLE and execute BODY. Dotted symbol is any symbol starting with a dot. Only those present in BODY are let-bound, and this search is done at compile time. For instance, the following code: #+begin_src emacs-lisp (h-let table (if (and .title .body) .body .site .site.contents)) #+end_src essentially expands to: #+begin_src emacs-lisp (let ((.title (h-get \\='title table)) (.body (h-get \\='body table)) (.site (h-get \\='site table)) (.site.contents (h-get \\='contents (h-get \\='site table)))) (if (and .title .body) .body .site .site.contents)) #+end_src That is still somewhat simplified, however. In fact, this line: #+begin_src emacs-lisp (h-get \\='title table) #+end_src looks more like: #+begin_src emacs-lisp (or (h-get \"title\" table) (h-get \\='title table) (h-get :title table)) #+end_src so that your query should work regardless of whether the key is a string, a symbol, or a keyword. (Or even number.) Note that if you mix these types in the keys and two keys happen to share the same word (such as having both \\='title and :title as keys of the same table), results will be unpredictable. But this case should be as rare as it's frequent the need for type autodetection in a given one-typed-key hash table. If you nest ‘h-let’ invocations, the inner one can't access the variables of the outer one. You can, however, access nested hash tables by using dots inside the symbol, as showed in the example above. This function was inspired by, and adapted from, ‘let-alist’. See also: ‘h-let-it’." (declare (indent 1) (debug t)) (let ((var (make-symbol "table"))) `(let ((,var ,table)) (let ,(mapcar (lambda (x) `(,(car x) ,(xht--let-access-sexp (car x) var))) (delete-dups (xht--let-deep-dot-search body))) ,@body)))) ;;;###autoload (defmacro h-let-it (thing &rest body) "Let-bind dotted symbols to their value in THING and execute BODY. Behind the scenes, it converts key–value THING to hash table using ‘h<-it’, and then dot-binds the result using ‘h-let’." (declare (indent 1) (debug t)) `(h-let (h<-it ,thing) ,@body)) ;;;;;; Canonical representations ;;;;;;; Changing canonical representation — from table (xht--describe "Functions returning a canonical form that would've generated a hash table. Note that hash tables will fail equality tests such as: #+begin_src emacs-lisp (eq #s(hash-table test equal data ('a 1)) #s(hash-table test equal data ('a 1))) ;=> nil #+end_src or: #+begin_src emacs-lisp (eql #s(hash-table test equal data ('a 1)) #s(hash-table test equal data ('a 1))) ;=> nil #+end_src or: #+begin_src emacs-lisp (equal #s(hash-table test equal data ('a 1)) #s(hash-table test equal data ('a 1))) ;=> nil #+end_src or: #+begin_src emacs-lisp (equal (ht ('a 1)) (ht ('a 1))) => nil #+end_src But, of course: #+begin_src emacs-lisp (equal '(ht ('a 1)) '(ht ('a 1))) => t (equal '(h* 'a 1) '(h* 'a 1)) => t #+end_src So the comparisons below work. Note: h-htbl-form, not shown here, is just the table itself in regular form: #s(hash-table…)") ;;;;;;;; #s(hash-table…) (defsubst h-htbl-form (table) "Hash TABLE object as itself. Function defined only so we have a parallel with ht and h* forms." (declare (pure t) (side-effect-free t)) table) (defun h-htbl-cm-str (table) "Hash TABLE object as a string in compact representation." (declare (pure t) (side-effect-free t)) (format "%S" table)) (defun h-htbl-pp-str (table) "Hash TABLE object as a string in pretty-printed representation." (declare (pure t) (side-effect-free t)) (pp-to-string table)) ;;;;;;;; (ht…) (defun h-ht-form (table) "Restore canonical (ht…) form that creates hash TABLE. Note that this doesn't preserve TABLE's equality test: (ht…) defaults to \\='equal as test, so if TABLE's test is anything else, it will become \\='equal if the form is evaluated." (declare (pure t) (side-effect-free t)) (let* (result (print-quoted t) (keyvals (dolist (key (h-keys table 'rev) result) (let ((value (h-get table key))) (push (list (xht--quote key) (if (ht? value) (h-ht-form value) ;<-- recurse (if (equal 't value) value (xht--quote value)))) result))))) `(ht ,@keyvals))) (defun h-ht-cm-str (table) "Restore as string canonical (ht…) form that creates hash TABLE. Compact representation." (declare (pure t) (side-effect-free t)) (let ((print-quoted t)) (->> table h-ht-form (format "%S")))) (defun h-ht-pp-str (table) "The pretty-print string of (ht…) form that creates hash TABLE." (with-temp-buffer (emacs-lisp-mode) (let ((print-quoted t)) (->> table h-ht-form pp s-trim (replace-regexp-in-string "(ht\n *" "(ht ") (replace-regexp-in-string "\\([^)]\\)\n *" "\\1 ") insert) (indent-region (point-min) (point-max)) (xht--buff-str-no-prop)))) ;;;;;;;; (h*…) and (h-st*…) (defun h-h*-form (table) "Restore canonical (h*…) or (h-st*…) form that creates hash TABLE. If TABLE's test is: - \\='equal, we use (h*…), which preserves it. Note that if this form is evaluated the nominal size of that table will be determined by ‘h*’. (This is unlikely to be an issue.) - not \\='equal, we recover its nominal size and equality test and use (h-st*…)." (declare (pure t) (side-effect-free t)) (let* (result (print-quoted t) (keyvals (dolist (key (h-keys table 'rev) result) (let ((value (h-get table key))) (push (if (ht? value) (h-h*-form value) ;<-- recurse (if (equal 't value) value (xht--quote value))) result) (push (xht--quote key) result))))) (if (equal 'equal (h-prop table 'test)) `(h* ,@keyvals) `(h-st* ,(h-prop table 'size) ',(h-prop table 'test) ,@keyvals)))) (defun h-h*-cm-str (table) "Restore as string (h*…) or (h-st*…) form that creates hash TABLE. Compact representation." (declare (pure t) (side-effect-free t)) (->> table h-h*-form (format "%S"))) (defun h-h*-pp-str (table) "The pretty-print string of (h*…) form that creates hash TABLE. If table's test isn't \\='equal, use (h-st*…) form." (let ((print-quoted t)) (with-temp-buffer (with-syntax-table emacs-lisp-mode-syntax-table (->> table h-h*-cm-str s-trim insert) (goto-char (point-min)) (while (not (eobp)) (cond ((or (looking-at " *(h\\* ") (looking-at " *(h-st\\* ")) (goto-char (scan-lists (point) 1 -1)) (goto-char (scan-sexps (point) 2))) (t (goto-char (scan-sexps (point) 1)) (when (looking-at " *)") (re-search-forward " *)+" nil t)) (re-search-forward " +$" nil t) (unless (eobp) (insert "\n" (let ((sz (save-excursion (backward-sexp 2) (1- (current-column))))) (if (>= sz 0) (make-string sz ?\s) ""))) (goto-char (scan-sexps (point) 1)))))) (xht--buff-str-no-prop))))) ;;;;;;; Changing canonical representation — from any ht-like form (xht--describe "Choose different representations of hash tables.") (defun h-lambdify (ht-like) "Convert an HT-LIKE object into a lambda that creates the table. HT-LIKE may come in any of the following autodetected formats: As Lisp objects: 1. #s(hash-table…) = regular representation 2. (ht…) form 3. (h*…), (h-s*…), (h-t*…), or (h-st*…) form or any of the above formatted as string (presumably with %S)." (pcase (xht--ht-like-format-type ht-like) ((or :ht-str :h*-str :h-s*-str :h-t*-str :h-st*-str) `(lambda () ,(read ht-like))) ((or :ht-form :h*-form :h-s*-form :h-t*-form :h-st*-form :htbl) `(lambda () ,ht-like)) (:no-hash-table (user-error "‘h-lambdify’: Not hash-table–like — won't lambdify")))) (defun h-format (format ht-like) "Format as FORMAT hash table–like object HT-LIKE. HT-LIKE may come in any of the following autodetected formats: As Lisp objects: 1. #s(hash-table…) = regular representation 2. (ht…) form 3. (h*…), (h-s*…), (h-t*…), or (h-st*…) form or as strings: Any of the above formatted as string (presumably with %S) The output FORMAT, in turn, must be specified as one of: 1. htbl :: #s(hash-table…) (Lisp object) 2. h*-form :: (h*…) form (Lisp object) 3. ht-form :: (ht…) form (Lisp object) 4. htbl-cm-str :: #s(hash-table…) string in compact format 5. htbl-pp-str :: #s(hash-table…) string in pretty-printed format 6. h*-cm-str :: (h*…) string in compact format 7. h*-pp-str :: (h*…) string in pretty-printed format 8. ht-cm-str :: (ht…) string in compact format 9. ht-pp-str :: (ht…) string in pretty-printed format Any of these FORMAT options may be passed as either a string, a keyword, or a symbol — as you prefer. So, for example, any among \"htbl\", :htbl, or \\='htbl works." (declare (pure t) (side-effect-free t)) (let ((as-htbl (pcase (xht--ht-like-format-type ht-like) ((or :ht-str :h*-str :h-s*-str :h-t*-str :h-st*-str) (-> ht-like read xht--dequote-list eval)) ;; it's likely not 'quoted in the str, ^ but it could be. ((or :ht-form :h*-form :h-s*-form :h-t*-form :h-st*-form) (-> ht-like xht--dequote-list eval)) (:htbl-str (-> ht-like read)) (:htbl ht-like) (:no-hash-table (user-error "‘h-format’: Not a hash table: %s" ht-like))))) (pcase (h-as-keyword format) (:htbl as-htbl) (:h*-form (h-h*-form as-htbl)) (:ht-form (h-ht-form as-htbl)) (:htbl-cm-str (h-htbl-cm-str as-htbl)) (:htbl-pp-str (h-htbl-pp-str as-htbl)) (:h*-cm-str (h-h*-cm-str as-htbl)) (:h*-pp-str (h-h*-pp-str as-htbl)) (:ht-cm-str (h-ht-cm-str as-htbl)) (:ht-pp-str (h-ht-pp-str as-htbl)) (_ (user-error "‘h-format’: Not a reformat output option"))))) ;;;;;;;; helper (defun xht--ht-like-format-type (ht-like) "Return type of hash table representation of HT-LIKE." (declare (pure t) (side-effect-free t)) (let* ((str? (stringp ht-like)) (lispobj (if str? (read ht-like) ht-like))) ;; Note: when string we won't test whether it's pretty-printed or not. ;; This is a bit tricky, so we'll take a simpler route. (cond ((h? lispobj) (if str? :htbl-str :htbl)) ((listp lispobj) (setq lispobj (xht--dequote-list lispobj)) ;; it could be '''multiply-quoted^, so we remove excesses. (pcase (car lispobj) ('ht (if str? :ht-str :ht-form)) ('h* (if str? :h*-str :h*-form)) ('h-s* (if str? :h-s*-str :h-s*-form)) ('h-t* (if str? :h-t*-str :h-t*-form)) ('h-st* (if str? :h-st*-str :h-st*-form)) ;; ^note that 'st' is about 'size' and 'test': nothing ;; to do with 'string', as is the case of '-str'. (_ :no-hash-table))) (t :no-hash-table)))) ;;;;;; Mapping from hash table ;;;;;;; to vector (xht--describe "Functions that apply a function to every key–value pair of a hash table and write the results to a vector. In particular, here's a guideline for the 'vmap family' of functions: - the added 'v' means 'result is a vector' - an added '-' means 'anaphoric: enter a form, not a lambda' In the anaphoric version, if you don't use any or either of the let-bound variables key and value in any or either of the forms, it's ok — no warnings. Summary: Side-effect-free: don't modify original TABLE, return a vector |----------------------+--------------+---------------------| | | a lambda | a form (anaphoric) | |----------------------+--------------+---------------------| | vector may have nils | ‘h-vmap’ | ‘h--vmap’ | |----------------------+--------------+---------------------|") (defun h-vmap (fun table &optional reverse) "Apply FUN to each key–value pair of TABLE and return vector. Function FUN is called with two arguments, key and value. If REVERSE is non-nil, show it in reverse order. If you don't use both of these variables, then to avoid warnings use an underscore before the to-be-ignored variable. For example, to return a vector of all (expected to be integer) values plus 1: #+begin_src emacs-lisp (h-vmap (lambda (_key value) (1+ value)) table) #+end_src Its anaphoric counterpart is ‘h--vmap’." (declare (pure t) (side-effect-free t)) (let* ((size (h-size table)) (results (make-vector size nil)) (pos (if reverse (lambda (idx) (- size idx 1)) (lambda (idx) idx))) (idx 0)) (maphash (lambda (key value) (aset results (funcall pos idx) (funcall fun key value)) (setq idx (1+ idx))) table) results)) (defmacro h--vmap (form table &optional reverse) "Anaphoric version of ‘h-vmap’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. Results given as vector. If REVERSE is non-nil, show it in reverse order." `(h-vmap (lambda (key value) (ignore key value) ,form) ,table ,reverse)) (defmacro h--vunzip-with (key-form val-form table &optional reverse) "Anaphoric version of ‘h-vunzip-with’. The form KEY-FORM modifies each key. The form VAL-FORM modifies each value. Both forms take two variables: key and value, respectively. Each key and value is taken one by one from their corresponding pairs at the hash table TABLE. If REVERSE is non-nil, reverse the order of both vectors. The (roughly) inverse of this function is ‘h-zip-vectors’. See also: ‘h--lunzip-with’." (let ((sz (make-symbol "size")) (ks (make-symbol "keys")) (vs (make-symbol "values")) (p (make-symbol "pos")) (i (make-symbol "i"))) `(let* ((,sz (h-size ,table)) (,ks (make-vector ,sz nil)) (,vs (make-vector ,sz nil)) (,p (if ,reverse (lambda (i) (- ,sz i 1)) (lambda (i) i))) (,i 0)) (maphash (lambda (key value) (ignore key value) (aset ,ks (funcall ,p ,i) ,key-form) (aset ,vs (funcall ,p ,i) ,val-form) (setq ,i (1+ ,i))) ,table) (list ,ks ,vs)))) (defun h-vunzip-with (key-fun val-fun table &optional reverse) "Return a list of modified vectors of KEYS and VALUES from TABLE. If REVERSE is non-nil, reverse the order of both vectors. The function KEY-FUN modifies each key in the results. The function VAL-FUN modifies each value in the results. Both functions take two variables: key and value, respectively. Each key and value is taken one by one from their corresponding pairs at the hash table TABLE. The (roughly) inverse of this function is ‘h-zip-vectors’. Its anaphoric counterpart is ‘h--vunzip-with’. See also: ‘h-lunzip-with’." (declare (pure t) (side-effect-free t)) (h--vunzip-with (funcall key-fun key value) (funcall val-fun key value) table reverse)) (defun h-vunzip (table &optional reverse) "Return a list of the vectors of KEYS and VALUES from TABLE. If REVERSE is non-nil, reverse the order of both vectors. The (roughly) inverse of this function is ‘h-zip-vectors’. See also: ‘h-lunzip’." (declare (pure t) (side-effect-free t)) (h--vunzip-with key value table reverse)) (defun h-vitems (table &optional reverse) "Return a vector of two-element lists \\='(key value) from TABLE. If REVERSE is non-nil, show it in reverse order (the vector; the pair remains in the (key, value) order)." (declare (pure t) (side-effect-free t)) (h--vmap (list key value) table reverse)) (defmacro h-vkeys (table &optional reverse) "Return a vector of all the keys in TABLE. If REVERSE is non-nil, show it in reverse order." (declare (pure t) (side-effect-free t)) `(h--vmap key ,table ,reverse)) (defmacro h-vvalues (table &optional reverse) "Return a vector of all the values in TABLE. If REVERSE is non-nil, show it in reverse order." (declare (pure t) (side-effect-free t)) `(h--vmap value ,table ,reverse)) (defun h-vrandom (table &optional n) "Return a vector of N pseudo-randomly chosen items from hash TABLE. N must be an integer; if not, signal error. - If N is nil, make it N=1. - If N≥ size(TABLE), return a vector of all TABLE's items (same result as running ‘h-vitems’). - If N=0, return nil. - If N=1, item is returned as a two-item list (key, value). - If N>1, items are returned as a vector of two-item lists (key, value). - If N is negative, make N = N + size(TABLE). For example, if TABLE has 10 items and N=-2, return 8 pseudo-random items. See also: ‘h-lrandom’." (declare (side-effect-free t)) (let ((size (h-size table))) (cond ((null n) (setq n 1)) ((not (integerp n)) (user-error "‘h-vrandom’: n must be an integer")) ((< n 0) (setq n (+ size n)))) (cond ((= n 0) nil) ((>= n size) (h-vitems table)) (t (let ((sel-keys (make-vector n nil)) (keys (h-keys table)) idx key) (while (> n 0) (setq idx (random (length keys)) key (nth idx keys) keys (-remove-at idx keys) n (1- n)) (aset sel-keys n (list key (h-get table key)))) sel-keys))))) ;;;;;;; to list (xht--describe "Functions that apply a function to every key–value pair of a hash table and write the results to a list. In particular, here's a guideline for the 'lmap/lkeep family' of functions: - the added 'l' means 'result is a list' - an added '-' means 'anaphoric: enter a form, not a lambda' In the anaphoric versions, if you don't use any or either of the let-bound variables key and value in any or either of the forms, it's ok — no warnings. Summary: Side-effect-free: don't modify original TABLE, return a list |----------------------+--------------+---------------------| | | a lambda | a form (anaphoric) | |----------------------+--------------+---------------------| | list may have nils | ‘h-lmap’ | ‘h--lmap’ | | any nils are removed | ‘h-lkeep’ | ‘h--lkeep’ | |----------------------+--------------+---------------------|") (defun h-lmap (fun table &optional reverse) "Apply FUN to each key–value pair of TABLE; list the results. Function FUN is called with two arguments, key and value. Identical to current ‘ht-map’, with the difference that the returned list matches the current order of the TABLE's keys. If REVERSE is non-nil, show it in reverse order. If you don't use both of these variables, then to avoid warnings use an underscore before the to-be-ignored variable. For example, to return a list of all (expected to be integer) values plus 1: #+begin_src emacs-lisp (h-lmap (lambda (_key value) (1+ value)) table). #+end_src Its anaphoric counterpart is ‘h--lmap’. See also: ‘h-vmap’." ;; Note: Using ‘nreverse’ after push seemed to me as more natural for ;; mapping, especially when one had control of the order of items added to ;; the hash table. ;; ;; The cost seems worth it, since ‘nreverse’ takes less than a millisecond ;; to reverse a list with 100k elements — and I suspect that one dealing ;; with a hash table that large will be unlikely to want to map the whole of ;; it to a list. And when that is the case and 1 ms is too much, pass a ;; non-nil REVERSE flag. ;; ;; For simpler, smaller hash tables, a delay in the order of microseconds ;; won't matter, and the user will likely be more interested in reproducing ;; the items in the order that he or she added. (declare (pure t) (side-effect-free t)) (let (results) (maphash (lambda (key value) (push (funcall fun key value) results)) table) ;; That's right, because ‘push’ reversed it already: (if reverse results (nreverse results)))) (defmacro h--lmap (form table &optional reverse) "Anaphoric version of ‘h-lmap’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. List the results. If REVERSE is non-nil, show it in reverse order. See also: ‘h--vmap’." `(h-lmap (lambda (key value) (ignore key value) ,form) ,table ,reverse)) (defun h-lkeep (fun table &optional reverse) "Apply FUN to each key–value pair of TABLE; list non-nil results. We follow Dash's naming scheme here, for which \"keep\" is just like \"map\" — but with the nils removed. Function FUN is called with two arguments, key and value. If REVERSE is non-nil, show it in reverse order. As with ‘h-lmap’, if you don't use both of these variables, then to avoid warnings use an underscore before the to-be-ignored variable. Its anaphoric counterpart is ‘h--lkeep’." (declare (pure t) (side-effect-free t)) (let (results) (maphash (lambda (key value) (when-let ((fkv (funcall fun key value))) (push fkv results))) table) ;; That's right, because ‘push’ reversed it already: (if reverse results (nreverse results)))) (defmacro h--lkeep (form table &optional reverse) "Anaphoric version of ‘h-lkeep’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. List the non-nil results. If REVERSE is non-nil, show it in reverse order." `(h-lkeep (lambda (key value) (ignore key value) ,form) ,table ,reverse)) (defmacro h--lunzip-with (key-form val-form table &optional reverse) "Anaphoric version of ‘h-lunzip-with’. The form KEY-FORM modifies each key. The form VAL-FORM modifies each value. Each element of KEYS is bound to the symbol key. Each element of VALUES is bound to the symbol value. Each key and value is taken one by one from their corresponding pairs at the hash table TABLE. If REVERSE is non-nil, reverse the order of both lists. The (roughly) inverse of this function is ‘h-zip-lists’. Alias: ‘h--unzip-with’. See also: ‘h--vunzip-with’." (let ((ks (make-symbol "keys")) (vs (make-symbol "values"))) `(let (,ks ,vs) (maphash (lambda (key value) (ignore key value) (push ,key-form ,ks) (push ,val-form ,vs)) ,table) (list (if ,reverse ,ks (nreverse ,ks)) (if ,reverse ,vs (nreverse ,vs)))))) (defun h-lunzip-with (key-fun val-fun table &optional reverse) "Return a list of modified lists of KEYS and VALUES from TABLE. If REVERSE is non-nil, reverse the order of both lists. The function KEY-FUN modifies each key in the results. The function VAL-FUN modifies each value in the results. Both functions take two variables: key and value, respectively. Each key and value is taken one by one from their corresponding pairs at the hash table TABLE. The (roughly) inverse of this function is ‘h-zip-lists’. Its anaphoric counterpart is ‘h--lunzip-with’. Alias: ‘h-unzip-with’. See also: ‘h-vunzip-with’." (declare (pure t) (side-effect-free t)) (h--lunzip-with (funcall key-fun key value) (funcall val-fun key value) table reverse)) (defun h-lunzip (table &optional reverse) "Return a list of the lists of KEYS and VALUES from TABLE. If REVERSE is non-nil, reverse the order of both lists. The (roughly) inverse of this function is ‘h-zip-lists’. Alias: ‘h-unzip’. See also: ‘h-vunzip’." (declare (pure t) (side-effect-free t)) (h--lunzip-with key value table reverse)) (defun h-litems (table &optional reverse) "Return a list of two-element lists \\='(key value) from TABLE. Like ‘ht-items’, but in TABLE's stored order by default. If REVERSE is non-nil, show it in reverse order (the larger list; the pair remains in the (key, value) order). Alias: ‘h-items’." (declare (pure t) (side-effect-free t)) (h--lmap (list key value) table reverse)) (defun h-lkeys (table &optional reverse) "Return a list of all the keys in TABLE. Like ‘ht-keys’, but in TABLE's stored order by default. If REVERSE is non-nil, show it in reverse order. Aliases: ‘h-keys’, ‘h-2d-keys’." (declare (pure t) (side-effect-free t)) (h-lmap (lambda (key _value) key) table reverse)) (defun h-lvalues (table &optional reverse) "Return a list of all the values in TABLE. Like ‘ht-values’, but in TABLE's stored order. If REVERSE is non-nil, show it in reverse order. Alias: ‘h-values’." (declare (pure t) (side-effect-free t)) (h-lmap (lambda (_key value) value) table reverse)) (defun h-lrandom (table &optional n) "Return a list of N pseudo-randomly chosen items from hash TABLE. N must be an integer; if not, signal error. - If N is nil, make it N=1. - If N≥ size(TABLE), return a list of all TABLE's items in pseudo-random order. (like ‘h-litems’, but shuffled). - If N=0, return nil. - If N=1, item is returned as a two-item list (key, value). - If N>1, items are returned as a list of two-item lists (key, value). - If N is negative, make N = N + size(TABLE). For example, if TABLE has 10 items and N=-2, return 8 pseudo-random items. See also: ‘h-vrandom’, ‘h-pop-random’, and ‘h-pop-random!’." (declare (side-effect-free t)) (let ((size (h-size table))) ;; Adjust n: (cond ((null n) (setq n 1)) ((not (integerp n)) (user-error "‘h-lrandom’: n must be an integer")) ((< n 0) (setq n (+ size n))) ((>= n size) (setq n size))) ;; Decide on n: (if (= n 0) nil (let ((keys (h-keys table)) idx key sel-keys) (while (> n 0) (setq idx (random (length keys)) key (nth idx keys) keys (-remove-at idx keys) n (1- n)) (push (list key (h-get table key)) sel-keys)) sel-keys)))) ;;;;;;; to hash table (xht--describe "Functions that apply two functions to every key–value pair of a hash table and write the results to a hash table. For example, if you want to produce a fresh table whose keys \(currently strings) are upcased and the values \(currently integers) are increased by 1, you could run: #+begin_src emacs-lisp (h-hmap (lambda (key _value) (upcase key)) (lambda (_key value) (1+ value)) table) #+end_src The underlines above are so that there's no warnings about unused variables. We do it because we didn't use VALUE in KEY-FUN, nor KEY in VAL-FUN — but this doesn't need to be so: we could want that the new key be also (or instead) a function of the VALUE, and vice-versa. Note that the pair is only added if KEY-FUN returns non-nil. So no spurious (nil, something) pair results from some reasonable conditional KEY-FUN such as: #+begin_src emacs-lisp (lambda (key, value) (when (> value 2) key)) #+end_src In the rare case where for some reason you chose to have nil as a key, you'll have to treat it specially. IMPORTANT: unlike mapping to a list, mapping to a hash table demands that the results of the keys be unique. So you must pay attention to possible collisions. If, for example, in the case above the original table had both \"a\" and \"A\" as original keys, one of them would end up overwritten, because both return \"A\" when upcased. Which one will depend on the key order. So: actual size of resulting table ≤ size of original table. In mathematical terms, excepting the rare case of a nil key in the original, the number of keys in the resulting hash table will match the number of keys in the original one if, and only if: 1. there's a bijection between the set of keys and the set of key-fun(key,value). 2. there's no (key,value) for which key-fun(key,value) returns nil. Guideline for the 'hmap family' of functions: - the added 'h' means 'result is a hash table' - an added '-' means 'anaphoric: enter 2 forms, not 2 lambdas' - an added '*' means 'recurse: apply the values-function or -form to the values whenever these are hash tables' - an added '!' means 'destructive: modify TABLE instead of creating a fresh one' In the anaphoric versions, if you don't use any or either of the let-bound variables key and value in any or either of the forms, it's ok — no warnings. Summary: Side-effect-free: don't modify original TABLE, return fresh table |-------------------------+---------------+---------------------| | | 2 lambdas | 2 forms (anaphoric) | |-------------------------+---------------+---------------------| | simple, doesn't recurse | ‘h-hmap’ | ‘h--hmap’ | | nesting-aware, recurses | ‘h-hmap*’ | ‘h--hmap*’ | |-------------------------+---------------+---------------------| Destructive: modify original TABLE, return nil |-------------------------+---------------+---------------------| | | 2 lambdas | 2 forms (anaphoric) | |-------------------------+---------------+---------------------| | simple, doesn't recurse | ‘h-hmap!’ | ‘h--hmap!’ | | nesting-aware, recurses | ‘h-hmap*!’ | ‘h--hmap*!’ | |-------------------------+---------------+---------------------| See also: ‘h-2d-hmap’, ‘h--2d-hmap’, ‘h-2d-hmap!’, ‘h--2d-hmap!’") ;;;;;;;; non-recursive (defun h-hmap (key-fun val-fun table) "Return a table from applying KEY-FUN, VAL-FUN to each pair of TABLE. While ‘h-lmap’ produces a list, this one produces a hash table. Both KEY-FUN and VAL-FUN are each called with two arguments, KEY and VALUE. - The result of KEY-FUN is the new key. - The result of VAL-FUN is the new value. Its anaphoric counterpart is ‘h--hmap’." (declare (pure t) (side-effect-free t)) (let ((result (h-clr table))) (maphash (lambda (key value) (let ((newk (funcall key-fun key value))) (when newk (puthash newk (funcall val-fun key value) result)))) table) result)) (defmacro h--hmap (key-form val-form table) "Anaphoric version of ‘h-hmap’. The form KEY-FORM modifies each key. The form VAL-FORM modifies each value. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. While ‘h--lkeep’ produces a list, this one produces a hash table." `(h-hmap (lambda (key value) (ignore key value) ,key-form) (lambda (key value) (ignore key value) ,val-form) ,table)) (defun h-hmap! (key-fun val-fun table) "Update TABLE by applying KEY-FUN, VAL-FUN to its pairs. Just like ‘h-hmap’ (which see), but modifies TABLE instead of producing a fresh one. Its anaphoric counterpart is ‘h--hmap!’." (maphash (lambda (key value) (let ((newk (funcall key-fun key value))) (remhash key table) (when newk (puthash newk (funcall val-fun key value) table)))) table)) (defmacro h--hmap! (key-form val-form table) "Anaphoric version of ‘h-hmap!’. For every key–value pair in TABLE, evaluate KEY-FORM and VAL-FORM with the variables key and value bound." `(h-hmap! (lambda (key value) (ignore key value) ,key-form) (lambda (key value) (ignore key value) ,val-form) ,table)) ;;;;;;;; recursive (defun h-hmap* (key-fun val-fun table) "Return a table from applying KEY-FUN, VAL-FUN to each pair of TABLE. Just like ‘h-hmap’, but recurses wherever TABLE is nested. This means that for any VALUE that is itself a hash table, instead of replacing it with the result of VAL-FUN, as ‘h-hmap’ would do, VALUE will be this very ‘h-hmap*’ recursively applied to that hash table with KEY-FUN and VAL-FUN. Its anaphoric counterpart is ‘h--hmap*’." (declare (pure t) (side-effect-free t)) (let ((result (h-clr table))) (maphash (lambda (key value) (let ((newk (funcall key-fun key value))) (when newk (puthash newk (if (ht? value) (h-hmap* key-fun val-fun value) ;; ^ recurses (funcall val-fun key value)) result)))) table) result)) (defmacro h--hmap* (key-form val-form table) "Anaphoric version of ‘h-hmap*’. For every key–value pair in TABLE, evaluate KEY-FORM and VAL-FORM with the variables key and value bound." `(h-hmap* (lambda (key value) (ignore key value) ,key-form) (lambda (key value) (ignore key value) ,val-form) ,table)) (defun h-hmap*! (key-fun val-fun table) "Update TABLE by applying KEY-FUN, VAL-FUN to each of its pairs. Just like ‘h-hmap*’ (which see), but modifies TABLE instead of producing a fresh one. Its anaphoric counterpart is ‘h--hmap*!’." (maphash (lambda (key value) (let ((newk (funcall key-fun key value))) (remhash key table) (when newk (puthash newk (if (ht? value) (h-hmap* key-fun val-fun value) (funcall val-fun key value)) table)))) table)) (defmacro h--hmap*! (key-form val-form table) "Anaphoric version of ‘h-hmap*!’. For every key–value pair in TABLE, evaluate KEY-FORM and VAL-FORM with the variables key and value bound." `(h-hmap*! (lambda (key value) (ignore key value) ,key-form) (lambda (key value) (ignore key value) ,val-form) ,table)) ;;;;;;; for side-effects only (xht--describe "Functions that map over the hash table for producing side-effects somewhere.") (defun h-each (table fun) "Apply function FUN to each key–value pair of TABLE. FUN is called with two arguments: key and value. Intended to be used for side-effects only. Returns nil. This function is similar to current ‘ht-each’ (in turn an alias to ‘maphash’), but with TABLE as the first argument. Its anaphoric counterpart is ‘h--each’." (declare (indent 1)) (maphash fun table)) (defmacro h--each (table &rest body) "Anaphoric version of ‘h-each’. For every key–value pair in TABLE, evaluate BODY with the variables key and value bound. This function is similar to current ‘ht-aeach’. Differences: 1. TABLE is the first argument. 2. BODY instead of FORM, obviating the need of ‘progn’ whenever more than one form is needed. 3. issues no warnings if either KEY or VALUE isn't used." (declare (debug (sexp body)) (indent 1)) `(maphash (lambda (key value) (ignore key value) ,@body) ,table)) ;;;;;; Keys operations ;;;;;;; Retrieval (getting) (xht--describe "Functions that retrieve key–value pairs from tables.") ;;;;;;;; Return value given key (xht--describe "Currently, there are no functions here. - ‘h-get’ is aliased to ‘ht-get’. - ‘h-get*’ is aliased to ‘ht-get*’.") ;;;;;;;; Return the very first pair (pop) (defun h-pop (table) "Return the very first pair from TABLE. Pair is returned as a two-element list: \\='(key value). Note that this function is not destructive: it won't remove the pair from TABLE. For that, use ‘h-pop!’." (catch 'break (maphash (lambda (key value) (throw 'break (list key value))) table))) (defun h-pop! (table) "Return the very first pair from TABLE and remove it. Pair is returned as a two-element list: \\='(key value). If you don't want it removed, use ‘h-pop’ instead." (let ((pop (h-pop table))) (h-rem! table (car pop)) pop)) ;;;;;;;; Return a random pair (defun h-pop-random (table) "Return a pseudo-random pair from TABLE. Pair is returned as a two-element list: \\='(key value). Note that this function is not destructive: it won't remove the pair from TABLE. For that, use ‘h-pop-random!’. See also: ‘h-vrandom’ and ‘h-lrandom’." (let ((goal (random (h-size table))) (idx 0)) (catch 'break (maphash (lambda (key value) (when (= idx goal) (throw 'break (list key value))) (setq idx (1+ idx))) table)))) (defun h-pop-random! (table) "Return a pseudo-random pair from TABLE and remove it. Pair is returned as a two-element list: \\='(key value). If you don't want it removed, use ‘h-pop-random’. See also: ‘h-vrandom’ and ‘h-lrandom’." (let ((pop (h-pop-random table))) (h-rem! table (car pop)) pop)) ;;;;;;;; Return the first pair matching a predicate (defun h-first (fun table) "Return the first pair from TABLE for which FUN returns non-nil. In this case, pair is returned as a two-element list: \\='(key value). Return nil otherwise. Function FUN is called with two arguments, key and value. This function is exactly like current ‘ht-find’." (catch 'break (maphash (lambda (key value) (when (funcall fun key value) (throw 'break (list key value)))) table))) (defmacro h--first (form table) "Anaphoric version of ‘h-first’. Return the first pair from TABLE for which FORM returns non-nil. In this case, pair is returned as a two-element list: \\='(key value). Return nil otherwise. FORM is called with two arguments: key and value." `(catch 'break (maphash (lambda (key value) (ignore key value) (when ,form (throw 'break (list key value)))) ,table))) (defun h-first! (fun table) "Remove from TABLE the first pair for which FUN returns non-nil. In this case, pair is returned as a two-element list: \\='(key value). Return nil otherwise. Function FUN is called with two arguments, key and value." (let ((first (h-first fun table))) (h-rem! table (car first)) first)) (defmacro h--first! (form table) "Anaphoric version of ‘h-first!’. Remove from TABLE the first pair for which FORM returns non-nil. In this case, pair is returned as a two-element list: \\='(key value). Return nil otherwise. FORM is called with two arguments, key and value." (let ((f (make-symbol "first"))) `(let ((,f (h--first ,form ,table))) (h-rem! ,table (car ,f)) ,f))) ;;;;;;; Addition (setting) (xht--describe "Functions that add a key–value pair to tables, either destructively or side-effects-free. Included are those that take a sequence of keys and a final value — for nested hash tables. Same logic applies to put functions as with mix functions — only that it's table + key–value instead of table + tables; and only one kv pair makes sense \(otherwise it'd be either a hash table with the pairs, to which we'd apply mixing; or some alist or plist, which we'd convert to htbl then mix). It may seem a bit odd to \"put a pair non-destructively\", but the logic is the same as that of h-mix, and having an exactly analogous abstraction is useful.") ;;;;;;;; put (xht--describe "Summary: |-------------------------+-----------------+-------------| | | Non-destructive | Destructive | |-------------------------+-----------------+-------------| | simple, doesn't recurse | ‘h-put’ | ‘h-put!’ | | nesting-aware, recurses | ‘h-put*’ | ‘h-put*!’ | |-------------------------+-----------------+-------------| While the destructive ‘h-put!’ is like ‘ht-set!’, there's no non-destructive counterparts in ht library.") (define-inline h-put! (table key value) "Associate KEY in TABLE with VALUE. This is EXACTLY equivalent to current ‘ht-set!’. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-put’. This function is not nesting-aware. Its also-destructive but nesting-aware counterpart is ‘h-put*!’. For nesting-awareness with no side-effects, use ‘h-put*’." (inline-quote (ignore (puthash ,key ,value ,table)))) (defun h-put (table key value) "Return a table that is TABLE with KEY–VALUE applied. This is a side-effect-free function. Its destructive counterpart is ‘h-put!’. This function is not nesting-aware. Its also-side-effect-free but nesting-aware counterpart is ‘h-put*’. For nesting-awareness with side-effects, use ‘h-put*!’." (declare (pure t) (side-effect-free t)) (let ((htbl (h-clone* table))) (h-put! htbl key value) htbl)) (defun h-put*! (table &rest keys-value) "Set VALUE to KEYS sequence in hash table TABLE. KEYS-VALUE are KEYS and VALUE. Each key in KEYS has as value the next, all of which hash tables, except for the final key, which could return any value. This last key will be set to VALUE. If any of the keys in the sequence KEYS can't be found, they will be created on-the-fly. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-put*’. This function is nesting-aware. Its also-destructive but only-first-level counterpart is ‘h-put!’. For no nesting-awareness and no side-effects, use ‘h-put’." (let ((newh (apply #'h-reduce-r keys-value))) (h-mix*! table newh))) (defun h-put* (table &rest keys-value) "Return a table that is TABLE with KEYS–VALUE applied. KEYS-VALUE are KEYS and VALUE. Each key in KEYS has as value the next, all of which hash tables, except for the final key, which could return any value. This last key will be set to VALUE. If any of the keys in the sequence KEYS can't be found, they will be created on-the-fly. This is a side-effect-free function. Its destructive counterpart is ‘h-put*!’. This function is nesting-aware. Its also-side-effect-free but only-first-level counterpart is ‘h-put’. For no nesting-awareness but with side-effects, use ‘h-put!’." (declare (pure t) (side-effect-free t)) (let ((htbl (h-clone* table)) (newh (apply #'h-reduce-r keys-value))) (h-mix* htbl newh))) ;;;;;;;; put-add (xht--describe "While 'put' replaces any current value (call it 'curval') with the newly provided value, 'put-add' adds to it. The idea is that you are adding to a set. So: | Current value | New value | |----------------+----------------------------------------------------| | #s(hash table) | - if VALUE is also a hash table, | | | then (‘h-mix*!’ VALUE CURVAL); | | | - if VALUE is an explicit key–value structure, try | | | to convert it to hash table, then ‘h-mix*!’; | | | - otherwise, don't add: keep CURVAL intact. | | | | | | By 'explicit' I mean: no implicit ones, such as | | | simple list, vector, or string lines. | | | | | | And for simplicity, 'key–value lines' string | | | is also out. | |----------------+----------------------------------------------------| | [some vector] | [VALUE some vector] | | '(some list) | '(VALUE some list) | | nil | VALUE | With anything else, make a list, adding VALUE to its first place. Example: | Current value | New value | |---------------+----------------------| | 42 | '(VALUE 42) | | :keyword | '(VALUE :keyword) | | 'symbol | '(VALUE symbol) | | \"string\" | '(VALUE \"string\") | | etc. | | Summary: |-------------------------+-----------------+---------------| | | Non-destructive | Destructive | |-------------------------+-----------------+---------------| | simple, doesn't recurse | ‘h-put-add’ | ‘h-put-add!’ | | nesting-aware, recurses | ‘h-put-add*’ | ‘h-put-add*!’ | |-------------------------+-----------------+---------------|") (defun h-put-add! (table key value) "Add VALUE to the current value of KEY in hash table TABLE. While ‘h-put!’ replaces any current value (call it \"curval\") with the newly provided value, ‘h-put-add!’ adds to it. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-put-add’. This function is not nesting-aware. Its also-destructive but nesting-aware counterpart is ‘h-put-add*!’. For nesting-awareness with no side-effects, use ‘h-put-add*’." (let* ((curval (ht-get table key)) (newval (xht--put-add curval value))) (h-put! table key newval))) (defun h-put-add (table key value) "Return a table that is TABLE with VALUE added to KEY's current. While ‘h-put’ replaces any current value with the newly provided value, ‘h-put-add’ adds to it. This is a side-effect-free function. Its destructive counterpart is ‘h-put-add!’. This function is not nesting-aware. Its also-side-effect-free but nesting-aware counterpart is ‘h-put-add*’. For nesting-awareness with side-effects, use ‘h-put-add*!’." (declare (pure t) (side-effect-free t)) (let* ((htbl (h-clone* table)) (curval (ht-get table key)) (newval (xht--put-add curval value))) (h-put htbl key newval))) (defun h-put-add*! (table &rest keys-value) "Add VALUE to the current KEYS sequence's value in hash table TABLE. KEYS-VALUE are KEYS and VALUE. While ‘h-put*!’ replaces any current value with the newly provided value, ‘h-put-add*!’ adds to it. If any of the keys in the sequence KEYS can't be found, they will be created on-the-fly. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-put-add*’. This function is nesting-aware. Its also-destructive but only-first-level counterpart is ‘h-put-add!’. For no nesting-awareness and no side-effects, use ‘h-put-add’." (let* ((keys (-butlast keys-value)) (value (-last-item keys-value)) (curval (apply #'ht-get* table keys)) (newval (xht--put-add curval value))) (apply #'h-put*! table (-snoc keys newval)))) (defun h-put-add* (table &rest keys-value) "Return a table that is TABLE with VALUE added to KEYS' current. KEYS-VALUE are KEYS and VALUE. While ‘h-put*’ replaces any current value with the newly provided value, ‘h-put-add*’ adds to it. For more information, please see ‘h-put-add!’. If any of the keys in the sequence KEYS can't be found, they will be created on-the-fly. This is a side-effect-free function. Its destructive counterpart is ‘h-put-add*!’. This function is nesting-aware. Its also-side-effect-free but only-first-level counterpart is ‘h-put-add’. For no nesting-awareness but with side-effects, use ‘h-put-add!’." (declare (pure t) (side-effect-free t)) (let* ((htbl (h-clone* table)) (keys (-butlast keys-value)) (value (-last-item keys-value)) (curval (apply #'ht-get* table keys)) (newval (xht--put-add curval value))) (apply #'h-put* htbl (-snoc keys newval)))) (defun xht--put-add (curval value) "Try to add VALUE to CURVAL depending on types. Helper to ‘h-put-add’ functions, which see." (declare (pure t) (side-effect-free t)) (let ((good (list :ht :lol :alist :plist :cons-pair :orgtbl :json :tsv :csv :ssv)) (no-good (list :vector :list :kvl :lines :empty :null))) (cond ((ht? curval) (let ((type (h-type value))) (cond ((memq type good) (h-mix curval (h<-it value))) ((memq type no-good) curval) ;; What else could it be? ;; Well, we'll keep curval. (t curval)))) ((null curval) value) ((vectorp curval) (->> (append curval nil) (cons value) vconcat)) ((listp curval) (cons value curval)) (t (list value curval))))) ;;;;;;; Removal (xht--describe "Functions that remove a key–value pair from tables, either destructively or side-effects-free. Included are those that take a sequence of keys — for nested hash tables.") ;;;;;;;; rem (xht--describe "Summary: |-------------------------+-----------------+-------------| | | Non-destructive | Destructive | |-------------------------+-----------------+-------------| | simple, doesn't recurse | ‘h-rem’ | ‘h-rem!’ | | nesting-aware, recurses | ‘h-rem*’ | ‘h-rem*!’ | |-------------------------+-----------------+-------------|") (define-inline h-rem! (table key) "Remove KEY from hash table TABLE. This is EXACTLY equivalent to current ‘ht-remove!’. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-rem’. This function is not nesting-aware. Its also-destructive but nesting-aware counterpart is ‘h-rem*!’. For nesting-awareness with no side-effects, use ‘h-rem*’." (inline-quote (remhash ,key ,table))) (defun h-rem (table key) "Return a table that is TABLE with KEY removed. This is a side-effect-free function. Its destructive counterpart is ‘h-rem!’. This function is not nesting-aware. Its also-side-effect-free but nesting-aware counterpart is ‘h-rem*’. For nesting-awareness with side-effects, use ‘h-rem*!’." (declare (pure t) (side-effect-free t)) (let ((htbl (h-clone* table))) (h-rem! htbl key) htbl)) (defun h-rem*! (table &rest keys) "Remove key at end of KEYS sequence from hash table TABLE. Each key in KEYS has as value the next, all of which hash tables, except for the final key, which could return any value. The last will be removed, together with the value associated with it. If any of the keys in the sequence KEYS can't be found, no action is taken. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-rem*’. This function is nesting-aware. Its also-destructive but only-first-level counterpart is ‘h-rem!’. For no nesting-awareness and no side-effects, use ‘h-rem’." (let* ((keys-b (-butlast keys)) (key-v (-last-item keys)) (key-k (with-demoted-errors "h-rem*!: %S" (apply #'ht-get* table keys-b)))) ;; If last key in KEYS is a key of the hash table found by ht-getting* the ;; penultimate key, so remove it; otherwise it doesn't exist, so it can't ;; be removed (and (ht? key-k) (ht-contains? key-k key-v) ;; (memq key-v (ht-keys key-k)) (remhash key-v key-k)))) (defun h-rem* (table &rest keys) "Return a table that is TABLE with key at end of KEYS removed. Each key in KEYS has as value the next, all of which hash tables, except for the final key, which could return any value. The last will be removed, together with the value associated with it. If any of the keys in the sequence KEYS can't be found, no action is taken. This is a side-effect-free function. Its destructive counterpart is ‘h-rem*!’. This function is nesting-aware. Its also-side-effect-free but only-first-level counterpart is ‘h-rem’. For no nesting-awareness but with side-effects, use ‘h-rem!’." (declare (pure t) (side-effect-free t)) (let* ((htbl (h-clone* table)) (keys-b (-butlast keys)) (key-v (-last-item keys)) (key-k (with-demoted-errors "h-rem*: %S" (apply #'ht-get* htbl keys-b)))) ;; If last key in KEYS is a key of the hash table found by ht-getting* the ;; penultimate key, so remove it; otherwise it doesn't exist, so it can't ;; be removed, so return the table as is: (and (ht? key-k) (memq key-v (ht-keys key-k)) (remhash key-v key-k)) htbl)) ;;;;;;; Selection (xht--describe "Functions that create a hash table that has only the specified keys.") ;;;;;;;; sel-keys (xht--describe "Summary: |-------------------------+-----------------+---------------| | | Non-destructive | Destructive | |-------------------------+-----------------+---------------| | simple, doesn't recurse | ‘h-sel-keys’ | ‘h-sel-keys!’ | |-------------------------+-----------------+---------------|") (defun h-sel-keys (table keys) "Return a copy of TABLE with only the pairs specified by KEYS. KEYS is a list. This function is similar to current ‘ht-select-keys’. The main difference is that the copy's size is equal to TABLE's instead of the default 65. All properties are preserved. This is a side-effect-free function. Its destructive counterpart is ‘h-sel-keys!’." (declare (pure t) (side-effect-free t)) (let ((result (h-clr table))) (dolist (key keys result) (unless (equal 'key-not-found (gethash key table 'key-not-found)) (puthash key (gethash key table) result))))) (defun h-sel-keys! (table keys) "Update TABLE to contain only the pairs specified by KEYS. KEYS is a list. This is a destructive function. Its side-effect-free counterpart is ‘h-sel-keys’." (maphash (lambda (key _value) (unless (member key keys) (remhash key table))) table)) ;;;;;;;; sel (xht--describe "Guideline for the 'sel family' of functions: - 'sel' means 'select: a nil in the result of FUN excludes KEY from the results.' - an added '-' means 'anaphoric: enter a form, not a lambda' - an added '*' means 'recurse: apply the values-function or -form to the values whenever these are hash tables' - an added '!' means 'destructive: modify TABLE instead of creating a fresh one' In the anaphoric versions, if you don't use any or either of the let-bound variables key and value in any or either of the forms, it's ok — no warnings. Summary: Side-effect-free: don't modify original TABLE, return a fresh table |-------------------------+-------------+---------------------| | | a lambda | a form (anaphoric) | |-------------------------+-------------+---------------------| | simple, doesn't recurse | ‘h-sel’ | ‘h--sel’ | | nesting-aware, recurses | ‘h-sel*’ | ‘h--sel*’ | |-------------------------+-------------+---------------------| Destructive: modify original TABLE, return nil |-------------------------+-------------+---------------------| | | a lambda | a form (anaphoric) | |-------------------------+-------------+---------------------| | simple, doesn't recurse | ‘h-sel!’ | ‘h--sel!’ | | nesting-aware, recurses | ‘h-sel*!’ | ‘h--sel*!’ | |-------------------------+-------------+---------------------|") (defmacro h-sel (fun table) "Return a table with pairs from TABLE for which FUN returns non-nil. Function FUN is called with two arguments, key and value. This function is similar to current ‘ht-select’. This is a side-effect-free function. Its destructive counterpart is ‘h-sel!’. Its anaphoric counterpart is ‘h--sel’." `(h--hmap (when (funcall ,fun key value) key) value ,table)) (defmacro h--sel (form table) "Anaphoric version of ‘h-sel’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a side-effect-free function. Its destructive counterpart is ‘h--sel!’." `(h--hmap (when ,form key) value ,table)) (defmacro h-sel! (fun table) "Update TABLE by keeping only pairs for which FUN returns non-nil. Function FUN is called with two arguments, key and value. This is a destructive function. Its side-effect-free counterpart is ‘h-sel’. Its anaphoric counterpart is ‘h--sel!’." `(h--hmap! (when (funcall ,fun key value) key) value ,table)) (defmacro h--sel! (form table) "Anaphoric version of ‘h-sel!’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a destructive function. Its side-effect-free counterpart is ‘h--sel’." `(h--hmap! (when ,form key) value ,table)) (defun h-sel* (fun table) "Return a table with pairs from TABLE for which FUN returns non-nil. Function FUN is called with two arguments, key and value. Just like ‘h-sel’, but recurses wherever TABLE is nested. This means that for any VALUE that is itself a hash table, instead of returning it as it is when FUNCTION returns non-nil, as ‘h-sel’ would do, VALUE will be the very ‘h-sel*’ recursively applied to that hash table with the same FUN. This is a side-effect-free function. Its destructive counterpart is ‘h-sel*!’. Its anaphoric counterpart is ‘h--sel*’." (declare (pure t) (side-effect-free t)) (let ((result (h-clr table))) (maphash (lambda (key value) (when (funcall fun key value) (h-put! result key (if (ht? value) (h-sel* fun value) ;; ^ recurses value)))) table) result)) (defmacro h--sel* (form table) "Anaphoric version of ‘h-sel*’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a side-effect-free function. Its destructive counterpart is ‘h--sel*!’." `(h-sel* (lambda (key value) (ignore key value) ,form) ,table)) (defun h-sel*! (fun table) "Update TABLE by keeping only pairs for which FUN returns non-nil. Function FUN is called with two arguments, key and value. This is a destructive function. Its side-effect-free counterpart is ‘h-sel*’. Its anaphoric counterpart is ‘h--sel*!’." (maphash (lambda (key value) (if (funcall fun key value) (when (ht? value) (h-put! table key ;; recurses: (h-sel* fun value))) (h-rem! table key))) table)) (defmacro h--sel*! (form table) "Anaphoric version of ‘h-sel*!’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a destructive function. Its side-effect-free counterpart is ‘h--sel*’." `(h-sel*! (lambda (key value) (ignore key value) ,form) ,table)) ;;;;;;; Rejection (xht--describe "Functions that create a hash table that has all but the specified keys.") ;;;;;;;; rej-keys (xht--describe "Summary: |-------------------------+-----------------+---------------| | | Non-destructive | Destructive | |-------------------------+-----------------+---------------| | simple, doesn't recurse | ‘h-rej-keys’ | ‘h-rej-keys!’ | |-------------------------+-----------------+---------------|") (defun h-rej-keys (table keys) "Return a copy of TABLE with all but the pairs specified by KEYS. KEYS is a list. This is a side-effect-free function. Its destructive counterpart is ‘h-rej-keys!’." (declare (pure t) (side-effect-free t)) (let ((result (h-clr table))) (maphash (lambda (key _value) (unless (member key keys) (puthash key (gethash key table) result))) table) result)) (defun h-rej-keys! (table keys) "Update TABLE by removing the pairs specified by KEYS. KEYS is a list. This is a destructive function. Its side-effect-free counterpart is ‘h-rej-keys’." (dolist (key keys) (remhash key table))) ;;;;;;;; rej (xht--describe "Guideline for the 'rej family' of functions: - 'rej' means 'reject: non-nil in the result of FUN excludes KEY from the results.' - an added '-' means 'anaphoric: enter a form, not a lambda' - an added '*' means 'recurse: apply the values-function or -form to the values whenever these are hash tables' - an added '!' means 'destructive: modify TABLE instead of creating a fresh one' In the anaphoric versions, if you don't use any or either of the let-bound variables key and value in any or either of the forms, it's ok — no warnings. Summary: Side-effect-free: don't modify original TABLE, return a fresh table |-------------------------+-------------+---------------------| | | a lambda | a form (anaphoric) | |-------------------------+-------------+---------------------| | simple, doesn't recurse | ‘h-rej’ | ‘h--rej’ | | nesting-aware, recurses | ‘h-rej*’ | ‘h--rej*’ | |-------------------------+-------------+---------------------| Destructive: modify original TABLE, return nil |-------------------------+-------------+---------------------| | | a lambda | a form (anaphoric) | |-------------------------+-------------+---------------------| | simple, doesn't recurse | ‘h-rej!’ | ‘h--rej!’ | | nesting-aware, recurses | ‘h-rej*!’ | ‘h--rej*!’ | |-------------------------+-------------+---------------------|") (defmacro h-rej (fun table) "Return a table with pairs from TABLE for which FUN returns nil. Function FUN is called with two arguments, key and value. This function is similar to current ‘ht-reject’. This is a side-effect-free function. Its destructive counterpart is ‘h-rej!’. Its anaphoric counterpart is ‘h--rej’." `(h--hmap (unless (funcall ,fun key value) key) value ,table)) (defmacro h--rej (form table) "Anaphoric version of ‘h-rej’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a side-effect-free function. Its destructive counterpart is ‘h--rej!’." `(h--hmap (unless ,form key) value ,table)) (defmacro h-rej! (fun table) "Update TABLE by keeping only pairs for which FUN returns nil. Function FUN is called with two arguments, key and value. This function is similar to current ‘ht-reject!’. This is a destructive function. Its side-effect-free counterpart is ‘h-rej’. Its anaphoric counterpart is ‘h--rej!’." `(h--hmap! (unless (funcall ,fun key value) key) value ,table)) (defmacro h--rej! (form table) "Anaphoric version of ‘h-rej!’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a destructive function. Its side-effect-free counterpart is ‘h--rej’." `(h--hmap! (unless ,form key) value ,table)) (defun h-rej* (fun table) "Return a table with pairs from TABLE for which FUN returns nil. Function FUN is called with two arguments, key and value. Just like ‘h-rej’, but recurses wherever TABLE is nested. This means that for any VALUE that is itself a hash table, instead of returning it as it is when FUN returns nil, as ‘h-rej’ would do, VALUE will be the very ‘h-rej*’ recursively applied to that hash table with the same FUN. This is a side-effect-free function. Its destructive counterpart is ‘h-rej*!’. Its anaphoric counterpart is ‘h--rej*’." (declare (pure t) (side-effect-free t)) (let ((result (h-clr table))) (maphash (lambda (key value) (unless (funcall fun key value) (h-put! result key (if (ht? value) (h-rej* fun value) ;; ^ recurses value)))) table) result)) (defmacro h--rej* (form table) "Anaphoric version of ‘h-rej*’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a side-effect-free function. Its destructive counterpart is ‘h--rej*!’." `(h-rej* (lambda (key value) (ignore key value) ,form) ,table)) (defun h-rej*! (fun table) "Update TABLE by keeping only pairs for which FUN returns nil. Function FUN is called with two arguments, key and value. This is a destructive function. Its side-effect-free counterpart is ‘h-rej*’. Its anaphoric counterpart is ‘h--rej*!’." (maphash (lambda (key value) (if (funcall fun key value) (h-rem! table key) (when (ht? value) (h-put! table key ;; recurses: (h-rej* fun value))))) table)) (defmacro h--rej*! (form table) "Anaphoric version of ‘h-rej*!’. For every key–value pair in TABLE, evaluate FORM with the variables key and value bound. This is a destructive function. Its side-effect-free counterpart is ‘h--rej*’." `(h-rej*! (lambda (key value) (ignore key value) ,form) ,table)) ;;;;;;; Reverse order (xht--describe "Functions to reverse the order in which the items are stored in the table. Summary: |-------------------------+-----------------|---------------+ | | non-destructive | destructive | |-------------------------+-----------------|---------------+ | simple, doesn't recurse | ‘h-reverse’ | ‘h-reverse!’ | | nesting-aware, recurses | ‘h-reverse*’ | ‘h-reverse*!’ | |-------------------------+-----------------|---------------+") (defun h-reverse (table) "Create a hash table identical to TABLE with key order reversed." (let ((r (h-clone* table))) (xht--reverse*! r nil) r)) (defun h-reverse! (table) "Reverse the key order of hash table TABLE." (xht--reverse*! table nil)) (defun h-reverse* (table) "Create a hash table identical to TABLE with key order reversed. When TABLE is nested, do the same to all values that are hash tables." (let ((r (h-clone* table))) (xht--reverse*! r 'recurse) r)) (defun h-reverse*! (table) "Reverse the key order of hash table TABLE. When TABLE is nested, do the same to all values that are hash tables." (xht--reverse*! table 'recurse)) (defun xht--reverse*! (table recurse) "Reverse the key order of hash table TABLE. If RECURSE is non-nil, recurse. Helper function for: ‘h-reverse!’, ‘h-reverse’, ‘h-reverse*!’, and ‘h-reverse*’." (let ((items (h-vitems table 'reverse)) (size (h-size table)) (idx 0)) (h-clr! table) (while (< idx size) (-let [(key value) (aref items idx)] (and recurse (h? value) (xht--reverse*! value recurse)) (h-put! table key value) (setq idx (1+ idx)))))) ;;;;;;; Change numerical value, including #'1+ and #'1- (xht--describe "Functions to alter values of keys when these values are numeric or number-like. Summary: | | Non-destructive | Destructive | |---------------+-------------------+--------------------| | Generic | ‘h-put-num-with*’ | ‘h-put-num-with*!’ | | Increase by 1 | ‘h-put-inc*’ | ‘h-put-inc*!’ | | Decrease by 1 | ‘h-put-dec*’ | ‘h-put-dec*!’ |") (defun h-put-num-with* (fun table &rest keys) "Return copy of TABLE with FUN applied to numeric value of KEYS. KEYS is a chain of keys to access the key of an internal hash table if TABLE is nested — just as with ‘h-get*’. If it's a simple, non-nested TABLE, or if the value is at the \"first level\", enter just one key. FUN is a unary function that can be applied to numerical values. These are some examples: ‘1+’, ‘1-’, ‘sqrt’, ‘log10’. You may also create partial application ones, such as: #+begin_src emacs-lisp (-partial \\='+ 10) (-partial \\='* 2) #+end_src or: #+begin_src emacs-lisp (lambda (x) (expt x 2)) #+end_src The value must, of course, be a number — but the function is flexible about what a number is. If it quacks like a number, we increase it. So it may be found stored also as a string or keyword, and it will be increased while maintaining the type. Moreover, it tries to preserve original leading zeros when they were there in strings, keywords, or symbols. So \"041\", receiving #'1+, becomes \"042\", and so on. When value is nil or key is not found, interpret it as being 0. The function uses base 10. This is a side-effect-free function. Its destructive counterpart is ‘h-put-num-with*!’." (declare (pure t) (side-effect-free t)) (let ((htbl (h-clone* table))) (apply #'h-put-num-with*! fun htbl keys) htbl)) (defun h-put-num-with*! (fun table &rest keys) "Apply FUN to the numeric value of KEYS in possibly nested hash TABLE. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-put-num-with*’." (apply #'h-put*! table (-snoc keys (xht--number-with fun (apply #'h-get* table keys))))) (defun h-put-inc* (table &rest keys) "Return a copy of TABLE with 1 added to the value of KEYS. KEYS is a chain of keys to access the key of an internal hash table if TABLE is nested — just as with ‘h-get*’. If it's a simple, non-nested TABLE, or if the value is at the \"first level\", enter just one key. The value must, of course, be a number — but the function is flexible about what a number is. If it quacks like a number, we increase it. So it may be found stored also as a string or keyword, and it will be increased while maintaining the type. Moreover, it tries to preserve original leading zeros when they were there in strings, keywords, or symbols. So \"041\" becomes \"042\", and so on. When value is nil or key is not found, interpret it as being 0. The function uses base 10. This is a side-effect-free function. Its destructive counterpart is ‘h-put-inc*!’." (declare (pure t) (side-effect-free t)) (apply #'h-put-num-with* #'1+ table keys)) (defun h-put-inc*! (table &rest keys) "Add 1 to the value of KEYS in possibly nested hash TABLE. See ‘h-put-inc*’ for more information. This function returns nil." (apply #'h-put-num-with*! #'1+ table keys)) (defun h-put-dec* (table &rest keys) "Return a copy of TABLE with 1 subtracted from the value of KEYS. KEYS is a chain of keys to access the key of an internal hash table if TABLE is nested — just as with ‘h-get*’. If it's a simple, non-nested TABLE, or if the value is at the \"first level\", enter just one key. The value must, of course, be a number — but the function is flexible about what a number is. If it quacks like a number, we decrease it. So it may be found stored also as a string or keyword, and it will be increased while maintaining the type. Moreover, it tries to preserve original leading zeros when they were there in strings, keywords, or symbols. So \"043\" becomes \"042\", and so on. The function uses base 10. This is a side-effect-free function. Its destructive counterpart is ‘h-put-dec*!’." (declare (pure t) (side-effect-free t)) (apply #'h-put-num-with* #'1- table keys)) (defun h-put-dec*! (table &rest keys) "Subtract 1 from the value of KEYS in possibly nested hash TABLE. See ‘h-put-dec*’ for more information. This function returns nil." (apply #'h-put-num-with*! #'1- table keys)) ;;;;;;;; helper (defun xht--number-with (fun n) "Apply function FUN to number-like creature N. Whatever quacks like a number should accept it. Return it in the same type it was found, and preserving original leading zeros when they were there in strings, keywords, or symbols. Interpret nil as 0. Helper function to ‘xht--number-with-1+’, ‘xht--number-with-1-’." (declare (pure t) (side-effect-free t)) (let* ((err-msg "Doesn't look like a number — nothing done") (as-num-maybe (cond ((null n) 0) ((numberp n) n) ((stringp n) (read n)) ((keywordp n) (read (xht--keyword->string n))) ((symbolp n) (read (format "%s" n))) (t (user-error err-msg))))) (unless (numberp as-num-maybe) (user-error err-msg)) (let ((res (funcall fun as-num-maybe))) (if (or (numberp n) (null n)) res (let* ((num-was (->> n (format "%s") (s-chop-prefix ":"))) (num-fmt (cond ;; Floats: to avoid spurious loss of precision after ;; a series of applications, we won't try to truncate ;; the numbers to the significant digits of the ;; original — you round it when you needed it. ((floatp res) "%s") ;; Integers: if no leading 0s in the original, we'll ;; just return the results; otherwise we'll check the ;; original's width and set it as a minimum. Note ;; that if the original was stored as integers, no ;; leading 0 would have been possible — this is for ;; strings or keywords etc. ((integerp res) (if (/= ?0 (aref num-was 0)) "%s" (format "%%0%sd" (->> num-was length (format "%s"))))) (t (error "‘xht--number-with’: %s" "neither integer nor float?!"))))) (--> (format num-fmt res) (cond ((stringp n) it) ((keywordp n) (xht--string->keyword it)) ((symbolp n) (make-symbol it))))))))) (defsubst xht--number-with-1+ (n) "Apply #'1+ to number-like creature N. Helper function to ‘h-put-inc*’ and ‘h-put-inc*!’." (xht--number-with #'1+ n)) (defsubst xht--number-with-1- (n) "Apply #'1- to number-like creature N. Helper function to ‘h-put-dec*’ and ‘h-put-dec*!’." (xht--number-with #'1- n)) ;;;;;;; Miscellaneous key, value, pair lookups (xht--describe "Other functions about checking keys, values, and pairs.") ;;;;;;;; Has this key, this value, or this pair at some level? (define-inline h-has-key? (table key) "Return t if TABLE has KEY. This function is just like current ‘ht-contains?’" (declare (pure t) (side-effect-free t)) (inline-quote (let ((nf (make-symbol "xht--not-found"))) (not (eq nf (gethash ,key ,table nf)))))) (defun h-has-key*? (table key) "Whether possibly-nested TABLE has KEY at some level. Similar to ‘h-has-key?’, but if TABLE is nested look for KEY also in the internal hash tables of TABLE." (declare (pure t) (side-effect-free t)) (if (h-has-key? table key) t ;; recurse: (xht--has-x*? #'h-has-key*? table key))) (define-inline h-has-value? (table value) "Return t if TABLE has some key whose value is VALUE." (declare (pure t) (side-effect-free t)) (inline-quote (when (h-first (lambda (_k v) (equal v ,value)) ,table) t))) (defun h-has-value*? (table value) "Whether possibly-nested TABLE has at some level key–VALUE pair. Similar to ‘h-has-value?’, but if TABLE is nested look for VALUE also in the internal hash tables of TABLE." (declare (pure t) (side-effect-free t)) (if (h-has-value? table value) t ;; recurse: (xht--has-x*? #'h-has-value*? table value))) (define-inline h-has-pair? (table key value) "Return t if TABLE has KEY and its value is VALUE." (declare (pure t) (side-effect-free t)) (inline-quote (equal ,value (gethash ,key ,table (make-symbol "xht--not-found"))))) (defun h-has-pair*? (table key value) "Whether possibly-nested TABLE has at some level KEY–VALUE pair. Similar to ‘h-has-pair?’, but if TABLE is nested look for KEY–VALUE pair also in the internal hash tables of TABLE." (declare (pure t) (side-effect-free t)) (if (h-has-pair? table key value) t ;; recurse: (xht--has-x*? #'h-has-pair*? table key value))) (defun xht--has-x*? (fun table &rest x) "Generic helper for ‘h-has-key*?’, ‘h-has-value*?’, ‘h-has-pair*?’. FUN is one of these functions above. Whether possibly-nested TABLE has at some level X." (declare (pure t) (side-effect-free t)) (when-let ((ht-vals (-filter #'hash-table-p (h-lvalues table 'reverse)))) (let (found) ;; 'reverse is actually cheaper ^ (while (and ht-vals (not found)) (setq found (apply fun (pop ht-vals) x))) found))) ;;;;;;;; Has all these keys, these values, or these pairs? (defun h-has-keys? (table keys) "Return t if TABLE has all KEYS. KEYS is either a single key or a list of keys." (declare (pure t) (side-effect-free t)) (--all? (h-has-key? table it) (-list keys))) (defun h-has-values? (table values) "Return t if TABLE has all VALUES. VALUES is either a single value or a list of values." (declare (pure t) (side-effect-free t)) (--all? (h-has-value? table it) (-list values))) (defun h-has-pairs? (table pairs) "Return t if TABLE has all PAIRS. PAIRS is either a single pair (a two-item list) or a list of such two-item lists." (declare (pure t) (side-effect-free t)) (--all? (h-has-pair? table (car it) (cadr it)) (if (-all? #'listp pairs) pairs (if (= 2 (length pairs)) (list pairs) (user-error "Malformed list of pairs"))))) ;; I currently see no need for writing (and testing) nestedness-aware versions ;; of these previous three. In any case, it seems to me they could be ;; constructed in the exact same way, just adding the ?*. ;;;;;; Multi-table operations (xht--describe "Functions that act on two or more hash tables simultaneously. XHT loves short function names. Sometimes, however, their meaning might not be obvious at first sight. Thankfully, this is easy to fix — and by third sight they'll already be familiar to you. With this table, it'll take you but a minute to figure out the meaning of the multi-table functions you'll see next: | Function | Implying... | Operation | |----------+----------------------------+--------------| | h-mix | Mixing the tables together | Union | | h-dif | Difference between tables | Difference | | h-cmn | Commonality between tables | Intersection | Easy? Let's see them.") ;;;;;;; Union (xht--describe "Functions that combine tables, either destructively or side-effects-free. Note that: - An '*' means it works with nesting; its absence means only first level. - A '!' means destructive: it changes the original table; its absence means that a new table is created, free of side-effects to the original. - When input has multiple tables, the first one is 'updated' with the others, which are processed from left to right. When not destructive, a shallow copy of the first is updated. So you can always think of it as: values of table1 updated with those of table2; the result updated with those of table3; the result updated with those of table4; and so on. The difference is that when there's a '!' table1 will be itself modified; otherwise not. - Functions for side-effect ('!') return nil; otherwise, the (1st) table.") ;;;;;;;; mix (xht--describe "The destructive ‘h-mix!’ is similar to ht library's ‘ht-update!’, whereas the side-effect-free ‘h-mix’ is similar to ‘ht-merge’.") (defun h-mix! (table &rest from-tables) "Update TABLE according to every key–value pair in FROM-TABLES. FROM-TABLES are processed from left to right, and only the first table will be modified — not the others. So (h-mix! table1 table2 table3 table4) will: - modify table1 with the data from table2; then - modify table1 with the data from table3; then - modify table1 with the data from table4. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-mix’. This function only works at \"the first level\" — no nesting. Its also-destructive nesting-aware counterpart is ‘h-mix*!’. For nesting-awareness but no side-effects, use ‘h-mix*’." (dolist (from-table from-tables) (xht--mix! table from-table))) (defun h-mix (table &rest from-tables) "Return a table that has all key–value pairs from the tables. The tables (TABLE plus FROM-TABLES) are processed from left to right, and no table is modified. So (h-mix table1 table2 table3 table4) will: - create a clone of table1 (let's call it table0) - modify table0 with the data from table2; then - modify table0 with the data from table3; then - modify table0 with the data from table4; - return table0. Tables 1 to 4 haven't changed. This is a side-effect-free function. Its destructive counterpart is ‘h-mix!’. This function is not nesting-aware. Its also-side-effect-free but nesting-aware counterpart is ‘h-mix*’. For nesting-awareness with side-effects, use ‘h-mix*!’." (declare (pure t) (side-effect-free t)) (let ((mixed (h-clone* table))) (dolist (from-table from-tables mixed) (h-mix! mixed from-table)))) (defun h-mix*! (table &rest from-tables) "Update TABLE by adding every key–value pair in FROM-TABLES. Unlike ‘h-mix!’ and ‘ht-update!’, it updates nested levels: so if both values are themselves hash tables, the new value will be the ‘h-mix*!’ of them. FROM-TABLES are processed from left to right, and only the first table will be modified — not the others. So (h-mix! table1 table2 table3 table4) will: - modify table1 with the data from table2; then - modify table1 with the data from table3; then - modify table1 with the data from table4. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-mix*’. This function is nesting-aware. Its also-destructive but only-first-level counterpart is ‘h-mix!’. For no nesting-awareness and no side-effects, use ‘h-mix’." (dolist (from-table from-tables) (xht--mix*! table from-table))) (defun h-mix* (table &rest from-tables) "Return a table that has all key–value pairs from the tables. Unlike ‘h-mix’ and ‘ht-merge’, it updates nested levels: so if both values are themselves hash tables, the new value will be the ‘h-mix*’ of them. The tables (TABLE plus FROM-TABLES) are processed from left to right, and no table is modified. So (h-mix* table1 table2 table3 table4) will: - create a clone of table1 (let's call it table0) - modify table0 with the data from table2; then - modify table0 with the data from table3; then - modify table0 with the data from table4; - return table0. Tables 1 to 4 haven't changed. This is a side-effect-free function. Its destructive counterpart is ‘h-mix*!’. This function is nesting-aware. Its also-side-effect-free but only-first-level counterpart is ‘h-mix’. For no nesting-awareness but with side-effects, use ‘h-mix!’." (declare (pure t) (side-effect-free t)) (let ((mixed (h-clone* table))) (dolist (from-table from-tables mixed) (h-mix*! mixed from-table)))) ;;;;;;;;; helper (defun xht--mix! (table from-table) "Update TABLE by adding every key–value pair in FROM-TABLE. \(Destructive, no nesting, only two tables.) This function returns nil. This is EXACTLY equivalent to current ‘ht-update!’. It's used as basis for ‘h-mix!’, ‘h-mix’, ‘h-mix*!’, and ‘h-mix*’, which see." (ignore (maphash (lambda (key value) (puthash key value table)) from-table))) (defun xht--mix*! (table from-table) "Update TABLE by adding every key–value pair in FROM-TABLE. \(Destructive, accepts nesting, only two tables.) This function returns nil. Helper function for ‘h-mix*!’, which see." (ignore (maphash (lambda (key value) (puthash key (let ((v-dest (gethash key table))) (if (and (ht? v-dest) (ht? value)) (progn (xht--mix*! v-dest value) ;; ^ recursive v-dest) value)) table)) from-table))) ;;;;;;; Difference (xht--describe "Functions that subtract tables, either destructively or side-effects-free.") ;;;;;;;; dif (defun h-dif! (table &rest from-tables) "Update TABLE by removing all matching key–value pairs in FROM-TABLES. That is: the removal only happens when both the key and the value in one of the tables in FROM-TABLES matches that found in TABLE. If only the key is matched, there's no removal. FROM-TABLES are processed from left to right, and only the first table will be modified — not the others. So (h-dif! table1 table2 table3 table4) will: - modify table1 with the data from table2; then - modify table1 with the data from table3; then - modify table1 with the data from table4. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-dif’. This function only works at \"the first level\" — no nesting. Its also-destructive nesting-aware counterpart is ‘h-dif*!’. For nesting-awareness but no side-effects, use ‘h-dif*’." (dolist (from-table from-tables) (xht--dif! table from-table))) (defun h-dif (table &rest from-tables) "Return a table of all key–value pairs in TABLE not in FROM-TABLES. That is: all pairs in TABLE appear in the results, except those that also appear in one of the tables in FROM-TABLES. If only the key is matched, there's no removal. FROM-TABLES are processed from left to right, and no table is modified. So (h-dif table1 table2 table3 table4) will: - create a clone of table1 (let's call it table0) - modify table0 with the data from table2; then - modify table0 with the data from table3; then - modify table0 with the data from table4; - return table0. Tables 1 to 4 haven't changed. This is a side-effect-free function. Its destructive counterpart is ‘h-dif!’. This function is not nesting-aware. Its also-side-effect-free but nesting-aware counterpart is ‘h-dif*’. For nesting-awareness with side-effects, use ‘h-dif*!’." (declare (pure t) (side-effect-free t)) (let ((diffed (h-clone* table))) (dolist (from-table from-tables diffed) (h-dif! diffed from-table)))) (defun h-dif*! (table &rest from-tables) "Update TABLE by removing all matching key–value pairs in FROM-TABLES. That is: the removal only happens when both the key and the value in one of the tables in FROM-TABLES matches that found in TABLE. If only the key is matched, there's no removal. Unlike ‘h-dif!’, it updates nested levels: so if both values are themselves hash tables, the new value will be the ‘h-dif*!’ of them. FROM-TABLES are processed from left to right, and only the first table will be modified — not the others. So (h-dif! table1 table2 table3 table4) will: - modify table1 with the data from table2; then - modify table1 with the data from table3; then - modify table1 with the data from table4. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-dif*’. This function is nesting-aware. Its also-destructive but only-first-level counterpart is ‘h-dif!’. For no nesting-awareness and no side-effects, use ‘h-dif’." (dolist (from-table from-tables) (xht--dif*! table from-table))) (defun h-dif* (table &rest from-tables) "Return a table of all key–value pairs in TABLE not in FROM-TABLES. That is: all pairs in TABLE appear in the results, except those that also appear in one of the tables in FROM-TABLES. If only the key is matched, there's no removal. Unlike ‘h-dif’, it updates nested levels: so if both values are themselves hash tables, the new value will be the ‘h-dif*’ of them. FROM-TABLES are processed from left to right, and no table is modified. So (h-dif* table1 table2 table3 table4) will: - create a clone of table1 (let's call it table0) - modify table0 with the data from table2; then - modify table0 with the data from table3; then - modify table0 with the data from table4; - return table0. Tables 1 to 4 haven't changed. This is a side-effect-free function. Its destructive counterpart is ‘h-dif*!’. This function is nesting-aware. Its also-side-effect-free but only-first-level counterpart is ‘h-dif’. For no nesting-awareness but with side-effects, use ‘h-dif!’." (declare (pure t) (side-effect-free t)) (let ((diffed (h-clone* table))) (dolist (from-table from-tables diffed) (h-dif*! diffed from-table)))) ;;;;;;;;; helper (defun xht--dif! (table from-table) "Update TABLE by removing every key–value pair in FROM-TABLE. \(Destructive, no nesting, only two tables.) This function returns nil. It's used as basis for ‘h-dif!’, ‘h-dif’, ‘h-dif*!’, and ‘h-dif*’, which see." (ignore (maphash (lambda (key value) (when (h-it= (gethash key table) value) (remhash key table))) from-table))) (defun xht--dif*! (table from-table) "Update TABLE by removing every key–value pair in FROM-TABLE. \(Destructive, accepts nesting, only two tables.) This function returns nil. Helper function for ‘h-dif*!’, which see." (ignore (maphash (lambda (key value) (let* ((v-dest (gethash key table))) (if (h-it= v-dest value) (remhash key table) (and (ht? v-dest) (ht? value) (puthash key ;; recursive: (progn (xht--dif*! v-dest value) v-dest) table))))) from-table))) ;;;;;;; Intersection (xht--describe "Functions that build a table with the pairs common to all tables, either destructively or side-effects-free.") ;;;;;;;; cmn (xht--describe "cmn as in: 'common' to all tables.") (defun h-cmn! (table &rest from-tables) "Update TABLE by keeping only the intersection with FROM-TABLES. That is: the key is kept only when the pair is found in all TABLES. If only the key is matched, not kept. FROM-TABLES are processed from left to right, and only the first table will be modified — not the others. So (h-cmn! table1 table2 table3 table4) will: - modify table1 with the data from table2; then - modify table1 with the data from table3; then - modify table1 with the data from table4. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-cmn’. This function only works at \"the first level\" — no nesting. Its also-destructive nesting-aware counterpart is ‘h-cmn*!’. For nesting-awareness but no side-effects, use ‘h-cmn*’." (dolist (from-table from-tables) (xht--cmn! table from-table))) (defun h-cmn (table &rest from-tables) "Return a table of all key–value pairs in TABLE also in FROM-TABLES. That is: no pairs in TABLE appear in the results, except those that appear in all the tables in FROM-TABLES. If only the key is matched, there's no removal. FROM-TABLES are processed from left to right, and no table is modified. So (h-cmn table1 table2 table3 table4) will: - create a clone of table1 (let's call it table0) - modify table0 with the data from table2; then - modify table0 with the data from table3; then - modify table0 with the data from table4; - return table0. Tables 1 to 4 haven't changed. This is a side-effect-free function. Its destructive counterpart is ‘h-cmn!’. This function is not nesting-aware. Its also-side-effect-free but nesting-aware counterpart is ‘h-cmn*’. For nesting-awareness with side-effects, use ‘h-cmn*!’." (declare (pure t) (side-effect-free t)) (let ((intersection (h-clone* table))) (dolist (from-table from-tables intersection) (h-cmn! intersection from-table)))) (defun h-cmn*! (table &rest from-tables) "Update TABLE by keeping only the intersection with FROM-TABLES. That is: the key is kept only when the pair is found in all TABLES. If only the key is matched, not kept. Unlike ‘h-cmn!’, it updates nested levels: so if both values are themselves hash tables, the new value will be the ‘h-cmn*!’ of them. FROM-TABLES are processed from left to right, and only the first table will be modified — not the others. So (h-cmn! table1 table2 table3 table4) will: - modify table1 with the data from table2; then - modify table1 with the data from table3; then - modify table1 with the data from table4. This function returns nil. This is a destructive function. Its side-effect-free counterpart is ‘h-cmn*’. This function is nesting-aware. Its also-destructive but only-first-level counterpart is ‘h-cmn!’. For no nesting-awareness and no side-effects, use ‘h-cmn’." (dolist (from-table from-tables) (xht--cmn*! table from-table))) (defun h-cmn* (table &rest from-tables) "Return a table of all key–value pairs in TABLE also in FROM-TABLES. That is: no pairs in TABLE appear in the results, except those that appear in all the tables in FROM-TABLES. If only the key is matched, there's no removal. Unlike ‘h-cmn’, it updates nested levels: so if both values are themselves hash tables, the new value will be the ‘h-cmn*’ of them. FROM-TABLES are processed from left to right, and no table is modified. So (h-cmn* table1 table2 table3 table4) will: - create a clone of table1 (let's call it table0) - modify table0 with the data from table2; then - modify table0 with the data from table3; then - modify table0 with the data from table4; - return table0. Tables 1 to 4 haven't changed. This is a side-effect-free function. Its destructive counterpart is ‘h-cmn*!’. This function is nesting-aware. Its also-side-effect-free but only-first-level counterpart is ‘h-cmn’. For no nesting-awareness but with side-effects, use ‘h-cmn!’." (declare (pure t) (side-effect-free t)) (let ((intersection (h-clone* table))) (dolist (from-table from-tables intersection) (h-cmn*! intersection from-table)))) ;;;;;;;;; helper (defun xht--cmn! (table from-table) "Update TABLE by keeping only the intersection with FROM-TABLE. \(Destructive, no nesting, only two tables.) This function returns nil. It's used as basis for ‘h-cmn!’, ‘h-cmn’, ‘h-cmn*!’, and ‘h-cmn*’, which see." ;; Based on the following mathematical equality: ;; ;; A ∩ B = A - (A - B) ;; (h-dif! table ;; (h-dif table ;; from-table))) ;; ;; ^ The above is fine, but the below ;; should be faster and replaced it: (ignore (let ((sentinel (make-symbol "xht--sentinel"))) (maphash (lambda (key value) (let ((v-from (gethash key from-table sentinel))) (when (or (eq v-from sentinel) (not (h-it= value v-from))) (remhash key table)))) table)))) (defun xht--cmn*! (table from-table) "Update TABLE by keeping only the intersection with FROM-TABLE. \(Destructive, accepts nesting, only two tables.) This function returns nil. Helper function for ‘h-cmn*!’, which see." (ignore (let ((sentinel (make-symbol "xht--sentinel"))) (maphash (lambda (key value) (let ((v-from (gethash key from-table sentinel))) (if (eq v-from sentinel) (remhash key table) (unless (h-it= value v-from) (if (and (ht? v-from) (ht? value)) (puthash key ;; recursive: (progn (xht--cmn*! value v-from) value) table) (remhash key table)))))) table)))) ;;;;;;; Comparison / Equality / Equivalence ;;;;;;;; 1D: implicit (Vector, List, Lines) (indices as keys) (xht--describe "Not needed. Vectors, lists, and strings can be compared with ‘equal’. Examples: #+begin_src emacs-lisp (equal '(a x) '(a x)) ;=> t (equal [a x] [a x]) ;=> t (equal \"a\\nx\\n\" \"a\\nx\\n\") ;=> t #+end_src And they can only be equal to exactly themselves. Also note that we'll keep these two (one of which with extra trailing newline) not being equal: #+begin_src emacs-lisp (equal \"a\\nx\\n\" \"a\\nx\") ;=> nil #+end_src") ;;;;;;;; 1D: explicit (KVL, Cons Cell) (xht--describe "Functions that compare if two or more objects are equal. Key–value pairs are of dimension 1: not nested. No need for a function to compare cons cells: equal will do: #+begin_src emacs-lisp (equal '(a . 1) '(a . 1)) ;=> t #+end_src") (defun h-kvl= (kvl1 kvl2 &rest more-kvls) "Return t only if all KEY–VALUE LINES have same KEY–VALUE pairs. It accepts any number ≥2 of KEY–VALUE LINES as input. \(The arguments KVL1, KVL2, and MORE-KVLS are key–value lines.)" (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-kvl kvl1 kvl2 more-kvls)) ;;;;;;;; ≥1D unspecified (Hash table) (possibly nested) (xht--describe "Functions that compare if two or more hash tables are equivalent. Key–value pairs are of unspecified dimension >1 (nested). Many distinctions here, so a summary of what the operators mean follows. These tests return t if: | Operator | Condition for returning a truthy value | |----------+-----------------------------------------------------------| | ‘h==’ | tables have same keys in THE SAME order, and the values: | | | - when both are hash tables, are also ‘h==’. | | | - when both are of some different type, they are ‘equal’. | |----------+-----------------------------------------------------------| | ‘h-pr==’ | same as the previous, but stricter: properties* | | | also match: same size, test function, etc. | |----------+-----------------------------------------------------------| | ‘h=’ | tables have same keys in ANY order, and the values: | | | - when both are hash tables, are also ‘h=’. | | | - when both are of some different type, they are ‘equal’. | |----------+-----------------------------------------------------------| | ‘h-pr=’ | same as the previous, but stricter: properties* | | | also match: same size, test function, etc. | |----------+-----------------------------------------------------------| | ‘h_=’ | tables have same keys in ANY order, and the values: | | | - are ‘equal’, OR: | | | -- when both are hash tables, are also ‘h_=’. | | | -- when both are of some different type, they return t | | | when tested with their respective equality function; | | | so, for example, if they're both alists, comparison | | | of them using ‘h-alist=’ returns t; when both TSVs. | | | comparison of them using ‘h-tsv=’ returns t; etc. | |----------+-----------------------------------------------------------| | ‘h~=’ | tables have same keys in ANY order, and the values: | | | - are ‘equal’, OR: | | | -- when both are hash tables, are also ‘h~=’. | | | -- when both are of some different type, they | | | return t when tested with ‘h-it~’, which in | | | turn means that they can even be of different | | | types but, when converted to a hash table, | | | become ‘h~=’. | So there's a gradation of strictness of equivalence, where: - ‘h-pr==’ > ‘h==’ > ‘h=’ > ‘h_=’ > ‘h~=’. and: - ‘h-pr=’ > ‘h=’ And so ‘h~=’ is the most permissive, where equivalences of each item are themselves deeply tested. The ‘h==’ one may be useful, for example, for deciding whether two hash tables would return the exact same results when mapping or converting. We could say that ‘h=’ is to ‘h-pr=’ as ‘equal’ is to ‘equal-including-properties’. When in doubt, ‘h=’ is likely the best choice, because: - It accepts different ordering of keys inside the tables. Order is not supposed to matter in hash tables, so this is most likely what you want: being stricter with ‘h==’ or any of the -pr would deem unequal tables that for most regular purposes are equivalent. - When nested, it applies the same logic to any hash tables stored in it as values, ignoring order: suffices that their keys are the same and that the values are either ‘equal’ or ‘h=’. - Any non–hash-table values are tested with ‘equal’, so you don't run the risk of false equivalence of lists that were not intended as plists (and therefore sensitive to ordering and to repeated items), and other such oddities.") (defun h-pr== (table1 table2 &rest more-tables) "One of the ways to see if all hash tables TABLES are equivalent. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | YES | | Compare keys order? | YES | | Compare non–hash-table values with… | equal | When the values are hash tables, they are compared with this very function, recursively. For more details, see the helper function ‘xht--equal?’, as well as the section heading description in the code source. See all: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (apply #'xht--equal-many #'xht---pr== #'identity table1 table2 more-tables)) (defun h-pr= (table1 table2 &rest more-tables) "One of the ways to see if all hash tables TABLES are equivalent. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | YES | | Compare keys order? | NO | | Compare non–hash-table values with… | equal | When the values are hash tables, they are compared with this very function, recursively. For more details, see the helper function ‘xht--equal?’, as well as the section heading description in the code source. See all: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (apply #'xht--equal-many #'xht---pr= #'identity table1 table2 more-tables)) (defun h== (table1 table2 &rest more-tables) "One of the ways to see if all hash tables TABLES are equivalent. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | NO | | Compare keys order? | YES | | Compare non–hash-table values with… | equal | When the values are hash tables, they are compared with this very function, recursively. For more details, see the helper function ‘xht--equal?’, as well as the section heading description in the code source. See all: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (apply #'xht--equal-many #'xht--== #'identity table1 table2 more-tables)) (defun h= (table1 table2 &rest more-tables) "One of the ways to see if all hash tables TABLES are equivalent. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | NO | | Compare keys order? | NO | | Compare non–hash-table values with… | equal | When the values are hash tables, they are compared with this very function, recursively. When in doubt, use this one. This function can be thought of as a variadic version of ‘ht-equal?’: it accepts any number ≥2 of tables as input. Moreover, it returns t when comparing two indistinguishable nested hash tables (of same key–value pairs), for which ‘ht-equal?’ until v2.4 returns nil: #+begin_src emacs-lisp (ht-equal? (ht (:a 1)) (ht (:a 1))) ;=> t (ht-equal? (ht (:a (ht (:b 2)))) (ht (:a (ht (:b 2))))) ;=> nil !! #+end_src For more details, see the helper function ‘xht--equal?’, as well as the section heading description in the code source. See all: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (apply #'xht--equal-many #'xht--= #'identity table1 table2 more-tables)) (defun h_= (table1 table2 &rest more-tables) "One of the ways to see if all hash tables TABLES are equivalent. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | NO | | Compare keys order? | NO | | Compare non–hash-table values with… | ‘h-it=’ | When the values are hash tables, they are compared with this very function, recursively. For more details, see the helper function ‘xht--equal?’, as well as the section heading description in the code source. See all: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (apply #'xht--equal-many #'xht--_= #'identity table1 table2 more-tables)) (defun h~= (table1 table2 &rest more-tables) "One of the ways to see if all hash tables TABLES are equivalent. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | NO | | Compare keys order? | NO | | Compare non–hash-table values with… | ‘h-it~’ | When the values are hash tables, they are compared with this very function, recursively. For more details, see the helper function ‘xht--equal?’, as well as the section heading description in the code source. See all: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (apply #'xht--equal-many #'xht--~= #'identity table1 table2 more-tables)) (defun h-props= (table1 table2 &rest more-tables) "Whether all hash tables TABLES have the same properties. Note: but not necessarily the same key–value pairs, just the properties. \(The arguments TABLE1, TABLE2, and MORE-TABLES are hash tables.) | Compare properties? | YES | | Compare data? | NO | See also: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--props= #'identity table1 table2 more-tables)) ;;;;;;;;; helper ;; Note: ‘xht--equal?’ fixes ‘ht-equal?’ returning nil for two nested HTs. ;; It also doesn't return error when either arg isn't a hash table — ;; returns nil instead. I think this is fine: ‘equal’ itself returns ;; nil when different types: (equal '(:a 1) [:a 1]) ;=> nil (defun xht--equal? (table1 table2 &optional strictness) "Return t if TABLE1 and TABLE2 have the same keys and values. Also works with nested hash tables. \(The arguments TABLE1 and TABLE2 are hash tables.) This is a helper function to build these: ‘h-pr==’, ‘h-pr=’, ‘h==’, ‘h=’, ‘h_=’, ‘h~=’. \(Note: when in doubt, ‘h=’ is probably what you want.) When STRICTNESS is nil, default to \\='=. When STRICTNESS is the symbol: - \\='-pr== - test their properties with ‘xht--props=’. - test the keys order of both. - test non–hash-table values with ‘equal’. - test hash-table values with (‘xht--equal?’ v1 v2 \\='pr==). - \\='-pr= - test their properties with ‘xht--props=’. - test non–hash-table values with ‘equal’. - test hash-table values with (‘xht--equal?’ v1 v2 \\='pr=). - \\='== - test the keys order of both. - test non–hash-table values with ‘equal’. - test hash-table values with (‘xht--equal?’ v1 v2 \\='==). - \\='= - test non–hash-table values with ‘equal’. - test hash-table values with (‘xht--equal?’ v1 v2 \\='=). - \\='_= - test non–hash-table values with ‘h-it=’. - test hash-table values with (‘xht--equal?’ v1 v2 \\='_=). - \\='~= - test non–hash-table values with ‘h-it~’. - test hash-table values with (‘xht--equal?’ v1 v2 \\='~=). For any chosen option, all tests must pass to return t. Comparison of properties mean: besides the data, they must all be of same nominal size, test function, weakness, rehash-size and rehash-threshold. Some comments. So if \\='_= is chosen, when the values of matching keys are of same type but aren't equal, try to convert them to hash tables and compare again. They could be, for example, equivalent plists or equivalent alists or equivalent JSONs or equivalent org tables: with just the order changed or updated (repeated) keys not deleted — but equivalent. In this case, values will be considered equal. So when this argument is \\='_= any pair among the following would return t to this function: #+begin_src emacs-lisp (h* :a 1 :b \\='(:c 3 :d 4)) (h* :a 1 :b \\='(:d 4 :c 3))) (h* :a 1 :b \\='(:d 4 :c 3 :c 1 :d 2))) #+end_src and the same for when 2D: org tables, lists of lists, TSVs, etc. And if \\='~= is chosen, it's like the previous but allow for the values' types to be different. So when this argument is \\='~= any pair among the following would return t to this function: #+begin_src emacs-lisp (h* :a 1 :b \\='(:c 3 :d 4)) ; plists (h* :a 1 :b \\='(:d 4 :c 3)) ; (h* :a 1 :b \\='(:d 4 :c 3 :c 1 :d 2)) ; (h* :a 1 :b \\='((:c . 3) (:d . 4))) ; alists (h* :a 1 :b \\='((:d . 4) (:c . 3) (:c . 20))) ; (h* :a 1 :b \\='(h* :c 3 :d 4)) ; (h* :a 1 :b \":c\t3\n:d\t4\") ; key–value lines #+end_src and the same for when 2D: org tables, lists of lists, TSVs, etc. Why bother with a separate function? Isn't it always desirable to have these equivalent things being considered equivalent? Not necessarily. Take the case of a value that looks like this: #+begin_src emacs-lisp \\='(a f n nil b x b nil a m w v a k) #+end_src Who knows what this list is supposed to mean? It may represent the first letter of the names of cats in your building from lowest- to highest-numbered apartments — and thus NOT intended to be a plist. So the list above would NOT be equivalent to: #+begin_src emacs-lisp \\='(a f n nil b x w v) #+end_src Doing that would have replaced the cat in apartment 107; and added one cat to 108, whose resident is allergic and won't be pleased. What about alists? Wouldn't it be safe to do it automatically to them? Also not necessarily. Perhaps you want to keep track of items as they are replaced. Maybe this: #+begin_src emacs-lisp \\='((Alice . 10) (Bob . 5) (Alice . 15) (Bob . 2) (Alice . 0) (Bob . 0)) #+end_src represents how much money you owed your friends Alice and Bob at the end of three consecutive days, which you want to keep a record of AND be able to ‘assoc’ the alist to get the current values (10 and 5, respectively). And you could get the value of the previous day using (nth 2 list) and (nth 3 list). What about org tables? Same thing. Maybe the one you have stored is not at all about some tabular data structure of unique IDs, and you don't want to reduce it to one. So we have separate functions, to be used according to what you'd like to consider equivalent. This depends on the data, and also on what you're doing." (declare (pure t) (side-effect-free t)) (if strictness (unless (memq strictness '(-pr== -pr= == = _= ~=)) (user-error "‘xht--equal?’: Unrecognized strictness option %s" strictness)) (setq strictness '=)) (and (h? table1) (h? table2) (pcase strictness ((or '-pr== '-pr=) (xht--props= table1 table2)) (_ t)) (let ((keys1 (h-lkeys table1 'rev)) (keys2 (h-lkeys table2 'rev))) (and (pcase strictness ((or '-pr== '==) (equal keys1 keys2)) (_ t)) (equal (length keys1) (length keys2)) (equal (->> keys1 (--map (format "%S" it)) (-sort #'string<)) (->> keys2 (--map (format "%S" it)) (-sort #'string<))) (--all? (let* ((sentinel (make-symbol "xht--sentinel")) (val1 (h-get table1 it)) (val2 (h-get table2 it sentinel))) (if (and (h? val1) (h? val2)) ;; recurse: (xht--equal? val1 val2 strictness) (pcase strictness ('-pr== (equal val1 val2)) ('-pr= (equal val1 val2)) ('== (equal val1 val2)) ('= (equal val1 val2)) ('_= (xht--it= val1 val2)) ('~= (xht--it~ val1 val2)) (_ (error "‘xht--equal?’: impossible option"))))) ;; Note: ;; - ‘xht--it~’ calls ‘h~=’ which is... ;; (xht--equal? h1 h2 '~=), where h1 and h2 will be the ;; hash tables found in the conversion. ;; ;; - ‘xht--it=’ calls ‘h=’ which is... ;; (xht--equal? h1 h2 '=), where h1 and h2 will be the ;; hash tables found in the conversion. ;; ;; Eventually these recursions end up in two things that can ;; be compared with ‘equal’ instead of converted. ;; ;; I suppose ‘h_=’ would be more consistent for the second ;; case, but this seems more than good enough. There are more ;; distinctions that could be made, such as what happens when ;; the hash tables have an alist that has a plist that itself ;; has a hash table with a JSON object storing two TSVs that may ;; or not be equivalent — but if you find yourself staring at ;; that, you may be better off reviewing your data structure... keys1))))) (defun xht--props= (table1 table2) "Whether TABLE1 and TABLE2 have the same properties. But not necessarily the same key–value pairs. Helper function for ‘xht--props-and-data=’ and ‘h-props=’." (declare (pure t) (side-effect-free t)) (h= (h-props table1) (h-props table2))) (defsubst xht---pr== (table1 table2) "Whether TABLE1 and TABLE2 are ‘xht--equal?’ with strictness \\='-pr==." (xht--equal? table1 table2 '-pr==)) (defsubst xht---pr= (table1 table2) "Whether TABLE1 and TABLE2 are ‘xht--equal?’ with strictness \\='-pr=." (xht--equal? table1 table2 '-pr=)) (defsubst xht--== (table1 table2) "Whether TABLE1 and TABLE2 are ‘xht--equal?’ with strictness \\='==." (xht--equal? table1 table2 '==)) (defsubst xht--= (table1 table2) "Whether TABLE1 and TABLE2 are ‘xht--equal?’ with strictness \\='=." (xht--equal? table1 table2 '=)) (defsubst xht--_= (table1 table2) "Whether TABLE1 and TABLE2 are ‘xht--equal?’ with strictness \\='_=." (xht--equal? table1 table2 '_=)) (defsubst xht--~= (table1 table2) "Whether TABLE1 and TABLE2 are ‘xht--equal?’ with strictness \\='~=." (xht--equal? table1 table2 '~=)) ;;;;;;;; ≥1D unspecified (Alist, Plist, JSON) (possibly nested) (xht--describe "Functions that compare if two or more alists, plists, or json objects are equivalent. Key–value pairs are of unspecified dimension >1 (nested).") (defun h-alist= (alist1 alist2 &rest more-alists) "Return t only if all ALISTs have same KEY–VALUE pairs. It accepts any number ≥2 of ALISTs as input. \(The arguments ALIST1, ALIST2, and MORE-ALISTS are association lists.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-alist* alist1 alist2 more-alists)) (defun h-plist= (plist1 plist2 &rest more-plists) "Return t only if all PLISTs have same KEY–VALUE pairs. It accepts any number ≥2 of PLISTs as input. \(The arguments PLIST1, PLIST2, and MORE-PLISTS are property lists.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-plist* plist1 plist2 more-plists)) (defun h-json= (json1 json2 &rest more-jsons) "Return t only if all JSONs have same KEY–VALUE pairs. It accepts any number ≥2 of JSONs as input. \(The arguments JSON1, JSON2, and MORE-JSONS are JSONs.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-json* json1 json2 more-jsons)) ;;;;;;;; 2D (LOL, Org table, TSV, CSV, SSV) (xht--describe "Functions that compare if two or more objects are equal. Key–value pairs are of dimension 2: tabular.") (defun h-lol= (lol1 lol2 &rest more-lols) "Return t only if all LISTS OF LISTS have same KEY–VALUE pairs. It accepts any number ≥2 of LISTS OF LISTS as input. \(The arguments LOL1, LOL2, and MORE-LOLS are lists of lists.) Note that regular lists and vectors are the same if they have the same elements at the same indices, so they can be compared with plain ‘equal’. Two LISTS OF LISTS, however, may, for our purposes, be equal while being nominally different. For example, these two: #+begin_src emacs-lisp \\='((:id :name :age) \\='((:id :name :age) (1 alice 42) (2 bob 30) (2 bob 30) (1 alice 42)) (1 alice 21)) #+end_src As with alists and plists, the top-most element of same ID wins, and the order between different items doesn't matter. Each unique ID returns the same information. The same applied when we convert from Org Table, TSV, CSV, SSV, or JSON." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-lol lol1 lol2 more-lols)) (defun h-orgtbl= (orgtbl1 orgtbl2 &rest more-orgtbls) "Return t only if all ORG TABLES have same KEY–VALUE pairs. It accepts any number ≥2 of ORG TABLES as input. \(The arguments ORGTBL1, ORGTBL2, and MORE-ORGTBLS are org tables.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-orgtbl orgtbl1 orgtbl2 more-orgtbls)) (defun h-tsv= (tsv1 tsv2 &rest more-tsvs) "Return t only if all TSVs have same KEY–VALUE pairs. It accepts any number ≥2 of TSVs as input. \(The arguments TSV1, TSV2, and MORE-TSVS are tab-separated values.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-tsv tsv1 tsv2 more-tsvs)) (defun h-csv= (csv1 csv2 &rest more-csvs) "Return t only if all CSVs have same KEY–VALUE pairs. It accepts any number ≥2 of CSVs as input. \(The arguments CSV1, CSV2, and MORE-CSVS are comma-separated values.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-csv csv1 csv2 more-csvs)) (defun h-ssv= (ssv1 ssv2 &rest more-ssvs) "Return t only if all SSVs have same KEY–VALUE pairs. It accepts any number ≥2 of SSVs as input. \(The arguments SSV1, SSV2, and MORE-SSVS are space-separated values.) See ‘h-lol=’ for more information on equivalence, which also applies here." (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--= #'h<-ssv ssv1 ssv2 more-ssvs)) ;;;;;;;; Unknown thing (xht--describe "Functions that compare if two or more objects are equal. Objects are unspecified.") (defun h-type (obj) "Detect object OBJ's type-for-conversion. Return a keyword. This isn't the same as ‘type-of’; it's more concerned with types of interest for conversions and lookups. So, for example, it'd return :tsv for a TSV, while ‘type-of’ would return \\='string. Testing is done in a selected order to best guess some potentially ambiguous types by trying to match the most specific of them first. - So LOL is tested for before alist and plist, which are also tested before list. - Moreover, if, while testing, the value of ‘h-kvl-sep-re’ matches TAB, then ‘h-kvl?’ could return t if all lines have a single TAB. Since by default this variable matches \" *= *\", it'll be interpreted that it's being let-bound, and that the OBJ is a KVL rather than a two-column TSV. Likewise in case the value of ‘h-kvl-sep-re’ matches a comma \(preference over CSV) or simply two or more spaces \(preference over SSV). It's worth reminding that the difference between a two-column *sv and a KVL of matching delimiter is that the latter has no headers \(one-dimensional: just key and value), whereas the former has a header \(two-dimensional: key, field, and value)" (declare (pure t) (side-effect-free t)) (let ((type (type-of obj))) (pcase type ('hash-table :ht) ('vector :vector) ('cons (cond ((-cons-pair? obj) :cons-pair) ((h-lol? obj) :lol) ((h-alist? obj) :alist) ((h-plist? obj) :plist) (t :list))) ('string (cond ((string= "" obj) :empty) ((h-orgtbl? obj) :orgtbl) ((h-json? obj) :json) ((h-kvl? obj) :kvl) ((h-tsv? obj) :tsv) ((h-csv? obj) :csv) ((h-ssv? obj) :ssv) (t :lines))) ('symbol (cond ((null obj) :null) (t type))) (_ type)))) (defun h-type= (obj1 obj2 &rest more-objs) "Are all OBJECTS of same type? For example, are they all hash tables? Or all org tables? Or all lists of lists? Or all alists? Or all TSVs? JSONs? \(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.) Return type. This isn't the same as ‘type-of’; it's more concerned with types of interest for this conversion and lookup. So, for example, it'd return :tsv for a TSV, while ‘type-of’ would return \\='string." (declare (pure t) (side-effect-free t)) (when (apply #'xht--equal-many #'xht--type= #'identity obj1 obj2 more-objs) (h-type obj1))) (defun h-it= (obj1 obj2 &rest more-objs) "Are all OBJECTS, all of same type, reducible to each other? Try to guess what they are. \(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.) The following tests are applied, and if any of them return t for every pair of objects, they are considered same-type equals: ‘equal’ ‘h-tsv=’ ‘h-json=’ ‘h=’ ‘h-csv=’ ‘h-alist=’ ‘h-lol=’ ‘h-ssv=’ ‘h-plist=’ ‘h-orgtbl=’. If so, return type. Otherwise, return nil." (declare (pure t) (side-effect-free t)) (when (apply #'xht--equal-many #'xht--it= #'identity obj1 obj2 more-objs) (h-type obj1))) (defun h-it~ (obj1 obj2 &rest more-objs) "Are all OBJECTS equivalent according to XHT tests? They are if, and only if, either they are equal or, when converted to a hash table, ‘h~=’ returns t for the pair. This means that repeated keys (in alists, plists, TSVs etc) are dealt with, and that, after that, ordering is irrelevant. \(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.)" (declare (pure t) (side-effect-free t)) (apply #'xht--equal-many #'xht--it~ #'identity obj1 obj2 more-objs)) ;;;;;;;;; helper (defun xht--type= (obj1 obj2) "Are OBJ1 and OBJ2 of same type?" (declare (pure t) (side-effect-free t)) (equal (h-type obj1) (h-type obj2))) (defun xht--it= (obj1 obj2) "Are OBJ1 and OBJ2, of same type, reducible to each other? If so, return type. Otherwise, return nil." (declare (pure t) (side-effect-free t)) (let ((t1 (h-type obj1)) (t2 (h-type obj2))) (and (equal t1 t2) (pcase t1 (:ht (h= obj1 obj2)) (:lol (h-lol= obj1 obj2)) (:tsv (h-tsv= obj1 obj2)) (:csv (h-csv= obj1 obj2)) (:ssv (h-ssv= obj1 obj2)) (:kvl (h-kvl= obj1 obj2)) (:json (h-json= obj1 obj2)) (:alist (h-alist= obj1 obj2)) (:plist (h-plist= obj1 obj2)) (:orgtbl (h-orgtbl= obj1 obj2)) (:empty t) (:null t) (:list (equal obj1 obj2)) (:lines (equal obj1 obj2)) (:vector (equal obj1 obj2)) (:cons-pair (equal obj1 obj2)) (_ (equal obj1 obj2))) t1))) (defun xht--it~ (obj1 obj2) "Are OBJ1 and OBJ2 equivalents according to XHT tests? They are if, and only if, when converted to a hash table, ‘h~=’ returns t for the pair." (declare (pure t) (side-effect-free t)) (or (equal obj1 obj2) (let ((h1 (ignore-errors (h<-it obj1))) (h2 (ignore-errors (h<-it obj2)))) (h~= h1 h2)))) ;;;;;;;; Helper (defun xht--equal-many (fn-compare fn-transform obj1 obj2 &rest more-objs) "Compare OBJECTS with FN-COMPARE after applying FN-TRANSFORM on them. \(The arguments OBJ1, OBJ2, and MORE-OBJS are objects.)" (declare (pure t) (side-effect-free t)) ;; Note: choice of obj1 and obj2 is to guarantee that at least 2 objects are ;; passed; and repetition below was chosen in order to avoid the unnecessary ;; consing that would come with the following simpler abstraction: ;; (--all? (funcall fn-compare ;; (funcall fn-transform obj1) ;; (funcall fn-transform it)) ;; (cons obj2 more-objs))) (and (funcall fn-compare (funcall fn-transform obj1) (funcall fn-transform obj2)) (--all? (funcall fn-compare (funcall fn-transform obj1) (funcall fn-transform it)) more-objs))) ;;;;;; Conversion (xht--describe "Functions to convert from/to hash tables. To hash tables: ‘h<-kvl’ ‘h<-lines’ ‘h<-vector’ ‘h<-list’ ‘h<-lol’ ‘h<-orgtbl’ ‘h<-alist’ ‘h<-plist’ ‘h<-alist*’ ‘h<-plist*’ ‘h<-json*’ ‘h<-tsv’ ‘h<-csv’ ‘h<-ssv’ From hash tables: ‘h->kvl’ ‘h->lines’ ‘h->vector’ ‘h->list’ ‘h->lol’ ‘h->orgtbl’ ‘h->alist’ ‘h->plist’ ‘h->alist*’ ‘h->plist*’ ‘h->json*’ ‘h->tsv’ ‘h->csv’ ‘h->ssv’ You can navigate functions easily with: M-. and M-, (elisp-slime-nav-mode) Note that when results on both sides evaluate to hash tables, we use for equality comparison the ‘H=>’ symbol (which uses ‘h=’). For getting (‘alist-get’, ‘plist-get’, ‘h-get’, etc.): | alist, plist: | Top-most values override bottom-most values | | lol, org-table, *sv: | Top-most values override bottom-most values | | kvl: | Bottom-most values override top-most values | | vector, list, lines: | Uniqueness guaranteed by index | Their nature: | kvl: | no nesting, keys are given | | lines: | no nesting, keys = line number (starting at 0) | | vector, list: | may be nested¹, keys = indices | | alist, plist, json: | may be nested², keys are given | | lol, org-table, *sv: | tabular, assumed no nesting (no lols inside lols) | ¹ treatment of nested not implemented for vectors and lists. ² treatment of nested implemented for alists, plists, and jsons — both ways.") ;;;;;;; Hash table to hash table (xht--describe "Functions that read a hash table and return a hash table. For shallow copying, ‘h-copy’ is offered as an alias to ‘copy-hash-table’. However, this function is not at all appropriate for dealing with nested hash tables. See ‘h-clone*’ for more.") (defun h-clone* (table &optional sz ts we rs rt) "Create a deep copy of possibly-nested hash table TABLE. Optional arguments after TABLE are SZ, TS, WE, RS, and RT — which correspond to, respectively, size, test, weakness, rehash-size, rehash-threshold. When nil, they match that of TABLE. The result has the same elements and structure of TABLE, but any nested (internal) hash tables are recursively replaced by new ones. This allows you to modify the internal hash tables without altering the original one. This is not possible with ‘copy-hash-table’, whose copy is shallow and any internal tables that might be present in the copy will point to the very same objects of the original one. Likewise with ‘ht-copy’, which uses ‘copy-hash-table’, and ‘h-copy’, which is an alias to the latter. You also shouldn't use shallow copying if you intend to apply destructive functions such as ‘sort’, ‘nconc’, or ‘nreverse’ to any list values of the copied table, as this will likely modify the original. To avoid that, either use non-destructive alternatives such as ‘-sort’ and ‘reverse’; or ‘h-clone*’ the original instead of using ‘h-copy’. See also: ‘h<-ht’." (declare (pure t) (side-effect-free t)) (let ((result (h-empty-clone table sz ts we rs rt))) (maphash (lambda (key value) (puthash key (if (h? value) (h-clone* value) ;; ^ recurses value) result)) table) result)) ;; ^ Could have used this: ;; (h-hmap* (lambda (key _value) key) ;; (lambda (_key value) value) ;; table) ;; but: ;; 1. h-hmap* removes nil keys (rare case, but still, we're cloning) ;; 2. chosen one is more direct, possibly faster, no need for funcalling (defun h<-ht (table &optional sz ts we rs rt) "Given hash table TABLE, return it or a clone, depending on params. Optional arguments after TABLE are SZ, TS, WE, RS, and RT — which correspond to, respectively, size, test, weakness, rehash-size, rehash-threshold. When nil, they match that of TABLE. If any of the optional arguments passed differs from the TABLE's corresponding properties, ‘h-clone*’ the table with these parameters. Otherwise just return TABLE. This is a non-destructive function: TABLE isn't changed. See also: ‘h-clone*’." (declare (pure t) (side-effect-free t)) (let ((rest (list sz ts we rs rt)) (props (-> table h-props h-lvalues))) (if (and (equal (nth 2 rest) (nth 2 props)) ;; This ^ because the below is not sufficient when passed ;; weakness is nil and TABLE's isn't. The other parameters, on ;; the other hand, cannot have nil as value in the table, so a ;; passed nil is just omission. (--all? (-let [(r . p) it] (or (not r) (equal r p))) (-zip-with #'cons rest props))) table (h-clone* table sz ts we rs rt)))) (defun h-2d<-1d (table &optional f1 f2 size test) "Make 2D the 1D hash table TABLE by adding header fields F1, F2. If F1 is nil, use the string \"key\". If F2 is nil, use \"value\". SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. Alias: ‘h-1d->2d’." (declare (pure t) (side-effect-free t)) (let* ((sznow (h-size table)) (size (or size (xht--init-size sznow))) (htbl (h-new size test))) (h--each table (h-put! htbl key (h* (or f1 "key") key (or f2 "value") value))) htbl)) (defun h-1d<-2d (table2d &optional size test) "Make 1D (KVL-like) the 2D hash table TABLE2D. Infer current header with ‘h-2d-header’. If header isn't of length 2, signal error. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’. Alias: ‘h-2d->1d’." (declare (pure t) (side-effect-free t)) (let ((header (h-2d-header table2d))) (unless (= 2 (length header)) (error "Header length of hash table isn't 2")) (let* ((sznow (h-size table2d)) (size (or size (xht--init-size sznow))) (htbl (h-new size test))) (h--each table2d (h-put! htbl key (h-get* table2d key (cadr header)))) htbl))) ;;;;;;; 1D: implicit (indices as keys) (xht--describe "Functions in this category convert from other formats to hash table or vice-versa. Key–value pairs are of dimension 1: not nested. Indices serve as keys.") ;;;;;;;; Vectors (defun h<-vector (vector &optional size test) "Convert vector VECTOR to a hash table using VECTOR's indices as keys. Keys are by default of integer type, not strings, and start at 0. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (let* ((sznow (length vector)) (size (or size (xht--init-size sznow))) (htbl (h-new size test)) (idx 0)) (while (< idx sznow) (h-put! htbl idx (aref vector idx)) (setq idx (1+ idx))) htbl)) (defun h->vector (table) "Convert hash table TABLE to vector. Do the opposite of ‘h<-vector’, which see. Return vector. Select only those keys that are natural numbers (an integer ≥0) or strings that could be converted to a natural number, which it automatically does. Any holes in the number sequence from 0 to max(keys) are filled in the vector with nil. If the table has keys that reduce to the same integer, such as having both 3 and \"3\" as keys, behavior is unpredictable." (declare (pure t) (side-effect-free t)) ;; `keys` is another hash table, where each pair is (newkey key): ;; e.g. (3 "3") ;; It maps the post-natural-num keys to the original keys. (let* ((tkeys (h-keys table)) (keys (h-new (xht--init-size (length tkeys)))) maxkey vector keys-keys) (dolist (key tkeys) (when-let ((keyN (xht--thing-to-nat key))) (h-put! keys keyN key))) (setq keys-keys (h-keys keys)) (if (null keys-keys) [] (setq maxkey (->> keys-keys (apply #'max)) vector (make-vector (1+ maxkey) nil)) (h--each keys (aset vector key (h-get table value))) vector))) (defun xht--thing-to-nat (obj) "Return OBJ as a natural number if it looks like one. If it's a string, read it. Then check if it's a natural number. If so, return it. Otherwise, return nil." (declare (pure t) (side-effect-free t)) (if (natnump obj) obj (--> obj h-as-string read (when (natnump it) it)))) ;;;;;;;; Lists (defun h<-list (list &optional size test) "Convert LIST to a hash table using LIST's indices as keys. Keys are by default of integer type, not strings, and start at 0. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (let* ((sznow (length list)) (size (or size (xht--init-size sznow))) (htbl (h-new size test)) (idx 0)) (while (< idx sznow) (h-put! htbl idx (elt list idx)) (setq idx (1+ idx))) htbl)) (defun h->list (table) "Convert hash table TABLE to list. Select only those keys that are natural numbers (an integer ≥0) or strings that could be converted to a natural number, which it automatically does. Any holes in the number sequence from 0 to max(keys) are filled in the list with nil. If the table has keys that reduce to the same integer, such as having both 3 and \"3\" as keys, behavior is unpredictable." (declare (pure t) (side-effect-free t)) (--> table (h->vector it) (append it nil))) ;;;;;;;; Lines (defun h<-lines (str &optional size test) "Convert string STR to a hash table using line numbers as keys. Keys are by default of integer type, not strings, and start at 0. The choice of zero for the first line number (instead of 1) was due to transitivity: a plain conversion from lines to hash table to vector to lines should yield the initial string. The alternatives seemed worse. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (h<-list (s-lines str) size test)) (defun h->lines (table) "Convert hash table TABLE whose keys are numbers to string. Select only those keys that are natural numbers (an integer ≥0) or strings that could be converted to a natural number, which it automatically does. Any holes in the number sequence from 0 to max(keys) are filled in the string with empty lines. If the table has keys that reduce to the same integer, such as having both 3 and \"3\" as keys, behavior is unpredictable." (declare (pure t) (side-effect-free t)) (->> table h->list (s-join "\n"))) ;;;;;;; 1D: explicit (xht--describe "Functions in this category convert from other formats to hash table or vice-versa. Strings with one key–value pair per line are of dimension 1: not nested. Likewise cons pairs.") ;;;;;;;; Key–Value Lines (defun h<-kvl (kvl &optional size test obsolete) "Convert key–value lines KVL to a hash table. Optional arguments SIZE and TEST may be passed. A KVL is the equivalent of a non-nested alist translated to a simple flat config file as used in Unix-like systems. It should look, for example, like this: --- data begins ---> key1 = val1 key2 = val2 key3 = val3 <--- data ends ----- One pair per line. There is no header. If there were, it'd be taken as another key–value line. The field delimiter (separator) to be used is given by the value of the regular expression in the variable ‘h-kvl-sep-re’, which see. By default it's equal sign surrounded or not by spaces. The variable can be temporarily let-bound when change is desirable. Comments are non-destructively stripped before conversion. Comments are anything matching ‘h-kvl-comment-re’ (which see), which can also be temporarily let-bound when some other comment regex is expected in KVL. Note that sections (as used in .ini files and some types of .conf) aren't implemented or dealt with here. KVLs are to be considered flat. So lines with [Section name] are not considered as nodes, and no tree-like structure will be generated. These section lines, if present, are ignored by the default ‘h-kvl-section-re’ (which see) as if they were comments, since this may be useful if the file you're processing has sections only for information purposes and the keys inside them are unique. If this is not the case, then this function is not suitable for this conversion. An option would be to pre-convert the input to JSON using some other tool, and then apply ‘h<-json*’ to the result. Also note that in an alist, when two elements have equal key, the first is used. With KVL data, we'll consider that the last row wins. This is because in an alist a new pair is pushed to the top, whereas KVL files are usually (but not always, as there are varying standards) read from top to bottom. An OBSOLETE form of calling this function is \(h<-kvl kvl &optional sep size test) where the optional arg SEP specified the field delimiter. This use is deprecated. The delimiter is now passed by the variable ‘h-kvl-sep-re’, which should be let-bound when the default is not the one expected in the KVL to be converted." (declare (advertised-calling-convention (kvl &optional size test) "2.0") (pure t) (side-effect-free t)) (let (delim-re strip-re lines htbl) ;; Backward compatibility with obsolete calling convention: (when (or (stringp size) (natnump test) obsolete) (setq delim-re size size test test obsolete)) (setq strip-re (format "%s\\|%s" h-kvl-comment-re h-kvl-section-re) delim-re (or delim-re h-kvl-sep-re) lines (s-lines kvl) size (or size (xht--init-size (length lines))) htbl (h-new size test)) (while lines (--> (pop lines) (s-replace-regexp strip-re "" it) (unless (s-blank-str? it) (-let [(k v) (s-split delim-re (s-trim it))] (h-put! htbl k v))))) htbl)) (defun h->kvl (table &optional sep) "Convert hash table TABLE to SEP-separated key–value lines. The optional separator SEP is a literal string, and could be, for example, \" = \" or \":\" or \"\t\". When nil, default to \"=\"." (declare (pure t) (side-effect-free t)) (with-output-to-string (h--each table (princ (format "%s%s%s\n" key (or sep "=") value))))) ;;;;;;;; Cons Pairs (defun h<-cons-pair (cons-pair &optional size test) "Convert CONS-PAIR to hash table. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (unless (-cons-pair? cons-pair) (error "%s is not a cons pair" cons-pair)) (-let [(key . value) cons-pair] (h-st* (or size (xht--init-size 1)) test key value))) (defun h->cons-pair (table) "Convert hash table TABLE to cons pair." (declare (pure t) (side-effect-free t)) (pcase (h-size table) (1 (car (h--lmap `(,key . ,value) table))) (_ (error "Can only convert to cons pair hash tables of size 1")))) ;;;;;;; ≥1D unspecific (possibly nested) (xht--describe "Functions in this category convert from other formats to hash table or vice-versa. Key–value pairs are of undetermined dimension >1 (nested).") ;;;;;;;; Association lists ;;;;;;;;; from alist (defun xht<--alist* (alist &optional size test recurse) "Convert ALIST to hash table, maybe recursing. Helper function, basis for ‘h<-alist’ and ‘h<-alist*’. Optional arguments SIZE and TEST may be passed. If RECURSE is non-nil, recurse." (declare (pure t) (side-effect-free t)) (let ((htbl (h-new (or size (-> alist length xht--init-size)) test))) (dolist (pair alist htbl) (-let [(key . value) pair] (when (eq 'xht:new-one (h-get htbl key 'xht:new-one)) ;; ^ Fastest solution I can think of to both preserve the original ;; ordering and exclude eventual repeats. The cost of this extra ;; h-get instead of processing it in reverse is ~1 μs, and I'm fine ;; with an extra 1 ms for processing some (rare) 1000-item list if ;; it preserves the list's order. (h-put! htbl key (if (and recurse (h-alist? value)) (xht<--alist* value nil test recurse) ;; sublevel sizes auto^ ; recursive^ value))))))) (defun h<-alist (alist &optional size test) "Convert ALIST to hash table. Similar to ‘ht<-alist’, except that it preserves original order and SIZE can also be passed as an argument. When SIZE is nil, it defaults to the result of applying ‘xht--init-size’ to half the plist's length. For the meaning of TEST, see ‘h-new’. Like ‘ht<-alist’, this function is not aware of nested alists. Its nesting-aware counterpart is ‘h<-alist*’." (declare (pure t) (side-effect-free t)) (xht<--alist* alist size test nil)) (defun h<-alist* (alist &optional size test) "Convert possibly nested ALIST to hash table. If the ALIST is nested, each alist found as value will be recursively converted into a hash table as well. SIZE is the nominal initial size of the table to be created. When SIZE is nil, it defaults to the result of applying ‘xht--init-size’ to the alist's length. For the meaning of TEST, see ‘h-new’. This function is aware of nested alists. Its non–nesting-aware counterpart is ‘h<-alist’." (declare (pure t) (side-effect-free t)) (xht<--alist* alist size test 'recurse)) ;;;;;;;;; to alist (defun xht-->alist* (table &optional recurse) "Convert hash table TABLE to alist, maybe recursing. Helper function, basis for ‘h->alist’ and ‘h->alist*’. Optional arguments SIZE and TEST may be passed. If RECURSE is non-nil, recurse." (declare (pure t) (side-effect-free t)) (h--lmap (cons key (if (and recurse (ht? value)) (xht-->alist* value recurse) ;; recursive^ value)) table)) (defun h->alist (table) "Convert hash table TABLE to alist. Similar to current ‘ht->alist’, with the difference that it preserves the hash table's order of keys. Like ‘ht->alist’, this function is not aware of nested alists. Its nesting-aware counterpart is ‘h->alist*’." (declare (pure t) (side-effect-free t)) (xht-->alist* table nil)) (defun h->alist* (table) "Convert hash table TABLE to alist, maybe recursing. If the ALIST is nested, each alist found as value will be recursively converted into a hash table as well. This function is aware of nested alists. Its non–nesting-aware counterpart is ‘h->alist’." (declare (pure t) (side-effect-free t)) (xht-->alist* table 'recurse)) ;;;;;;;; Property lists ;;;;;;;;; from plist (defun xht<--plist* (plist &optional size test recurse) "Convert PLIST to hash table, maybe recursing. Helper function, basis for ‘h<-plist’ and ‘h<-plist*’. Optional arguments SIZE and TEST may be passed. If RECURSE is non-nil, recurse." (declare (pure t) (side-effect-free t)) (let* ((lenpl (length plist)) (htbl (h-new (or size (-> lenpl (/ 2) xht--init-size)) test)) (even (if (= 0 (% lenpl 2)) (-copy plist) (-butlast plist)))) (while even (let ((key (pop even)) (value (pop even))) (when (eq 'xht:new-one (h-get htbl key 'xht:new-one)) ;; ^ Fastest solution I can think of to both preserve the original ;; ordering and exclude eventual repeats. The cost of this extra ;; h-get instead of processing it in reverse is ~1 μs, and I'm fine ;; with an extra 1 ms for processing some (rare) 1000-item list if ;; it preserves the list's order. (h-put! htbl key (if (and recurse (h-plist? value)) (xht<--plist* value nil test recurse) ;; sublevel sizes auto^ ; recursive^ value))))) htbl)) (defun h<-plist (plist &optional size test) "Convert PLIST to hash table. Similar to ‘ht<-plist’, except that it preserves original order and SIZE can also be passed as an argument. When SIZE is nil, it defaults to the result of applying ‘xht--init-size’ to half the plist's length. For the meaning of TEST, see ‘h-new’. This function is not aware of nested plists. Its nesting-aware counterpart is ‘h<-plist*’." (declare (pure t) (side-effect-free t)) (xht<--plist* plist size test nil)) (defun h<-plist* (plist &optional size test) "Convert possibly nested PLIST to hash table. If the PLIST is nested, each plist found as value will be recursively converted into a hash table as well. SIZE is the nominal initial size of the table to be created. When SIZE is nil, it defaults to the result of applying ‘xht--init-size’ to half the plist's length. For the meaning of TEST, see ‘h-new’. This function is aware of nested plists. Its non–nesting-aware counterpart is ‘h<-plist’." (declare (pure t) (side-effect-free t)) (xht<--plist* plist size test 'recurse)) ;;;;;;;;; to plist (defun xht-->plist* (table &optional recurse) "Convert hash table TABLE to plist, maybe recursing. Helper function, basis for ‘h->plist’ and ‘h->plist*’. If RECURSE is non-nil, recurse." (declare (pure t) (side-effect-free t)) (apply #'append (h--lmap (list key (if (and recurse (ht? value)) (xht-->plist* value recurse) ;; recursive^ value)) table))) (defun h->plist (table) "Convert hash table TABLE to plist. Similar to current ‘ht->plist’, with the difference that it preserves the hash table's order of keys. This function is not aware of nested plists. Its nesting-aware counterpart is ‘h->plist*’." (declare (pure t) (side-effect-free t)) (xht-->plist* table nil)) (defun h->plist* (table) "Convert hash table TABLE to plist, maybe recursing. If the PLIST is nested, each plist found as value will be recursively converted into a hash table as well. This function is aware of nested plists. Its non–nesting-aware counterpart is ‘h->plist’." (declare (pure t) (side-effect-free t)) (xht-->plist* table 'recurse)) ;;;;;;;; JSON (defun h<-json* (json &optional size test) "Convert possibly nested JSON to hash table. If JSON is a string enclosed in [] instead of {}, the first step of the conversion will output a vector instead of directly a hash table. The vector will be inspected, and: - If every item is a hash table, guess from it the ID for creating a 2D hash table, and return such table. - Otherwise, treat it as vector and convert to hash table as usual: indices as numeric keys, items as values. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (let* ((json-object-type 'hash-table) (converted (json-read-from-string json))) (cond ((ht? converted) (h<-ht converted size (or test 'equal))) ((vectorp converted) (let ((values (append converted nil))) (if (-all? #'ht? values) (let* ((headers-lol (-map #'h-keys values)) (common (-reduce #'-intersection headers-lol))) (unless common (error "h<-json*: Don't know how to convert: dim < 2D?")) (let ((id (car common)) (htbl (h-new size test))) (--each values (h-put! htbl (h-get it id) it)) htbl)) (h<-vector converted))))))) (defun h->json* (table &optional no-pp sort) "Convert hash table TABLE to JSON, maybe recursing. If NO-PP is nil, return formatted (pretty-printed string). If NO-PP is non-nil, return as is (compact string). If SORT is non-nil, return it sorted. Otherwise don't sort it." (declare (pure t) (side-effect-free t)) ;; It seems JSON needs keys to be strings or symbols. ;; No numbers as keys, for example. (let ((json-encoding-object-sort-predicate (when sort 'string<)) (json-encoding-pretty-print (not no-pp)) (htbl (h-new (h-size table)))) ;; Problem: ‘json-encode-hash-table’ reverses the key order of the hash ;; table, and of its values (also hash tables) — but we'd rather preserve ;; the order when sort is nil (it won't matter if we choose to sort it, of ;; course). So I thought it was time to write some functions to reverse ;; the key order of a hash table. See ‘h-reverse’. (h--each table (h-put! htbl (h-as-string key) value)) ;; Function ‘json-encode-hash-table’ is obsolete as of Emacs 28.1. So: (if (fboundp 'json-encode) (json-encode (if sort htbl (h-reverse* htbl))) (with-no-warnings (json-encode-hash-table (if sort htbl (h-reverse* htbl))))))) ;;;;;;; 2D (tabular) (xht--describe "Functions in this category convert from other formats to hash table or vice-versa. Key–value pairs are of dimension 2: tabular.") ;;;;;;;; Lisp tables (lists of lists) (defun h<-lol (lol &optional size test) "Create a hash table with initial values according to list of lists LOL. - The first element of the LOL is a list of keys. - The other elements of the LOL are their respective values. - The values of the first column of the LOL are used as unique IDs: keys to the first hash table. - The value of each unique ID is a hash table composed of all key–value pairs associated with this unique ID. As with alists, in case of repeated unique IDs the one uppermost in the list has preference for returning values. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (let* ((sentinel (make-symbol "xht--sentinel")) (size (or size (xht--init-size (length lol)))) (htbl (h-new size test)) (header (car lol)) key) (dolist (row (cdr lol) htbl) (setq key (car row)) (when (eq sentinel (h-get htbl key sentinel)) ;; ^ Fastest solution I can think of to both preserve the original ;; ordering and exclude eventual repeats. The cost of this extra h-get ;; instead of processing it in reverse is ~1 μs, and I'm fine with an ;; extra 1 ms for processing some (rare) 1000-item list if it ;; preserves the list's order. (h-put! htbl key (h-zip-lists header row)))))) (defun h->lol (table2d &optional reverse) "Do the opposite of ‘h<-lol’, which see. Return list of lists. Input is TABLE2D. If REVERSE is non-nil, reverse the display order of all rows after header. Notice that the internal order of items depends on where the data came from and whether it has been updated." (declare (pure t) (side-effect-free t)) (unless (h-empty? table2d) (let* ((header (h-2d-header table2d)) (rest (h-lmap (lambda (_k value) (--map (ht-get value it) header)) table2d 'reverse))) ;<--more efficient (cons header (if reverse rest (reverse rest)))))) ;;;;;;;; Org tables (defun h<-orgtbl (orgtbl &optional size test) "Create a hash table with initial values according to org table ORGTBL. Does to an org table what ‘h<-lol’ does to Lisp tables (lists of lists). SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (autoload #'org-table-to-lisp "org-table") (let ((lol (->> orgtbl org-table-to-lisp (-remove-item 'hline)))) (h<-lol lol size test))) (defun h->orgtbl (table2d &optional reverse) "Do the opposite of ‘h<-orgtbl’, which see. Return org table as string. Input is TABLE2D. If REVERSE is non-nil, reverse the display order of all rows after header. Notice that the internal order of items depends on where the data came from and whether it has been updated." (declare (pure t) (side-effect-free t)) (if (ht-empty? table2d) "" (with-temp-buffer (let* ((standard-output (current-buffer)) (header (h-2d-header table2d)) (rest (h-lmap (lambda (_k value) (--map (ht-get value it) header)) table2d 'reverse))) ;<-- more efficient (princ (xht--list-to-orgtbl-row header)) (princ "|----------|\n") (dolist (item (if reverse rest (reverse rest))) ;; That's right ^ because if it came from a lol or ;; org table or tsv, it was originally read from ;; bottom to top, so it's already internally reversed. (princ (xht--list-to-orgtbl-row item)))) ;; Without org it will also work — but the org table ;; output, although valid, will remain unformatted. (ignore-errors (org-mode)) (when (equal major-mode 'org-mode) (goto-char 3) (call-interactively #'org-ctrl-c-ctrl-c)) (s-trim (xht--buff-str-no-prop))))) (defun xht--list-to-orgtbl-row (list) "Convert a flat LIST to an org table's row." (declare (pure t) (side-effect-free t)) (->> (-map #'h-as-string list) (s-join " | ") (format "| %s |\n"))) ;;;;;;;; TSV, CSV, SSV ;;;;;;;;; Without using org (preferred) (defun h<-tsv (tsv &optional size test) "Create a hash table with initial values according to TSV. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (let (lol) (--> (s-lines tsv) (dolist (row it (nreverse lol)) (unless (s-blank-str? row) (push (s-split "\t" row) lol))) (h<-lol it size test)))) ;; This ^ should be faster and more direct than using org, and without limits: ;; ‘org-table-convert-region’ uses ‘org-table-convert-region-max-lines’, ;; normally set to 999. (defun h->tsv (table2d &optional reverse) "Do the opposite of ‘h<-tsv’, which see. Return tsv. Input is TABLE2D. If REVERSE is non-nil, reverse the display order of all rows after header. Notice that the internal order of items depends on where the data came from and whether it has been updated." (declare (pure t) (side-effect-free t)) (if (h-empty? table2d) "" (with-temp-buffer (let* ((standard-output (current-buffer)) (header (h-2d-header table2d)) (rest (h-lmap (lambda (_k v) (h-values v)) table2d 'reverse))) ;<-- more efficient (princ (xht--list-to-tsv-row header)) (dolist (item (if reverse rest (reverse rest))) ;; That's right ^ because if it came from a lol or ;; org table or tsv, it was originally read from ;; bottom to top, so it's already internally reversed. (princ (xht--list-to-tsv-row item)))) (->> (xht--buff-str-no-prop) s-trim-right (format "%s\n"))))) (defun xht--list-to-tsv-row (list) "Convert a flat LIST to a TSV's row." (declare (pure t) (side-effect-free t)) (->> (-map #'h-as-string list) (s-join "\t") (s-append "\n"))) (defun h<-ssv (ssv &optional size test) "Create a hash table with initial values according to SSV. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (let (lol) (--> (s-lines ssv) (dolist (row it (nreverse lol)) (unless (s-blank-str? row) (push (s-split " +" row) lol))) (h<-lol it size test)))) (defun h->ssv (table2d &optional reverse) "Do the opposite of ‘h<-ssv’, which see. Return ssv. Input is TABLE2D. If REVERSE is non-nil, reverse the display order of all rows after header. Notice that the internal order of items depends on where the data came from and whether it has been updated." (declare (pure t) (side-effect-free t)) (if (h-empty? table2d) "" (with-temp-buffer (let* ((standard-output (current-buffer)) (header (h-2d-header table2d)) (rest (h-lmap (lambda (_k v) (h-values v)) table2d 'reverse)) ;<--more efficient (widths (--map (xht--2d-col-width table2d it) header))) (princ (xht--list-to-ssv-row header widths)) (dolist (item (if reverse rest (reverse rest))) ;; That's right ^ so we only reverse it once; and if it came from a ;; lol or org table or ssv, it was originally read from bottom to ;; top, so it's already internally reversed. (princ (xht--list-to-ssv-row item widths)))) (->> (xht--buff-str-no-prop) s-trim-right (format "%s\n"))))) (defun xht--2d-col-width (table2d field) "Maximum string length of column FIELD of 2D hash table TABLE2D. For example, in the SSV below, resulting widths are, respectively: 2, 5, 3. id name age 01 alice 42 02 bob 30 So passing \"name\" as FIELD for an equivalent 2D table returns 5." (declare (pure t) (side-effect-free t)) (->> (h-2d-col table2d field) (--map (->> it h-as-string length)) -max)) (defun xht--list-to-ssv-row (list &optional widths) "Convert a flat LIST to an SSV's row. If WIDTHS is nil, just use two spaces as separator. If however a list of WIDTHS is provided, fields will be padded to these widths before adding the two spaces." (declare (pure t) (side-effect-free t)) (->> (h-zip-lists (-map #'h-as-string list) (or widths (-repeat (length list) 1))) (h--lmap (format (format "%%-%ds" value) key)) (s-join " ") ;join with 2 spaces (s-trim-right) ;no need for trailing spaces in the row (s-append "\n"))) ;; ‘h<-csv’: aliased to ‘xht--csv’. (defun h->csv () "Do the opposite of ‘h<-csv’, which see. Return csv. NOTE: This function has not yet been implemented." (message "h->csv: not yet implemented")) ;;;;;;;;; Using org (defun xht<--*sv (input arg &optional size test) "Read a tsv, csv, or ssv INPUT and convert it to a hash table. INPUT can be a file or a buffer or a string. For the meaning of ARG, see ‘org-table-convert-region’. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (if (xht--str-nw? input) (with-temp-buffer (autoload #'org-table-convert-region "org-table") (insert input) (org-table-convert-region (point-min) (point-max) arg) (h<-orgtbl (xht--buff-str-no-prop) size test)) (h-new size test))) (defun xht<--tsv (input &optional size test) "Create a hash table with initial values according to TSV. INPUT can be a file or a buffer or a string. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (xht<--*sv input '(16) size test)) (defun xht<--csv (input &optional size test) "Create a hash table with initial values according to CSV. INPUT can be a file or a buffer or a string. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (xht<--*sv input '(4) size test)) (defun xht<--ssv (input &optional size test) "Create a hash table with initial values according to SSV. INPUT can be a file or a buffer or a string. SSV here means a table using 2+ spaces, or a TAB, as field separators. SIZE is the nominal initial size of the table to be created. If nil, it defaults to the result of ‘xht--init-size’, which see. For the meaning of TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (xht<--*sv input 2 size test)) ;;;;;;; Unknown thing (xht--describe "Functions that read an unknown object, try to guess what it is, then convert it to a hash table.") (defun h<-it (thing &rest rest) "Convert THING to hash table. Try to guess what it is. When in doubt, it prefers higher dimension. So if type is alist,it uses ‘h<-alist*’, not ‘h<-alist’. Likewise ‘h<-plist*’ instead of ‘h-plist’. If you prefer the latter, be explicit, passing the exact function instead. And since it uses ‘h-type’ for type-detection, it tries to match: - lol, alist, and plist before list — so when you prefer plain lists, be explicit, passing ‘h<-list’ instead. - *sv before lines — so when you prefer plain lines, be explicit, passing ‘h<-lines’ instead. REST are arguments that might be taken by the respective conversion functions." (declare (pure t) (side-effect-free t)) (let ((type (h-type thing))) (pcase type (:ht (apply #'h<-ht thing rest)) (:lol (apply #'h<-lol thing rest)) (:tsv (apply #'h<-tsv thing rest)) (:csv (apply #'h<-csv thing rest)) (:ssv (apply #'h<-ssv thing rest)) (:kvl (apply #'h<-kvl thing rest)) (:null (apply #'h<-list thing rest)) (:list (apply #'h<-list thing rest)) (:json (apply #'h<-json* thing rest)) (:alist (apply #'h<-alist* thing rest)) (:plist (apply #'h<-plist* thing rest)) (:empty (apply #'h<-tsv thing rest)) (:lines (apply #'h<-lines thing rest)) (:orgtbl (apply #'h<-orgtbl thing rest)) (:vector (apply #'h<-vector thing rest)) (:cons-pair (apply #'h<-cons-pair thing rest)) (_ (user-error "Cannot convert %s to hash table" type))))) ;;;;;; Predicates (xht--describe "Miscellaneous predicates, mostly about checking type for conversion purposes.") ;;;;;;; Type (xht--describe "For hash tables, use ‘h?’, which is the same as ‘ht?’: an alias to Emacs' internal ‘hash-table-p’.") (defun h-kvl? (str &optional obsolete) "Could string STR be a KVL? Only if, after stripped of any comments and section headers, and trimmed of any leading and trailing whitespace, every line has exactly one common separator. Comments and section headers are matched with ‘h-kvl-comment-re’ and ‘h-kvl-section-re’, respectively, which see. An OBSOLETE form is \(h-kvl? str &optional sep) where the optional arg SEP specified the field delimiter. This use is deprecated. The delimiter is now passed by the variable ‘h-kvl-sep-re’ (which see), which should be let-bound when the default value is not expected." (declare (advertised-calling-convention (str) "2.0") (pure t) (side-effect-free t)) (let ((strip-re (format "%s\\|%s" h-kvl-comment-re h-kvl-section-re)) (delim-re (or obsolete h-kvl-sep-re)) (lines (s-lines str)) (kvl? t)) (while (and lines kvl?) (--> (pop lines) (s-replace-regexp strip-re "" it) s-trim (or (s-blank-str? it) (= 1 (s-count-matches delim-re it)) (setq kvl? nil)))) kvl?)) (defun h-alist? (list) "Non-nil if and only if LIST is a non-nil alist with simple keys." ;; Adapted from json.el's ‘json-alist-p’. (declare (pure t) (side-effect-free t)) (when list (let ((l list)) (while (consp l) (setq l (if (and (consp (car l)) (atom (caar l))) (cdr l) 'not-alist))) (null l)))) (defun h-plist? (list) "Non-nil if and only if LIST is a non-nil plist with non-nil atom keys." (declare (pure t) (side-effect-free t)) (when list (let ((l list)) (while (consp l) (setq l (if (consp (cdr l)) (cddr l) 'not-plist))) (null l)))) (defun h-lol? (obj) "Return t if OBJ is a list of lists." (declare (pure t) (side-effect-free t)) (and obj (listp obj) ;; we don't want it if it's only header (more likely it'd be an alist) (> (length obj) 1) ;; then all items must be lists: (--all? (and (listp it) ;; and none of them cons cells: (listp (cdr it)) ;; for now we're assuming exactly 2D (not higher), so no ;; item of the internal lists should itself be a list; ;; this distinguishes it from 2D alists — but will break ;; if we ever want to make it 3D, in which case we'd need ;; better detection. (-all? #'nlistp it)) obj) ;; for our purposes, no item's length should be larger than the ;; header's (but ok if they're shorter: trailing nils) (--> (-map #'length obj) (= (car it) (-max it))))) (defun h-orgtbl? (str) "Could string STR be an Org Table?" (autoload #'org-at-table-p "org") (with-temp-buffer (insert str) (goto-char 2) (org-at-table-p))) (defun h-json? (str) "Could string STR be a JSON object? Return nil if, after trimming, it isn't enclosed in {} or []. Return t if inside is either empty or returns non-nil when converted with ‘json-read-from-string’ ignoring errors." (let ((trimmed (s-trim str))) (unless (string= "" trimmed) (let ((1st (aref trimmed 0)) (last (aref trimmed (1- (length trimmed))))) (and (or (and (= 1st ?{) (= last ?})) (and (= 1st ?\[) (= last ?\]))) (or (not (xht--str-nw? (substring trimmed 1 -1))) (ignore-errors (json-read-from-string str))) t))))) (defun h-tsv? (str) "Could string STR be a TSV? Only if all non-commented, non-blank lines have at least one tab not in the beginning of the line. Commented means `^\s*#` in the line. Note that if the TSV has two fields, and therefore exactly one tab per line, it can't be distinguished from a KVL-with-tab using tab as separator, since it can't be detected whether the first line is a TSV header or a KVL entry. So functions such as ‘h<-it’, ‘h-it=’, ‘h-type’, and others may guess wrongly." (declare (pure t) (side-effect-free t)) (xht--*sv? "\t" str)) (defun h-csv? (str) "Could string STR be a CSV? Only if all non-commented, non-blank lines have at least one comma not in the beginning of the line. Commented means `^\s*#` in the line." (declare (pure t) (side-effect-free t)) (xht--*sv? "\"?[ \t]*,[ \t]*\"?" str)) (defun h-ssv? (str) "Could string STR be an SSV? Only if all non-commented, non-blank lines have at least TWO whitespaces or a tab not in the beginning of the line. Commented means `^\s*#` in the line." (h-tsv? (s-replace-regexp " +" "\t" str))) (defun xht--*sv? (sep str) "Could string STR be a *SV? SEP is a regex representing the separator. Helper for ‘h-tsv?’, ‘h-csv?’, ‘h-ssv?’." (declare (pure t) (side-effect-free t)) (->> str s-lines (--remove (or (not (xht--str-nw? it)) (s-matches? "^[ \t]*#" it))) (--all? (s-matches? sep it)))) ;;;;;;; Dimension (xht--describe "See ‘h-1d?’, ‘h-2d?’, ‘h-nested?’.") ;;;;;; Properties and dimension (xht--describe "Functions to look up properties and dimension of hash tables (and of other objects convertible to hash tables). See also: ‘xht--props=’, ‘xht--props-and-data=’, ‘h-props=’") ;;;;;;; Hash tables (defun h-prop (table prop) "Return the value of property PROP of hash table TABLE. Valid values for PROP are: - size - test - weakness - rehash-size - rehash-threshold PROP may be given as a symbol, string or keyword. So any of these work: \\='test \"test\" :test Note that \"size\" is the nominal size, which defaults to 65 when created. For the actual size, use ‘ht-size’." (declare (pure t) (side-effect-free t)) (when (member (h-as-symbol prop) '(size test weakness rehash-size rehash-threshold)) (--> (h-as-string prop) (s-lex-format "hash-table-${it}") read (funcall it table)))) (defun h-props (table) "Return a hash table containing properties of hash table TABLE. The property \\='data isn't included. The reason is that while all other properties can be compared directly, the value of \\='data is a plist, and plists with different ordering can be equivalent. This would create an exception for functions using this function. For comparing just the data from two hash tables, use ‘h=’." (declare (pure t) (side-effect-free t)) (let* ((props '(size test weakness rehash-size rehash-threshold)) (lprops (length props)) (hprops (h-new (xht--init-size lprops) 'equal))) (dolist (prop props hprops) (h-put! hprops prop (h-prop table prop))))) (defun h-dim (table) "Determine dimension of hash table TABLE. Return: - error if not a hash table. - 0 if empty hash table. - 1 if simple key–value: no value is itself a hash table. - 1.5 if *some* values are hash tables of dimension 1, and some values aren't hash tables. - 2 if ‘h-2d?’ returns t - 2.5 if *every* value is a hash table of dimension at least 2, but some are higher. - 3 if *every* value is a hash table of dimension 2. ... etc. Generally: 1+ (minimum common dimension of hash table values) + (if any ht has dim > mcd(dims), then 0.5, else 0) Regarding conversions to hash table, when: - from KVL, vector, list: should be of dim = 1. - from lol, orgtbl, *sv: should be of dim = 2. - from alist, plist: 1 when simple, and usually 1.5 when nested. (but could be 2+ with the same logic)" (declare (pure t) (side-effect-free t)) (cond ((not (ht? table)) (error "Not a hash table")) ((h-empty? table) 0) (t (let ((values (h-values table 'reverse))) (cond ((-none? #'ht? values) 1) ((-all? #'ht? values) (let* ((dims (-map #'h-dim values)) (mind (-min dims)) (maxd (-max dims)) (frac (if (equal mind maxd) 0 0.5))) (+ 1 mind frac))) ((-some? #'ht? values) 1.5) (t (error "h-dim error: ?"))))))) (defun h-1d? (obj) "Return t if OBJ is a 1D hash table, nil otherwise. It's 1D if, and only if: 1. it's a hash table AND 2. none of the values are hash tables." (declare (pure t) (side-effect-free t)) (and (ht? obj) (= 1 (h-dim obj)))) (defun h-2d? (obj) "Return t if OBJ is a 2D hash table, nil otherwise. Return t if, and only if: 1. it's a hash table AND 2. all values are regular hash tables AND 3. none of the keys of these values (the \"header labels\") are themselves hash tables AND 4. at least one of these keys has as property: its value is equal to OBJ's key for all of OBJ's keys. In other words, an id ID exists if, and only if, (h-get* key id) returns key for all keys." (declare (pure t) (side-effect-free t)) (and (ht? obj) (h-2d-header obj) t)) (defun h-nested? (obj) "Return t if OBJ is a nested hash table, nil otherwise. It's nested if, and only if: 1. it's a hash table AND 2. at least one value is a hash table." (declare (pure t) (side-effect-free t)) (and (h? obj) (--any (h? it) (h-values obj 'reverse)) t)) ;;;;;;; Generalization to other types (defun h-it-dim (obj) "Determine dimension of object OBJ. This is a generalization of ‘h-dim’ (which see) to other objects." (declare (pure t) (side-effect-free t)) (xht--itify #'h-dim obj)) (defun h-it-empty? (obj) "Return t if OBJ is empty, nil otherwise. This is a generalization of ‘h-empty?’ (which see) to other objects." (declare (pure t) (side-effect-free t)) (xht--itify #'h-empty? obj)) (defun h-it-1d? (obj) "Return t if OBJ is of dimension 1, nil otherwise. This is a generalization of ‘h-1d?’ (which see) to other objects." (declare (pure t) (side-effect-free t)) (xht--itify #'h-1d? obj)) (defun h-it-2d? (obj) "Return t if OBJ is of dimension 2, nil otherwise. This is a generalization of ‘h-2d?’ (which see) to other objects." (declare (pure t) (side-effect-free t)) (xht--itify #'h-2d? obj)) (defun h-it-nested? (obj) "Return t if OBJ is nested, nil otherwise. This is a generalization of ‘h-nested?’ (which see) to other objects." (declare (pure t) (side-effect-free t)) (xht--itify #'h-nested? obj)) (defun xht--itify (fun-of-table obj) "Generalize function f(table) FUN-OF-TABLE to f(OBJ). Helper function." (declare (pure t) (side-effect-free t)) (funcall fun-of-table (if (ht? obj) obj (h<-it obj)))) ;;;;;; Writing and reading ;;;;;;; Write to file (xht--describe "Functions that write to file.") (defun h-write! (filename obj &optional mustbenew) "Write OBJ to file FILENAME. OBJ may represent any of the input and output data structures of the conversion functions. - If they are of string type (as kvl, *sv, org tables), they are written as they are — strings. - Anything else (numbers, lists, vectors, hash tables) is written as it looks like — as an s-expression. Filename is created if nonexistent, and overwritten if existent — but the parent directory, if it doesn't exist, is only created if the \\='f library is installed and loaded. The optional arg MUSTBENEW, if non-nil, insists on a check for an existing file with the same name. If MUSTBENEW is ‘excl’, that means you get an error if the file already exists; never overwrite. If MUSTBENEW is neither nil nor ‘excl’, that means ask for confirmation before overwriting, but do go ahead and overwrite the file if you confirm. Note that hash table objects are written using their #s(hash-table…) raw representation. For finer control of how hash tables are written, including the option of using (h*…) or (ht…) representations, see the specialized ‘h-write-ht-like!’." (when (fboundp 'f-parent) (mkdir (f-parent filename) '-p)) (write-region (h-as-string obj) nil filename nil nil nil mustbenew)) (defun h-write-ht-like! (filename format ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as FORMAT. For more about FILENAME and MUSTBENEW, see ‘h-write!’. HT-LIKE may come in any of the following autodetected formats: As Lisp objects: 1. #s(hash-table…) = regular representation 2. (ht…) form 3. (h*…), (h-s*…), (h-t*…), or (h-st*…) form or any of the above formatted as string (presumably with %S) FORMAT is how you want your hash table to be written to FILENAME, and may be any one of the following: 1. htbl-cm :: #s(hash-table…) = compact representation 2. h*-cm :: (h*…) in compact format 3. ht-cm :: (ht…) in compact format 4. htbl-pp :: #s(hash-table…) = in pretty-printed format 5. h*-pp :: (h*…) in pretty-printed format 6. ht-pp :: (ht…) in pretty-printed format For convenience, you may drop the -pp and it will be implied: 4. htbl :: #s(hash-table…) = in pretty-printed format 5. h* :: (h*…) in pretty-printed format 6. ht :: (ht…) in pretty-printed format \(It's implicit that these are strings, since we're writing to a file, so we can do without -str suffixes) Any of these FORMAT options may be passed as either a string, a keyword, or a symbol — as you prefer. So, for example, any among \"h*-pp\", :h*-pp, or \\='h*-pp works. When the table being read has an equality test different than \\='equal and either h*-cm or h*-pp is chosen, the output form will be made into (h-st*…), for which size and test are explicit, thereby preserving the original test equality information. (This can't be done with the (ht…) form, for which the equality test, if not \\='equal, would be implicitly converted to \\='equal.) Which of the five should you choose? - Either of the pretty-printed h-formats are much more readable by humans and can be easily browsed in the command line with the \"less\" command line utility. Disadvantages: 1. conversion to and from h-formats costs a bit of time, which might become noticeable when dealing with larger tables. 2. less portable, depends on this library. 3. occupies a tiny bit more of disk space (about which you probably don't care) because of the added whitespace. - The compact regular form and the compact h-forms often occupy a single line (unless their keys or values have line breaks), and as such aren't suitable for using with \"less\" — it'll flow straight to the end. And if you read any of them into a buffer, you may run into Emacs problem of displaying buffers with very long lines. - The regular form has the advantage of being universal, which may be preferable if the file is shared or if for some reason you don't have access to this and/or the \\='ht libraries. But it's harder for human reading, especially when nested, because it's displayed unformatted in one line, and because of its metadata. - The regular form is also faster. If you're dealing with huge hash tables, it'll be faster to write and read regular compact forms than converting them. - The h-forms may have more security issues when reading files from untrustworthy sources. Consider that this: #+begin_src emacs-lisp #s(hash-table test equal data (:a (+ 2 3))) #+end_src has as value for :a the quoted expression \\='(+ 2 3), whereas any of these: #+begin_src emacs-lisp (h* :a (+ 2 3)) (ht (:a (+ 2 3))) #+end_src will see it evaluated to 5 if you read with ‘h-read-h’ a file containing it. This seems unlikely to be a problem if you control the reading and writing. In any case, you can inspect it by using ‘h-read-list’ instead — which will assume it's just a plain list and return it to you quoted, without evaluating it. If it's a huge expression you may want to apply pp to it. Compare the results of: #+begin_src emacs-lisp (h-read-h \"(ht (:a (+ 2 3)))\") (h-read-list \"(ht (:a (+ 2 3)))\") (pp (h-read-list \"(ht (:a (+ 2 3)))\")) #+end_src Any of the h-forms can be easily read and re-converted to any hash-table–like format with ‘h-format’, which see. For convenience, partial-application functions are available for the different formats: - ‘h-write-htbl-cm!’, ‘h-write-htbl-pp!’, - ‘h-write-h*-cm!’, ‘h-write-h*-pp!’, - ‘h-write-ht-cm!’, ‘h-write-ht-pp!’ The pp ones are aliased, respectively, to the shorter: - ‘h-write-htbl!’, ‘h-write-h*!’ and ‘h-write-ht!’. See also: ‘h-read-h’ and ‘h-read’." (h-write! filename (h-format (pcase (h-as-keyword format) (:h*-cm :h*-cm-str) (:ht-cm :ht-cm-str) (:htbl-cm :htbl-cm-str) ((or :h* :h*-pp) :h*-pp-str) ((or :ht :ht-pp) :ht-pp-str) ((or :htbl :htbl-pp) :htbl-pp-str) (_ (user-error "‘h-write-ht!’: Invalid format %s" format))) ht-like) mustbenew)) (defsubst h-write-htbl-cm! (filename ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as \\='htbl-cm. For more about this output format, see ‘h-write-ht!’. For more about FILENAME and MUSTBENEW, see ‘h-write!’." (h-write-ht-like! filename 'htbl-cm ht-like mustbenew)) (defsubst h-write-htbl-pp! (filename ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as \\='htbl-pp. For more about this output format, see ‘h-write-ht!’. For more about FILENAME and MUSTBENEW, see ‘h-write!’." (h-write-ht-like! filename 'htbl-pp ht-like mustbenew)) (defsubst h-write-h*-cm! (filename ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as \\='h*-cm. For more about this output format, see ‘h-write-ht!’. For more about FILENAME and MUSTBENEW, see ‘h-write!’." (h-write-ht-like! filename 'h*-cm ht-like mustbenew)) (defsubst h-write-ht-cm! (filename ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as \\='ht-cm. For more about this output format, see ‘h-write-ht!’. For more about FILENAME and MUSTBENEW, see ‘h-write!’." (h-write-ht-like! filename 'ht-cm ht-like mustbenew)) (defsubst h-write-h*-pp! (filename ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as \\='h*-pp. For more about this output format, see ‘h-write-ht!’. For more about FILENAME and MUSTBENEW, see ‘h-write!’. Alias: ‘h-write-h*!’." (h-write-ht-like! filename 'h*-pp ht-like mustbenew)) (defsubst h-write-ht-pp! (filename ht-like &optional mustbenew) "Write hash table HT-LIKE to file FILENAME formatted as \\='ht-pp. For more about this output format, see ‘h-write-ht!’. For more about FILENAME and MUSTBENEW, see ‘h-write!’. Alias: ‘h-write-ht!’." (h-write-ht-like! filename 'ht-pp ht-like mustbenew)) ;;;;;;; Read (xht--describe "Functions that read objects, possible from a file or a buffer. The generic function is ‘h-read’, with others that take one argument offered for convenience — so, for example: #+begin_src emacs-lisp (h-read-alist obj) #+end_src is the same as #+begin_src emacs-lisp (h-read \\='alist obj) #+end_src Note that ‘h-read-htbl’ expects OBJ to be of regular hash table type: #s(hash-table…). But we also have a ‘h-read-h’ that also accepts, quoted, the h-forms: \\='(ht…), \\='(h*…), \\='(h-s*…), \\='(h-t*…), or \\='(h-st*…). It also accepts any of these passed as a string: \"#s(hash-table…)\", \"(h*…)\", etc. This makes it flexible to read from files, and to have hash tables stored in files as h-forms.") (defun h-read (type obj) "Deal with object OBJ, expected to be of type TYPE. - If OBJ, a string, is a valid path of an existing file, read as string the contents of file. - If OBJ is a buffer, read as string the contents of buffer. - Otherwise, take OBJ as it is. Then read it and test it according to ‘xht--to-type’, which see. TYPE is a symbol, and can be one of: - \\='s OR \\='str OR \\='string - \\='vec OR \\='vector - \\='alist - \\='plist - \\='list - \\='cons-pair - \\='lol - \\='htbl - \\='ht-like When dealing with TSV, CSV, SSV, Org Table, or JSON: use \\='str (or \\='s, or \\='string) or the respective associated functions — same thing. When dealing with hash tables, you have two options: - \\='htbl expects something looking strictly like #s(hash-table…), and signals error otherwise. - \\='ht-like is flexible and accepts any ht-like object — which is a Lisp object or string that looks like or contains one among: #s(hash-table…), (ht…), (h*…), (h-s*…), (h-t*…), or (h-st*…). TYPE represents the expectation of what type the string input should be, so it signals error when mismatched. So if, for example, you expect your input to be a hash table #s(hash-table…), this: #+begin_src emacs-lisp (h->lol (h-read \\='htbl \"/path/to/hash-table.el\")) #+end_src would read this file looking for a hash table, and return it as such (as Lisp object, no longer a string), to be converted to a list of lists as specified. If it found, say, an alist in the file it would signal error. You could also use the equivalent: #+begin_src emacs-lisp (-> \"/path/to/hash-table.el\" h-read-htbl h->lol) #+end_src and this also works: #+begin_src emacs-lisp (-> (ht (:a 1) (:b 2)) h-read-htbl h->lol) #+end_src In this case, ‘h-read-htbl’ returns the very hash table read, like ‘identity’. No problem, but unnecessary — this would suffice: #+begin_src emacs-lisp (-> (ht (:a 1) (:b 2)) h->lol) #+end_src For convenience, partial-application functions are available which receive only OBJ as argument: ‘h-read-s’ plus ‘h-read-kvl’, ‘h-read-tsv’, ‘h-read-csv’, ‘h-read-ssv’, ‘h-read-json’, ‘h-read-orgtbl’, ‘h-lines’ (all are ‘h-read-s’), plus ‘h-read-lol’, ‘h-read-alist’, ‘h-read-plist’, ‘h-read-list’, ‘h-read-cons-pair’, ‘h-read-vector’ and for hash tables: - ‘h-read-htbl’ for (h-read \\='htbl…) = #s(hash-table…) only - ‘h-read-h’ for (h-read \\='ht-like…) = any ht-like form. See also: ‘h-write!’ and ‘h-write-ht-like!’." (declare (side-effect-free t)) (cond ((stringp obj) (xht--to-type (cond ((not (xht--str-nw? obj)) obj) ((file-exists-p obj) (xht--file-str-no-prop obj)) (t obj)) type)) ((bufferp obj) (xht--to-type (xht--buff-str-no-prop) type)) ((eq type 'ht-like) (xht--to-type (format "%S" obj) type)) (t obj))) (defsubst h-read-ht-like (obj) "Deal with object OBJ, expected to be ht-like. For more, see ‘h-read’. Alias: ‘h-read-h’." (h-read 'ht-like obj)) (defsubst h-read-htbl (obj) "Deal with object OBJ, expected to be a hash table. For more, see ‘h-read’." (h-read 'htbl obj)) (defsubst h-read-s (obj) "Deal with object OBJ, expected to be a string. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-kvl (obj) "Deal with object OBJ, expected to be a KVL. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-tsv (obj) "Deal with object OBJ, expected to be a TSV. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-csv (obj) "Deal with object OBJ, expected to be a CSV. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-ssv (obj) "Deal with object OBJ, expected to be a SSV. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-json (obj) "Deal with object OBJ, expected to be JSON. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-lines (obj) "Deal with object OBJ, expected to be lines of text. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-orgtbl (obj) "Deal with object OBJ, expected to be an org table. For more, see ‘h-read’." (h-read 'str obj)) (defsubst h-read-lol (obj) "Deal with object OBJ, expected to be a list of lists. For more, see ‘h-read’." (h-read 'lol obj)) (defsubst h-read-alist (obj) "Deal with object OBJ, expected to be an association list. For more, see ‘h-read’." (h-read 'alist obj)) (defsubst h-read-plist (obj) "Deal with object OBJ, expected to be a property list. For more, see ‘h-read’." (h-read 'plist obj)) (defsubst h-read-list (obj) "Deal with object OBJ, expected to be a list. For more, see ‘h-read’." (h-read 'list obj)) (defsubst h-read-cons-pair (obj) "Deal with object OBJ, expected to be a cons pair. For more, see ‘h-read’." (h-read 'cons-pair obj)) (defsubst h-read-vector (obj) "Deal with object OBJ, expected to be a vector. For more, see ‘h-read’." (h-read 'vector obj)) ;;;;;; Miscellaneous ;;;;;;; Format object as symbol, string, keyword, or number (xht--describe "Functions to turn object into a desirable format. These functions were initially internal only (and therefore named as \"xht--as-string\", etc), since they weren't directly about hash tables. But they are useful for formatting keys and values, which is a common enough operation — so I renamed them and made them public. The alternative would have been to have you either write the code yourself every time you wanted to do these operations, or else use the longer-named internal functions, both of which seemed worse.") (defun h-as-string (obj) "Return OBJ as a string." (declare (pure t) (side-effect-free t)) (cond ((keywordp obj) (xht--keyword->string obj)) ((symbolp obj) (format "%s" obj)) ((stringp obj) obj) (t (format "%S" obj)))) (defun h-as-keyword (obj) "Return OBJ as a keyword." (declare (pure t) (side-effect-free t)) (cond ((keywordp obj) obj) ((symbolp obj) (xht--symbol->keyword obj)) ((stringp obj) (xht--string->keyword obj)) (t (xht--string->keyword (format "%S" obj))))) (defun h-as-symbol (obj) "Return OBJ as an interned symbol." (declare (pure t) (side-effect-free t)) (cond ((keywordp obj) (xht--keyword->symbol obj)) ((symbolp obj) obj) ((stringp obj) (intern obj)) (t (intern (format "%S" obj))))) (defun h-as-number (obj) "Return OBJ as a number, if convertible. If not convertible, signal error." (declare (pure t) (side-effect-free t)) (cond ((numberp obj) obj) ((stringp obj) (xht--string->number obj)) ((symbolp obj) (xht--symbol->number obj)) ; same for keyword (t (error "h-as-number: Can't convert %s" obj)))) ;;;;; Specialized hash table operations ;;;;;; “Hash tabular”: 2D-specific operations (xht--describe "Functions to operate on two-dimensional hash tables. A 2D hash table looks like this: #+begin_src emacs-lisp (h* \"n01\" (h* \"n\" \"n01\" \"name\" \"Alice\") \"age\" \"42\" \"editor\" \"Emacs\" \"n02\" (h* \"n\" \"n02\" \"name\" \"Emily\" \"age\" \"21\" \"editor\" \"Nano\")) #+end_src It looks like this when converted to an org table: #+begin_src org | n | name | age | editor | |-----+-------+-----+--------| | n01 | Alice | 42 | Emacs | | n02 | Emily | 21 | Nano | #+end_src or to a list of lists: #+begin_src emacs-lisp '((\"n\" \"name\" \"age\" \"editor\") (\"n01\" \"Alice\" \"42\" \"Emacs\") (\"n02\" \"Emily\" \"21\" \"Nano\")) #+end_src or to SSV: #+begin_example n name age editor n01 Alice 42 Emacs n02 Emily 21 Nano #+end_example or to JSON: #+begin_src javascript { \"n01\": { \"n\": \"n01\", \"name\": \"Alice\" \"age\": \"42\", \"editor\": \"Emacs\", }, \"n02\": { \"n\": \"n02\", \"name\": \"Emily\" \"age\": \"21\", \"editor\": \"Nano\", } } #+end_src Name of its elements (I'm removing the string quotes here): - header: '(n name age editor) - idcol: '(n n01 n02) - id: n Either of these three triads of labels could be given in reference to \"Alice\" in the example above: | n01 | name | Alice | |-----+--------------+----------------| | key | key of value | value of value | | key | field | value | | row | column | cell | - The first is exact but awkward. - In code I often opted for the second. - The third is also nice, but row and column most often alludes to the entire row and column, not to the first item of either — to which we want to refer here. But it's understood if we say that the \"intersection of row 'n01' and column 'name' is 'Alice'\". This is \"field 'name' of key 'n01'\" (second) or the awkward \"the value of the key 'name' of the value of key 'n01'\" (first). See also: ‘h-1d<-2d’ and ‘h-2d<-1d’ under General hash table operations > Conversion > 2D > Hash table to hash table.") ;;;;;;; Creation (xht--describe "Functions for creating 2D hash tables.") (defun h-2d-new (keys header &optional size test) "Return a 2D hash table from KEYS and HEADER. Both must be lists. If either is nil, or not a list, return empty hash table. All field values are set to nil, where field is an element of header. For the meaning of SIZE and TEST, see ‘h-new’." (declare (pure t) (side-effect-free t)) (if (not (and keys header (listp keys) (listp header))) (h-new) (let* ((id (car header)) (htbl (h-new (or size (length keys)) test)) (nils (-repeat (length header) nil)) (hnils (h<-plist (-interleave header nils)))) (dolist (key keys htbl) (h-put! htbl key (h-put hnils id key)))))) ;;;;;;; Information (xht--describe "Functions for retrieving information about 2D hash tables. Note: ‘h-2d-keys’ is an alias to ‘h-lkeys’ (for consistency).") (defun h-2d-header (table2d) "Return the header of 2D hash table TABLE2d. The header is the union of all keys-of-keys of TABLE2D. This function does the heavy work to correctly guess what the table's ID would be — which ‘h-2d-id’ then uses." (declare (pure t) (side-effect-free t)) (let (values headers-lol common union candidates keys id) (when (h? table2d) (unless (h-empty? table2d) (setq values (h-values table2d)) (when (-all? #'h? values) (setq headers-lol (-map #'h-keys values) common (-reduce #'-intersection headers-lol) union (-reduce #'-union headers-lol)) (unless (-any? #'h? union) (when common (setq keys (h-keys table2d)) (dolist (candidate common) (when (-none? #'null (--map (equal it (ht-get* table2d it candidate)) keys)) (push candidate candidates))) (setq id (-last-item candidates)) (when id (->> union (-remove-item id) (cons id)))))))))) (defun h-2d-idcol (table2d) "The ID column of 2D hash table TABLE2D, as list. It's ID + keys." (declare (pure t) (side-effect-free t)) (let ((id (h-2d-id table2d))) (when id (cons id (h-2d-keys table2d))))) (defun h-2d-id (table2d) "Find the ID-like element of the header of 2D hash table TABLE2D. It's the one associated with the table's keys. If none is found, return nil, in which case it's not a 2D hash table. If more than one is found (unlikely), use the first." (declare (pure t) (side-effect-free t)) (car (h-2d-header table2d))) (defun h-2d-row (table2d key &optional header) "List row from 2D hash table TABLE2D given KEY and HEADER. Example: (h-2d-row tb2 \"id01\" \\='(:id :name :age)) could return: \\='(\"id01\" \"Mary\" 27). HEADER can be given in some desirable permutation. If HEADER is nil, it will be inferred with ‘h-2d-header’." (declare (pure t) (side-effect-free t)) (let ((id (h-2d-id table2d)) res) (when id (unless header (setq header (h-2d-header table2d))) (if (equal key id) header (dolist (field (cdr header) (cons key (nreverse res))) (push (ht-get* table2d key field) res)))))) (defun h-2d-col (table2d col &optional idcol) "List column from 2D hash table TABLE2D given COL and IDCOL. Example: (h-2d-col tb2 :name \\='(:id id01 id03 id02)) could return: \\='(:name \"Mary\" \"John\" \"Jane\"). IDCOL is the ID plus keys. It can be given in some desirable permutation. If IDCOL is nil, it will be inferred with ‘h-2d-idcol’." (declare (pure t) (side-effect-free t)) (let ((id (h-2d-id table2d)) res) (when id (unless idcol (setq idcol (h-2d-idcol table2d))) (if (equal col id) idcol (dolist (key (cdr idcol) (cons col (nreverse res))) (push (ht-get* table2d key col) res)))))) ;;;;;;; Mapping (xht--describe "Functions for mapping 2D hash tables.") ;;;;;;;; to hash table 2D (xht--describe "Functions that apply three functions or forms to every key–field–value triad of a 2D hash table and write the results to a hash table. You can think of the key as the label of the row of the equivalent org table it would produce; of the field as the label of the column; and of the value as the contents of the cell. For example, if you want to produce a fresh table2d whose keys are upcased, fields are downcased, and the values \(currently integers) are increased by 1, you could run: #+begin_src emacs-lisp (h-2d-hmap (lambda (key _field _value) (upcase key)) (lambda (_key field _value) (downcase field)) (lambda (_key _field value) (1+ value)) table2d) #+end_src The underlines above are so that there's no warnings about unused variables. The fact that in each function the only variable used was that which it changes needed not be so. You could have well had (upcase (concat key \"-\" field)) in the first one, for example. But it's unlikely you'd want that. Most often you'd want to only use key for the first one, only field for the second one, and the third one could have any combination. Finally, note that when the field is the table's ID (the table's \"first column\"), the value will be the newly calculated key instead of applying the value function to it. This is almost certainly what you wanted to do: apply the value functions to all columns but the first one, which is what you'd do in a spreadsheet with labeled rows and columns. Note that the item is only added if both KEY-FUN and FIE-FUN return non-nil. So no spurious (nil, something) triad results from some reasonable conditional KEY-FUN such as: #+begin_src emacs-lisp (lambda (key, field, value) (when (> value 2) key)) #+end_src In the rare case where for some reason you chose to have nil as a key, you'll have to treat it specially. IMPORTANT: unlike mapping to a list, mapping to a hash table demands that the results of the keys be unique. So you must pay attention to possible collisions. If, for example, in the case above the original table had both \"a\" and \"A\" as original keys, one of them would end up overwritten, because both return \"A\" when upcased. Which one will depend on the key order. So: actual size of resulting table ≤ size of original table. In mathematical terms, excepting the rare case of a nil key in the original, the number of keys in the resulting hash table will match the number of keys in the original one if, and only if: 1. there's a bijection between the set of keys and the set of key-fun(key,field,value). 2. there's no (key,field,value) for which either key-fun(key,field,value) or fie-fun(key,field,value) returns nil. IMPORTANT: Just because you can pass three arbitrary functions using all the three variables in each of them doesn't mean you should. If you, for example, selectively change a field for some keys but not others, you'll likely split the column, so one of them will have empty values where the other has something, and vice-versa. And other such odd operations. It seems unlikely that you'd want that. Guideline for the '2d-hmap family' of functions: - the added 'h' means 'result is a hash table' - an added '-' means 'anaphoric: enter 3 forms, not 3 lambdas' - an added '!' means 'destructive: modify TABLE instead of creating a fresh one' In the anaphoric versions, if you don't use any of the let-bound variables key, field, or value in any of the forms, it's ok — no warnings. Summary: |-----------------+--------------+---------------------| | | 3 lambdas | 3 forms (anaphoric) | |-----------------+--------------+---------------------| | non-destructive | ‘h-2d-hmap’ | ‘h--2d-hmap’ | | destructive | ‘h-2d-hmap!’ | ‘h--2d-hmap!’ | |-----------------+--------------+---------------------|") (defun h-2d-hmap (key-fun fie-fun val-fun table2d) "Return a 2D table from applying functions to each triad of TABLE2D. KEY-FUN, FIE-FUN, and VAL-FUN are each called with three arguments: key, field, and value. - The result of KEY-FUN is the new key. - The result of FIE-FUN is the new field. - The result of VAL-FUN is the new value. Its anaphoric counterpart is ‘h--2d-hmap’." (declare (pure t) (side-effect-free t)) (let ((id (h-2d-id table2d)) (result (h-clr table2d))) (maphash (lambda (key t2d-v) (maphash (lambda (field value) (let ((newk (funcall key-fun key field value)) (newf (funcall fie-fun key field value))) (and newk newf (h-put*! result newk newf (if (equal field id) newk (funcall val-fun key field value)))))) t2d-v)) table2d) result)) (defmacro h--2d-hmap (key-form fie-form val-form table2d) "Anaphoric version of ‘h-2d-hmap’. For every key–value pair in TABLE2D, evaluate KEY-FORM, FIE-FORM, VAL-FORM with the variables key, field, and value bound." `(h-2d-hmap (lambda (key field value) (ignore key field value) ,key-form) (lambda (key field value) (ignore key field value) ,fie-form) (lambda (key field value) (ignore key field value) ,val-form) ,table2d)) (defun h-2d-hmap! (key-fun fie-fun val-fun table2d) "Update TABLE2D by applying functions to each of its triads. Functions are KEY-FUN, FIE-FUN, VAL-FUN. Just like ‘h-2d-hmap’ (which see), but modifies TABLE2D instead of producing a fresh one. Its anaphoric counterpart is ‘h--2d-hmap!’." (let ((id (h-2d-id table2d))) (maphash (lambda (key t2d-v) (remhash key table2d) (maphash (lambda (field value) (let ((newk (funcall key-fun key field value)) (newf (funcall fie-fun key field value))) (and newk newf (h-put*! table2d newk newf (if (equal field id) newk (funcall val-fun key field value)))))) t2d-v)) table2d))) (defmacro h--2d-hmap! (key-form fie-form val-form table2d) "Anaphoric version of ‘h-2d-hmap!’. For every key–value pair in TABLE2D, evaluate KEY-FORM, FIE-FORM, VAL-FORM with the variables key, field, and value bound." `(h-2d-hmap! (lambda (key field value) (ignore key field value) ,key-form) (lambda (key field value) (ignore key field value) ,fie-form) (lambda (key field value) (ignore key field value) ,val-form) ,table2d)) ;;;;;;; Selection or rejection of columns (xht--describe "Functions for selecting or rejecting columns from 2D hash tables.") (defun h-2d-sel-cols (table2d cols) "Return a table with only selected named columns COLS of TABLE2D. Any column of COLS that isn't in TABLE2D is ignored. The first column (IDCOL), containing the keys, is always implicitly selected: otherwise non-repeatability of keys wouldn't be guaranteed. This function doesn't modify TABLE2D. Its destructive counterpart is ‘h-2d-sel-cols!’." (declare (pure t) (side-effect-free t)) (when (xht--ht-and-empty-or-2d+-or-error table2d) (let ((htbl (h-clone* table2d))) (h-2d-sel-cols! htbl cols) htbl))) (defun h-2d-sel-cols! (table2d cols) "Remove all but named columns COLS from 2D hash table TABLE2D. The first column (IDCOL), containing the keys, is always implicitly selected: otherwise non-repeatability of keys wouldn't be guaranteed. This function modifies TABLE2D. Its non-destructive counterpart is ‘h-2d-sel-cols’." (when (xht--ht-and-empty-or-2d+-or-error table2d) (let ((cs (-uniq (cons (h-2d-id table2d) (-list cols))))) (h--hmap! key (h-sel (lambda (k _v) (member k cs)) value) table2d)))) (defun h-2d-rej-cols (table2d cols) "Return a table without the named columns COLS of TABLE2D. Any column of COLS that isn't in TABLE2D is ignored. The first column (IDCOL), containing the keys, is never rejected: otherwise non-repeatability of keys wouldn't be guaranteed. This function doesn't modify TABLE2D. Its destructive counterpart is ‘h-2d-rej-cols!’." (declare (pure t) (side-effect-free t)) (when (xht--ht-and-empty-or-2d+-or-error table2d) (let ((htbl (h-clone* table2d)) (cs (-uniq (-remove-item (h-2d-id table2d) (-list cols))))) (when cs (h-each table2d (lambda (key value) (h-put! htbl key (dolist (col cs value) (h-rem! value col)))))) htbl))) (defun h-2d-rej-cols! (table2d cols) "Remove named columns COLS from 2D hash table TABLE2D. Any column of COLS that isn't in TABLE2D is ignored. The first column (IDCOL), containing the keys, is never rejected: otherwise non-repeatability of keys wouldn't be guaranteed. This function modifies TABLE2D. Its non-destructive counterpart is ‘h-2d-rej-cols’." (when (xht--ht-and-empty-or-2d+-or-error table2d) (let ((cs (-uniq (-remove-item (h-2d-id table2d) (-list cols))))) (when cs (h-each table2d (lambda (key value) (h-put! table2d key (dolist (col cs value) (h-rem! value col)))))) table2d))) ;;;;;;; Transposition (xht--describe "Functions for transposing 2D hash tables.") (defun h-2d-transpose (table2d) "Return a hash table that is TABLE2D transposed." (declare (pure t) (side-effect-free t)) (when (xht--ht-and-empty-or-2d+-or-error table2d) (let (header keys id res) (setq header (h-2d-header table2d) keys (h-keys table2d) res (h-new (length header) (h-prop table2d 'test)) id (h-2d-id table2d)) (dolist (field (cdr header) res) (dolist (key keys) (h-put*! res field id field) (h-put*! res field key (h-get* table2d key field))))))) (defun h-2d-transpose! (table2d) "Transpose TABLE2D." (let ((htbl (h-2d-transpose table2d))) (h-clr! table2d) (h-mix! table2d htbl))) ;;;;;;; helper (defun xht--ht-and-empty-or-2d+-or-error (obj) "Error if OBJ isn't hash table or of dimension 0 or 2+. Else t." (declare (pure t) (side-effect-free t)) (let ((type (h-type obj)) dim) (unless (equal type :ht) (user-error "Input is %s, not hash table" type)) (setq dim (h-dim obj)) (if (< 0 dim 2) (user-error "Input is %dD hash-table, can't do it" dim) t))) ;;;;;; Hash tables as sets (xht--describe "Functions for creating and manipulating hash tables as sets. Values are all set to t, and all that matters is the presence of keys. Note: once created, hash-tables-as-sets can be manipulated with the same functions for regular ones, including union (‘h-mix’), difference (‘h-dif’), and intersection (‘h-cmn’) and their destructive counterparts.") (defun h-as-set<-vector (vector &optional size test) "Create a hash table as set from VECTOR. The KEYS will be the elements of the vector, and all VALUES will be t. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (let ((result (h-new (or size (length vector)) (or test 'equal))) (idx 0)) (while (< idx (length vector)) (h-put! result (aref vector idx) t) (setq idx (1+ idx))) result)) (defun h-as-set<-list (list &optional size test) "Create a hash table as set from LIST. The KEYS will be the elements of the list, and all VALUES will be t. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (let ((result (h-new (or size (length list)) (or test 'equal)))) (dolist (key list result) (h-put! result key t)))) (defun h-as-set<-lines (str &optional size test) "Create a hash table as set from the lines of string STR. The KEYS will be the lines, and all VALUES will be t. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (h-as-set<-list (s-lines str) size test)) (defun h-as-set<-words (str &optional size test) "Create a hash table as set from the words of string STR. The KEYS will be the words, and all VALUES will be t. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (h-as-set<-list (s-split-words str) size test)) ;;;;;; Hash tables for counting (xht--describe "Functions for using hash tables for counting.") (defun h-count<-vector (vector &optional size test) "Create a hash table with the elements of VECTOR and their count. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (let ((result (h-new (or size (length vector)) (or test 'equal))) (idx 0) key) (while (< idx (length vector)) (setq key (aref vector idx)) (h-put! result key (1+ (or (h-get result key) 0))) (setq idx (1+ idx))) result)) (defun h-count<-list (list &optional size test) "Create a hash table with the elements of LIST and their count. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (let ((result (h-new (or size (length list)) (or test 'equal)))) (dolist (key list result) (h-put! result key (1+ (or (h-get result key) 0)))))) (defun h-count<-lines (str &optional size test) "Create a hash table with the lines of string STR and their count. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (h-count<-list (s-lines str) size test)) (defun h-count<-words (str &optional size test) "Create a hash table with the words of string STR and their count. Optional arguments SIZE and TEST may be passed." (declare (pure t) (side-effect-free t)) (h-count<-list (s-split-words str) size test)) (defun h-count-put (table &rest objs) "Create a hash table with objects OBJS with the counts as values. Each OBJ might be: - a hash table with counts for values: will be added to the count of TABLE. - a vector: each element will add 1 to the count of TABLE. - a list: each element will add 1 to the count of TABLE. - anything else: added as element: if new, with count 1, or otherwise added to the count of TABLE. This function is free of side-effects. Its destructive counterpart is ‘h-count-put!’." (declare (pure t) (side-effect-free t)) (let ((result (h-clone* table))) (apply #'h-count-put! result objs) result)) (defun h-count-put! (table &rest objs) "Add counts of objects OBJS to the counts of hash table TABLE. This function modifies TABLE. Its side-effects–free counterpart is ‘h-count-put’." (dolist (obj objs) (pcase (type-of obj) ('hash-table (xht--count-put-ht! table obj)) ('cons (xht--count-put-list! table obj)) ('vector (xht--count-put-vector! table obj)) (_ (xht--count-put-elem! table obj))))) ;;;;;;; helper (defun xht--count-put-ht! (table table2) "Add counts of TABLE2 to TABLE." (h--each table2 (h-put-num-with*! (-partial #'+ (h-as-number value)) table key))) ;; Above we had this: ;; (h-put! table key ;; (+ value (or (ht-get table key) 0))))) ;; Changed because ‘h-put-num-with*!’ also changes strings etc., ;; which table1 might have as value; more flexible. (defun xht--count-put-vector! (table vector) "Add counts of VECTOR to TABLE." (let ((idx 0) key) (while (< idx (length vector)) (setq key (aref vector idx)) (h-put-inc*! table key) (setq idx (1+ idx))))) (defun xht--count-put-list! (table list) "Add counts of LIST to TABLE." (dolist (key list) (h-put-inc*! table key))) (defun xht--count-put-elem! (table elem) "Add 1 to count of ELEM in TABLE." (h-put-inc*! table elem)) ;;;;; Helper ;;;;;; keyword <-> symbol <-> string <-> number (defalias 'xht--string->symbol 'make-symbol "Convert string to symbol.") (defun xht--keyword->symbol (keyword) "Convert KEYWORD to string." (->> keyword xht--keyword->string intern)) (defun xht--keyword->string (keyword) "Convert KEYWORD to string." (declare (pure t) (side-effect-free t)) (->> keyword (format "%s") (s-chop-prefix ":"))) (defalias 'xht--symbol->string 'xht--keyword->string "Convert symbol to string.") (defun xht--string->keyword (str) "Convert string STR to keyword." (->> str (s-chop-prefix ":") (s-prepend ":") intern)) (defun xht--symbol->keyword (symbol) "Convert SYMBOL to keyword." (->> symbol (format "%s") xht--string->keyword)) (defun xht--string->number (obj) "Convert string to number. Unlike ‘string-to-number’, signal error if OBJ can't be converted." (declare (pure t) (side-effect-free t)) (--> obj read (if (numberp it) it (error "‘xht--string->number’: Not a number: %s" it)))) (defalias 'xht--symbol->number 'xht--keyword->number "Convert symbol to number.") (defun xht--keyword->number (obj) "Convert keyword to number. Signal error if OBJ can't be converted." (declare (pure t) (side-effect-free t)) (--> obj (format "%s" it) (s-chop-prefix ":" it) read (if (numberp it) it (error "‘xht--keyword->number’: Not a number: %s" it)))) (defalias 'xht--number->string 'number-to-string "Convert number to string.") (defun xht--number->keyword (obj) "Convert number to keyword. Signal error if OBJ can't be converted." (--> obj (format "%s" it) (s-prepend ":" it) intern)) (defun xht--number->symbol (obj) "Convert number to symbol. Signal error if OBJ can't be converted." (--> obj (format "%s" it) intern)) ;;;;;; Convert from string to types + types predicates ;;;;;;; string to type (selector) (defun xht--to-type (str &optional type) "Convert string STR to TYPE. If TYPE is \\='htbl, read STR as if it's a stringified #s(hash-table…). If TYPE is \\='ht-like, read STR as if it's an ht-like string, which is one containing #s(hash-table…), (ht…), (h*…), (h-s*…), (h-t*…), or (h-st*…). If TYPE is \\='lol, read STR as if it's a stringified ((list)(of)(lists)). If TYPE is \\='alist, read STR as if it's a stringified ((a . list)). If TYPE is \\='plist, read STR as if it's a stringified (p list). If TYPE is \\='list, read STR as if it's a stringified (list). If TYPE is \\='cons-pair, read STR as if it's a stringified (cons . pair). If TYPE is \\='vector, read STR as if it's a stringified [vector]. If TYPE is \\='str or nil, return STR. Otherwise signal error." (declare (pure t) (side-effect-free t)) (cond ((memq type '(ht-like)) (xht--htstr-to-htbl str)) ((memq type '(htbl)) (xht--str-to-htbl str)) ((memq type '(lol)) (xht--str-to-lol str)) ((memq type '(alist)) (xht--str-to-alist str)) ((memq type '(plist)) (xht--str-to-plist str)) ((memq type '(list)) (xht--str-to-list str)) ((memq type '(cons-pair)) (xht--str-to-cons-pair str)) ((memq type '(vec vector)) (xht--str-to-vector str)) ((memq type '(s str string)) str) ((null type) str) (t (error "‘xht--to-type’: Unknown type ‘%s’" type)))) ;;;;;;; string to hash table (defun xht--htstr-to-htbl (str) "Read STR as if it's an ht-like string. An ht-like string is one whose contents look like one of these: #s(hash-table…), (ht…), (h*…), (h-s*…), (h-t*…), or (h-st*…). Look for objects that, when read, either already are a hash table or will be evaluated to one through one of these canonical forms. If one is found, format it as a hash table and return it. If not, signal error. See also: ‘xht--str-to-htbl’ looks strictly for the #s(hash-table…) format." (declare (pure t) (side-effect-free t)) (if (xht--str-nw? str) (h-format :htbl str) (h-new 1))) (defun xht--str-to-htbl (str) "Read STR as if it's a stringified #s(hash-table…). If STR is empty, return empty hash table. If not empty but doesn't look like a hash table, signal error. See also: ‘xht--htstr-to-htbl’ looks more broadly for ht-like formats." (declare (pure t) (side-effect-free t)) (if (xht--str-nw? str) (->> str read xht--htbl-or-error) (h-new))) (defun xht--htbl-or-error (obj) "Test if OBJ is a hash table. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (ht? obj) obj (error "Not a hash table: %s" obj)))) ;;;;;;; string to alist (defun xht--str-to-alist (str) "Read STR as if it's a stringified ((a . list)). If STR is empty, return \\='(()). If not empty but doesn't look like an alist, signal error." (declare (pure t) (side-effect-free t)) (when (xht--str-nw? str) (->> str read xht--dequote-list xht--alist-or-error xht--canonicalize-alist))) (defun xht--canonicalize-alist (alist) "Canonicalize ALIST." (declare (pure t) (side-effect-free t)) (-non-nil alist)) (defun xht--alist-or-error (obj) "Test if OBJ is an alist. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (h-alist? obj) obj (error "Not an alist: %s" obj)))) ;;;;;;; string to plist (defun xht--str-to-plist (str) "Read STR as if it's a stringified \\='(p list). If STR is empty, return \\='(()). If not empty but doesn't look like an plist, signal error." (declare (pure t) (side-effect-free t)) (when (xht--str-nw? str) (->> str read xht--dequote-list xht--plist-or-error xht--canonicalize-plist))) (defun xht--canonicalize-plist (plist) "Canonicalize PLIST." (declare (pure t) (side-effect-free t)) plist) (defun xht--plist-or-error (obj) "Test if OBJ is a plist. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (h-plist? obj) obj (error "Not a plist: %s" obj)))) ;;;;;;; string to lol (list of lists) (defun xht--str-to-lol (str) "Read STR as if it's a stringified ((list)(of)(lists)). If STR is empty, return \\='(()). If not empty but doesn't look like a lol, signal error." (declare (pure t) (side-effect-free t)) (when (xht--str-nw? str) (->> str read xht--dequote-list xht--lol-or-error xht--canonicalize-lol))) (defun xht--canonicalize-lol (lol) "Canonicalize list of lists LOL. Remove any \\='hlines, make one-item lists of any atomic elements." (declare (pure t) (side-effect-free t)) (->> lol (-remove-item 'hline) ;; ^ if it came from an org-table, it might have ;; 'hlines, which we remove. (--map (if (atom it) ; this would probably work, too: (-map #'-list) (list it) it)))) (defun xht--lol-or-error (obj) "Test if OBJ is a list of list. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (h-lol? obj) obj (error "Not a list of lists: %s" obj)))) (defun xht--dequote-list (list) "Dequote LIST recursively." ;; If in a file, for example, it is quoted, we remove that, ;; for it'll be quoted again when read from string. (declare (pure t) (side-effect-free t)) (let ((l list)) (while (equal 'quote (car l)) (setq l (cadr l))) l)) ;;;;;;; string to list (defun xht--str-to-list (str) "Read STR as if it's a stringified (list). If STR is empty, return \\='(). If not empty but doesn't look like a list, signal error." (declare (pure t) (side-effect-free t)) (if (xht--str-nw? str) (->> str read xht--dequote-list xht--list-or-error) '())) (defun xht--list-or-error (obj) "Test if OBJ is a list. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (listp obj) obj (error "Not a list: %s" obj)))) ;;;;;;; string to cons pair (defun xht--str-to-cons-pair (str) "Read STR as if it's a stringified (cons . pair). If STR is empty, return \\='(). If not empty but doesn't look like a cons pair, signal error." (declare (pure t) (side-effect-free t)) (if (xht--str-nw? str) (->> str read xht--dequote-list xht--cons-pair-or-error) '())) (defun xht--cons-pair-or-error (obj) "Test if OBJ is a cons cell. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (-cons-pair? obj) obj (error "Not a cons pair: %s" obj)))) ;;;;;;; string to vector (defun xht--str-to-vector (str) "Read STR as if it's a stringified [vector]. If STR is empty, return []. If not empty but doesn't look like a vector, signal error." (declare (pure t) (side-effect-free t)) (if (xht--str-nw? str) (->> str read xht--vector-or-error) [])) (defun xht--vector-or-error (obj) "Test if OBJ is a list. Return OBJ or signal error." (declare (pure t) (side-effect-free t)) (when obj (if (vectorp obj) obj (error "Not a vector: %s" obj)))) ;;;;;; Miscellaneous (defun xht--str-nw? (str) "Test if string STR contains a non-whitespace character. If so, return STR. Otherwise return nil." ;; Adapted from org-macs.el's ‘org-string-nw-p’. (declare (pure t) (side-effect-free t)) (and (stringp str) (string-match-p "[^ \r\t\n]" str) str)) (defun xht--quote (obj) "Quote OBJ for printed representation when OBJ is symbol or list. Strings, numbers, keywords, and vectors need no treatment." (declare (pure t) (side-effect-free t)) (let ((print-quoted t)) (pcase obj ((pred null) nil) ((pred keywordp) obj) ((pred symbolp) `',obj) ((pred listp) `',obj) (_ obj)))) ;; Is this ^ robust enough? Any other more straightforward way? (defun xht--non-aggressively-insert (&rest strings) "Insert STRINGS with ‘aggressive-indent-mode’ temporarily off." (if (bound-and-true-p aggressive-indent-mode) (prog2 (aggressive-indent-mode -1) (apply #'insert strings) (aggressive-indent-mode 1)) (apply #'insert strings))) (defun xht--file-str-no-prop (file) "FILE contents as string, no properties." (with-temp-buffer ;; Better to make it ‘-literally’? Why? (insert-file-contents file) (xht--buff-str-no-prop))) (defun xht--buff-str-no-prop () "Buffer string, no properties. \(Why doesn't Emacs have this handy function natively?)" (buffer-substring-no-properties (point-min) (point-max))) ;; Unused: decided to keep the behavior of ht<-alist, which returns ;; (ht (nil nil)) for alist '(()) instead of an empty hash list. (defun xht--remove-nested-nils (obj) "Reduce to nil \\='(((...))) in OBJ." (declare (pure t) (side-effect-free t)) (let ((x obj)) (while (and x (listp x) (listp (car x)) (null (cdr x))) (setq x (car x))) (if x obj nil))) ;;;;; Miscellaneous ;;;;;; Additional examples (xht--describe "Some additional examples that may be useful and don't fit any particular function.") (defun xht--misc-examples () "This function does nothing. It's just a hack to add to examples.el (and also to the README) a few miscellaneous tests and examples that aren't attached to any particular function." (ignore)) ;;;; Commands ;;;;; XHT dispatcher (xht--describe "With the dispatcher you can interactively convert, display, insert, or write to disk whatever looks like a hash table or could be converted from/into one. There're also some specialized functions that offer you \"speed dial\" access to the dispatcher options.") ;;;;;; Main dispatcher ;;;###autoload (defun xht-do (&optional ht-opts) "The main command for you to do all sorts of hash table things. This is XHT's main dispatcher. - Launch it to convert, display, insert, write to disk. - It reads and write from (almost) everything that can be transformed from or into a hash table. - Input can be the last sexp, some thing at point, region selection, a file, your clipboard, or the whole buffer you're in. Try it. You can also make your own function to call it non-interactively. You must then provide HT-OPTS, a tiny hash table with options to bypass the input of any specific questions. This hash table may have any number of the up to six variables holding answers to the questions asked, namely: #+begin_src emacs-lisp \\='(do-what where-from where-to what-to format-to file-to) #+end_src For example, if you frequently convert the last sexp to hash table, or want to visualize the hash table before point as (h*…), and then have this inserted at point. In fact, this one should be particularly frequent, so we already have a function for it: ‘xht-do-h*-ify-last-sexp-to-point’, which is essentially this: #+begin_src emacs-lisp (xht-do (h* :do-what ?c :where-from ?x :where-to ?i :what-to ?h :format-to ?1 :file-to nil)) #+end_src That is: [c]onvert, from last se[x]p, [i]nsert at point, to [h]ash table, format #[1] (which is pretty-printed h* form). Try that command when point is after a #s(hash-table…) or alist. With that in mind you can create a similar one adapted to your needs. You can then key-bind it for quick access. See also these related specialized commands: - ‘xht-do-convert-dwim’ - ‘xht-do-h*-ify-dwim-to-new-buffer’ - ‘xht-do-h*-ify-dwim-to-point’ - ‘xht-do-h*-ify-selected-region-to-point’ ‘xht-do-h*-ify-thing-at-point-to-point’ ‘xht-do-h*-ify-last-sexp-to-point’" (interactive) (h-let (xht--do-ht-opts-cleanup ht-opts) (let* ((do-what (or .do-what ;;USER-INPUT (xht--do-convert-or-as-is))) ;; from -------> (tap-type (xht--do-type-of-thing-at-point)) (where-from (or .where-from ;;USER-INPUT (xht--do-where-from tap-type))) (thing-from (pcase where-from (?f (xht--do-read-file)) (?c (xht--do-read-clipboard)) (?x (xht--do-read-last-sexp)) (?b (xht--do-read-whole-buffer)) (?t (xht--do-read-thing-at-point)) (?r (xht--do-read-selected-region)))) (boundaries (pcase where-from (?x (xht--do-boundaries-of-last-sexp)) (?t (xht--do-boundaries-of-thing-at-point)) (?r (xht--do-boundaries-of-selected-region)))) (read-from (xht--do-read-from thing-from)) (type-from (h-type read-from)) (as-ht-from (if (eq type-from :ht) read-from ;; ^ h<-it would take care of it all the same, ;; but this is more direct and explicit. (h<-it read-from))) (dim-from (h-dim as-ht-from)) ;; <------- from (where-to (or .where-to ;;USER-INPUT (xht--do-where-to where-from tap-type))) (what-to-u (and .what-to ;; check whether non-interactively–entered ;; user-input is possible for the dimension; if so ;; return it, otherwise nil. (memq .what-to (pcase dim-from (0 '(?h ?v ?l ?#)) (1 (if (h--first (not (natnump key)) as-ht-from) '(?h ?k ?j ?a ?p) '(?h ?v ?l ?n ?k ?a ?p ?j))) (2 '(?h ?o ?l ?j ?a ?p ?t ?s)) (_ '(?h ?a ?p ?j)))) .what-to)) (what-to (pcase do-what (?a nil) (?c (or what-to-u (pcase dim-from (0 (xht--do-what-to-0D)) (1 (if (h--first (not (natnump key)) as-ht-from) (xht--do-what-to-1D-e) (xht--do-what-to-1D-i))) (2 (xht--do-what-to-2D)) (_ (xht--do-what-to-nested))))))) (type-to (pcase what-to (?h :ht) (?s :ssv) (?j :json) (?o :orgtbl) (?c :csv) (?a :alist) (?v :vector) (?t :tsv) (?p :plist) (?# 'empty) (?k 'kvl) (?n 'lines) (?l (if (= 2 dim-from) :lol :list)))) (conv-fun (pcase type-to (:ht #'identity) (:lol #'h->lol) ('lines #'h->lines) (:list #'h->list) ('empty #'h->lines) (:json #'h->json*) ('kvl #'h->kvl) (:alist #'h->alist*) (:csv #'h->csv) (:plist #'h->plist*) (:tsv #'h->tsv) (:orgtbl #'h->orgtbl) (:ssv #'h->ssv) (:vector #'h->vector))) (converted (if type-to (funcall conv-fun as-ht-from) (setq type-to type-from) thing-from)) (format-to (or .format-to ;; USER-INPUT: (pcase where-to (?f (pcase type-to (:ht (xht--do-format-to-ht-write)) (_ (xht--do-format-to-non-ht-write)))) (_ (pcase type-to (:ht (xht--do-format-to-ht-all)) (_ (xht--do-format-to-non-ht-all))))))) (file-to (or .file-to ;; USER-INPUT: (pcase where-to (?f (read-file-name "Save to: ")))) ) (insert-fun #'xht--non-aggressively-insert-str-from-here) (final-str (xht--do-make-final-string converted type-to format-to where-to))) (pcase where-to (?c (kill-new final-str)) (?f (h-write! file-to final-str 'overwrite?)) (?b (xht--to-display-buffer "*xht display*") (when (> (point-max) 3) (save-excursion (insert "\n\n\f\n\n"))) (pcase type-to (:json (let ((pr (make-progress-reporter "Turning on js-mode..."))) (ignore-errors (js-mode)) (progress-reporter-done pr))) (:orgtbl (let ((pr (make-progress-reporter "Turning on orgtbl-mode..."))) (ignore-errors (orgtbl-mode)) (progress-reporter-done pr)))) (funcall insert-fun final-str)) (?i (barf-if-buffer-read-only) (pcase where-from ((or ?t ?r) (goto-char (cdr boundaries)))) (funcall insert-fun final-str 'with-spacing)) (?r (barf-if-buffer-read-only) (save-excursion (atomic-change-group (-let [(b . e) boundaries] (goto-char b) (delete-region b e) (while (looking-back "'" (- (point) 2)) (delete-char -1)) ;get rid of quotes (funcall insert-fun final-str))))))))) ;;;;;;; Helper functions ;;;;;;;; Non-interactive user input: cleanup (defun xht--do-ht-opts-cleanup (&optional ht-opts) "Clean up some possibly inconsistent user-input options. Optional HT-OPTS is a hash table with options. Silently revert to nil any option that is unavailable. Not exhaustive, since :where-to depends on dimension, which shows up only later (but cleaned up in ‘xht-do’ itself)." (if ht-opts (h-let ht-opts (h--hmap! (h-as-keyword key) value ht-opts) (or (memq .do-what '(nil ?c ?a)) (h-rem! ht-opts :do-what)) (or (memq .where-from '(nil ?f ?c ?b ?r ?t ?x)) (h-rem! ht-opts :where-from)) (or (memq .where-to '(nil ?f ?c ?b ?r ?i)) (h-rem! ht-opts :where-to)) (or (memq .what-to '(nil ?h ?l ?s ?j ?o ?c ?a ?v ?t ?p ?k ?n ?#)) (h-rem! ht-opts :what-to)) (pcase .where-to (?f (or (file-writable-p .file-to) (h-rem! ht-opts :file-to))) (_ (h-rem! ht-opts :file-to))) (or (memq .format-to (pcase .what-to (?h (-concat '(nil) (number-sequence ?1 ?9) (number-sequence ?a ?c))) (_ '(nil ?c ?C ?p ?P)))) (h-rem! ht-opts :format-to)) ht-opts) (h-new 1))) ;;;;;;;; Interactive user input (defun xht--do-input (strings chars) "Read characters given lists of STRINGS and CHARS." (read-char-choice (->> strings -non-nil (-remove-item "") (s-join "\n")) (->> chars -non-nil))) (defun xht--do-convert-or-as-is () "Read characters to choose whether to convert." (xht--do-input '("Convert it, or use thing as it is? > " "[c] Convert it" "[a] Use it as it is ") '(?c ?a))) (defun xht--do-where-from (&optional tap-type) "Read characters to choose where input comes from. TAP-TYPE is the type of the thing ate point, evaluated by ‘xht--do-type-of-thing-at-point’." (xht--do-input `("Input from where? > " ,(when tap-type (format "[t] Thing-at-point (%s)" (h-as-string tap-type))) ,(when (use-region-p) "[r] Region (selected)") "[x] Last sexp [c] Clipboard" "[f] File [b] Buffer (this) ") `(,(when tap-type ?t) ,(when (use-region-p) ?r) ?x ?c ?f ?b))) (defun xht--do-where-to (where-from &optional tap-type) "Read characters to choose where to output to. WHERE-FROM can be ?t, ?x, or ?r. Optional TAP-TYPE is the type of thing at point." (let* ((type-s (if tap-type (h-as-string tap-type) "")) (repl (pcase where-from (?t (format "thing-at-point (%s)" type-s)) (?x "last-sexp") (?r "selected region"))) (insr (pcase where-from (?t (format "after thing-at-point (%s)" type-s)) (?x "after last sexp") (?r "after selected region") (_ "at point"))) (repl-s (when repl (format "[r] Replace %s" repl))) (insr-s (format "[i] Insert %s" insr))) (xht--do-input `("Output to where? > " ,repl-s ,insr-s "[c] Clipboard [f] File [b] Buffer (new) ") `(,(when repl ?r) ?i ?c ?f ?b)))) (defun xht--do-what-to-0D () "Read characters to choose which 0D output." (xht--do-input '("Input is 0D. Convert to what? > " "[h] Empty hash table" "[l] Empty list [#] Empty string" "[v] Empty vector ") '(?h ?# ?v ?l))) (defun xht--do-what-to-1D-e () "Read characters to choose which 1D explicit output." (xht--do-input '("Input is 1D. Convert to what? > " "[h] Hash table [a] Association list" "[k] Key–value pairs [p] Property list" "[j] JSON ") '(?h ?k ?j ?a ?p))) (defun xht--do-what-to-1D-i () "Read characters to choose which 1D implicit output." (xht--do-input '("Input is (implicitly) 1D. Convert to what? > " "[h] Hash table [v] Vector [l] List" "[n] Lines of string [k] Key–value pairs" "[a] Alist [p] Plist [j] JSON ") '(?h ?v ?l ?n ?k ?a ?p ?j))) (defun xht--do-what-to-2D () "Read characters to choose which 2D output." (xht--do-input '("Input is 2D. Convert to what? > " "[h] Hash table [j] JSON" "[o] Org table [a] Alist" "[l] List of lists [p] Plist" "[t] TSV [s] SSV") ;no CSV yet '(?h ?o ?l ?j ?a ?p ?t ?s))) (defun xht--do-what-to-nested () "Read characters to choose which nested output." (xht--do-input '("Input is nested. Convert to what? > " "[h] Hash table" "[a] Association list" "[p] Property list" "[j] JSON ") '(?h ?a ?p ?j))) (defun xht--do-format-to-ht-all () "Read characters to choose hash table options." (xht--do-input '(" Lisp obj String " "| Which format? | | pp | cm | | pp | cm |" "|-----------------| |----+----| |----+----|" "| (h*…) | | 1 | 4 | | 7 | a |" "| (ht…) | | 2 | 5 | | 8 | b |" "| #s(hash-table…) | | 3 | 6 | | 9 | c |" "pp = pretty-printed, cm = compact") '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?a ?b ?c))) (defun xht--do-format-to-ht-write () "Read characters to choose hash table options." (xht--do-input '("| Which format? | | pp | cm |" "|-----------------| |----+----|" "| (h*…) | | 1 | 4 |" "| (ht…) | | 2 | 5 |" "| #s(hash-table…) | | 3 | 6 |" "pp = pretty-printed, cm = compact") '(?1 ?2 ?3 ?4 ?5 ?6))) (defun xht--do-format-to-non-ht-all () "Read characters to choose: read string? pretty-print?" (xht--do-input '("| Which format? | | pp | cm |" "|-----------------| |----+----|" "| Read it (%s) | | p | c |" "| As string (%S) | | P | C |" "pp = pretty-printed, cm = compact") '(?p ?c ?P ?C))) (defun xht--do-format-to-non-ht-write () "Read characters to choose: read string? pretty-print?" (xht--do-input '("| Which format? | | pp | cm |" "|-----------------| |----+----|" "| Read it (%s) | | p | c |" "pp = pretty-printed, cm = compact") '(?p ?c))) ;;;;;;;; String-maker (defun xht--do-make-final-string (converted type-to format-to where-to) "Make final string for insertion. Helper for ‘xht-do’. Needs CONVERTED, TYPE-TO, FORMAT-TO, WHERE-TO." (pcase type-to (:ht (--> converted (pcase format-to ((or ?1 ?7) (h-format :h*-pp-str it)) ((or ?2 ?8) (h-format :ht-pp-str it)) ((or ?3 ?9) (h-format :htbl-pp-str it)) ((or ?4 ?a) (h-format :h*-cm-str it)) ((or ?5 ?b) (h-format :ht-cm-str it)) ((or ?6 ?c) (h-format :htbl-cm-str it)) (_ (error "‘xht--do-make-final-string’: invalid format-to"))) (pcase format-to ((or ?1 ?2 ?3 ?4 ?5 ?6) it) ((or ?7 ?8 ?9 ?a ?b ?c) (format "%S" it))))) (_ (--> converted (if (and (/= ?f where-to) (memq type-to '(:alist :plist :list :lol))) `',it ;;<-- restore quote of lists it) (pcase format-to ;; compact ((or ?c ?C) (pcase type-to ((or :tsv :ssv :csv :orgtbl 'lines 'kvl) (xht--do-string-cm-ify it)) ((or :alist :plist :list :lol :vector) (let ((print-quoted t)) (format "%S" it))) (:json (xht--do-json-cm-ify it)) (_ it))) ;; pretty-printed ((or ?p ?P) (pcase type-to ((or :alist :plist :list :lol :vector) (--> it pp-to-string s-chomp)) (:json (xht--do-json-pp-ify it)) (_ it))) (_ (error "‘xht--do-make-final-string’: invalid format-to"))) ;; insert string or insert *as* string? (pcase format-to ((or ?c ?p) (format "%s" it)) ((or ?C ?P) (format "%S" it))))))) ;;;;;;;; Reading (defun xht--do-read-from (thing-from) "Try to read string THING-FROM as hash table, list/vector, or string." (if-let ((hreadh (ignore-errors (h-read-h thing-from)))) hreadh (let ((reading (--> (read thing-from) (if (listp it) (xht--dequote-list it) it)))) ;; (if (equal (h-type reading) 'symbol) ;; thing-from ;; reading) (pcase (type-of reading) ((or 'vector 'cons) reading) ('hash-table (error "‘xht--do-read-from’: not possible, tested before")) (_ thing-from))))) (defun xht--do-read-file () "Read file." (with-temp-buffer (insert-file-contents (read-file-name "From which file? ")) (buffer-string))) (defun xht--do-read-clipboard () "Read clipboard." (if kill-ring (substring-no-properties (current-kill 0 'do-not-move)) "")) (defun xht--do-read-last-sexp () "We evaluate it and then make it readable again with %S." (let ((evald (xht--form-last-sexp-eval))) (if (stringp evald) evald (format "%S" evald)))) (defun xht--do-read-whole-buffer () "Read whole buffer." (xht--buff-str-no-prop)) (defun xht--do-read-selected-region () "Read selected region." (buffer-substring-no-properties (region-beginning) (region-end))) (defun xht--do-read-thing-at-point () "Read thing at point." (autoload #'org-at-table-p "org") (cond ((org-at-table-p) (xht--do-as-string-orgtbl-at-point)) ((xht--do-at-hash-table?) (xht--do-as-string-hshtbl-at-point)) ((nth 3 (syntax-ppss)) (xht--do-as-string-string-at-point)) ((nth 1 (syntax-ppss)) (xht--do-as-string-lstvec-at-point)) (t (error "Can't read it")))) ;;;;;;;;; Thing at point (defun xht--do-type-of-thing-at-point (&optional try-seq) "Type of thing at point: org table, hash table, or nil. If TRY-SEQ is non-nil, also check if I'm inside a list or vector and return its ‘h-type’ accordingly." (autoload #'org-at-table-p "org") (cond ((org-at-table-p) :orgtbl) ((xht--do-at-hash-table?) :ht) ((nth 3 (syntax-ppss)) (h-type (xht--do-as-string-string-at-point))) (t (when try-seq (xht--do-type-of-lstvec-at-point-maybe))))) ;; This one is unused above because of ambiguities: if I run DWIM with type ;; and it says I'm inside a list, do I mean to act on that list or on the ;; last sexp? So the detecting function is below but unused for DWIM. (defun xht--do-type-of-lstvec-at-point-maybe () "Detect if thing at point is list or vector." (-if-let* ((pos (nth 1 (syntax-ppss))) (delim (char-after pos))) (pcase delim (?\( (h-type (read (xht--do-as-string-lstvec-at-point)))) (?\[ :vector) (_ nil)))) (defun xht--do-as-string-orgtbl-at-point () "String of org table at point." (autoload #'org-table-begin "org-table") (autoload #'org-table-end "org-table") (buffer-substring-no-properties (org-table-begin) (org-table-end))) (defun xht--do-as-string-string-at-point () "Outside-quotations string at point." (save-excursion (goto-char (nth 8 (syntax-ppss))) ;<-- Outside the opening " (let ((beg (point))) (forward-sexp) (read (buffer-substring-no-properties beg (point)))))) (defun xht--do-as-string-lstvec-at-point () "String of list or vector at point. Must be run from the first level of the list or vector." (save-excursion (goto-char (nth 1 (syntax-ppss))) ;<-- Outside parent ( or [ (let ((beg (point))) (forward-sexp) (buffer-substring-no-properties beg (point))))) ;; see also: ‘xht--do-as-string-hshtbl-at-point’ ;; ‘xht--do-boundaries-of-thing-at-point’ ;; ‘xht--do-boundaries-of-hash-table-at-point’ ;;;;;;;;;; Hash table (defun xht--do-as-string-hshtbl-at-point () "String of hash table at point." (-let [(beg . end) (xht--do-boundaries-of-hash-table-at-point)] (when beg (buffer-substring-no-properties beg end)))) (defun xht--do-at-hash-table? () "Is thing at point a hash table? Return t if in any of the hash table forms: - #s(hash-table…) - (ht…) - (h*…), (h-s*…), (h-t*…), or (h-st*…) unless the h forms are quoted, as in \\='(ht…), in which case return nil." (save-excursion (when (xht--do-goto-hash-table-beginning-maybe) t))) (defun xht--do-goto-hash-table-beginning-maybe () "Try to go to beginning of hash table. Return point before its starting \"(\", or nil if not found. Works in any of the hash table forms: - #s(hash-table…) - (ht…) - (h*…), (h-s*…), (h-t*…), or (h-st*…) unless the h forms are quoted, as in \\='(ht…), in which case also return nil." (let (beg) (save-excursion (when (looking-at "#") (forward-char 1)) (when (looking-at "s") (forward-char 1)) (when (looking-at "(") (forward-char 1)) (catch 'break (while (nth 1 (syntax-ppss)) (goto-char (nth 1 (syntax-ppss))) (when (xht--do-at-beginning-of-hash-table?) (setq beg (point)) (throw 'break (point)))))) (when beg (goto-char beg)))) (defun xht--do-at-beginning-of-hash-table? () "Am I just before the first ?( of some hash table representation?" (or (and (looking-back "#s" (- (point) 3)) (looking-at (rx (: "(hash-table" (+ (in " \t\n")))))) (and (not (looking-back "'" (- (point) 2))) (looking-at (rx (: "(" (| "h-t*" "h*" "ht" "h-s*" "h-st*") (+ (in " \t\n")))))))) (defun xht--do-boundaries-of-hash-table-at-point () "Cons cell with boundaries of hash table at point." (save-excursion (when (xht--do-goto-hash-table-beginning-maybe) (let ((delta (if (looking-back "#s" -3) -2 0)) (beg (point))) (forward-sexp) (cons (+ beg delta) (point)))))) ;;;;;;;;; Boundaries (defun xht--do-boundaries-of-last-sexp () "Cons cell with boundaries of last sexp." (save-excursion (backward-sexp) (let ((lsexp-beg (point))) (forward-sexp) ;; boundaries of last sexp: (cons lsexp-beg (point))))) (defun xht--do-boundaries-of-thing-at-point () "Cons cell with boundaries of thing at point." (autoload #'org-at-table-p "org") (autoload #'org-table-begin "org-table") (autoload #'org-table-end "org-table") (let* (b e) (cond ((org-at-table-p) (setq b (org-table-begin)) (setq e (org-table-end))) ((xht--do-at-hash-table?) (-setq (b . e) (xht--do-boundaries-of-hash-table-at-point))) ((nth 3 (syntax-ppss)) ;string (save-excursion (goto-char (nth 8 (syntax-ppss))) ;<-- Outside the opening " (setq b (point)) (forward-sexp) (setq e (point)))) ((nth 1 (syntax-ppss)) ;list or vector (save-excursion (goto-char (nth 1 (syntax-ppss))) ;<-- Outside parent ( or [ (forward-sexp) (setq e (point)) (backward-sexp) (while (looking-back "'" (- (point) 2)) (forward-char -1)) (setq b (point)))) (t (error "Can't set boundaries"))) (cons b e))) (defun xht--do-boundaries-of-selected-region () "Cons cell with boundaries of selected region." (cons (region-beginning) (region-end))) ;; see also: ‘xht--do-boundaries-of-hash-table-at-point’ ;;;;;;;; String formatting (cm or pp) ;;;;;;;;; JSON (defun xht--do-json-format (pp str) "Compactify or pretty-print-ify a JSON string. If PP is non-nil, pretty-print-ify STR. Otherwise compactify it." (let ((json-encoding-pretty-print pp) (json-object-type 'alist)) (json-encode (json-read-from-string str)))) (defun xht--do-json-cm-ify (str) "Compactify JSON string STR." (xht--do-json-format nil str)) (defun xht--do-json-pp-ify (str) "Pretty-print-ify JSON string STR." (xht--do-json-format t str)) ;;;;;;;;; Other strings (defun xht--do-string-cm-ify (str) "Escape tabs and newlines in string STR." ;; Adapted from Magnar Sveen's string-edit.el ;; was: se/string-at-point/escape (with-temp-buffer (insert str) (xht--do-string-escape "\\") (xht--do-string-escape-ws "n" "\n") (xht--do-string-escape-ws "r" "\r") (xht--do-string-escape-ws "t" "\t") (xht--do-string-escape "\"") (goto-char (point-min)) (insert "\"") (goto-char (point-max)) (insert "\"") (xht--buff-str-no-prop))) (defun xht--do-string-escape (quote) ;; Adapted from Magnar Sveen's string-edit.el ;; was: se/escape "Escape quotes of type QUOTE in buffer." (goto-char (point-min)) (while (search-forward quote nil t) (replace-match "") (insert "\\" quote))) (defun xht--do-string-escape-ws (signifier char) ;; Adapted from Magnar Sveen's string-edit.el ;; was: se/escape-ws "Escape whitespace in buffer given SIGNIFIER and CHAR." (goto-char (point-min)) (while (search-forward char nil t) (replace-match "") (insert "\\" signifier))) ;;;;;;;; Insertion (defun xht--insert-str-from-here (str &optional with-spacing) "Save excursion, insert STR on next line (or this line if empty). If WITH-SPACING is non-nil, add one empty line before and after." (save-excursion (let ((beg (point))) (unless (bolp) (forward-line)) (unless (eolp) (open-line 1)) (xht--if-with-spacing str with-spacing) (indent-region beg (point))))) (defun xht--non-aggressively-insert-str-from-here (str &optional with-spacing) "Insert STR from here with ‘aggressive-indent-mode’ temporarily off. If WITH-SPACING is non-nil, add newlines before and after." (save-excursion (unless (bolp) (forward-line)) (unless (eolp) (open-line 1)) (if (bound-and-true-p aggressive-indent-mode) (prog2 (aggressive-indent-mode -1) (xht--if-with-spacing str with-spacing) (aggressive-indent-mode)) (xht--if-with-spacing str with-spacing)))) (defun xht--if-with-spacing (str &optional with-spacing) "Insert string STR, maybe with spacing. If WITH-SPACING is non-nil, add newlines before and after." (if with-spacing (insert "\n" str "\n") (insert str))) ;;;;;;;; Display (defun xht--to-display-buffer (name) "Go to beginning of elisp buffer named NAME (create as needed)." (let ((buffer (get-buffer-create name))) (pop-to-buffer buffer) (emacs-lisp-mode) (goto-char (point-min)))) ;;;;;;;; Last sexp (defun xht--form-last-sexp-eval () "Eval last sexp (the one immediately before point)." (let* ((lsf (xht--form-last-sexp)) (type (type-of lsf))) (pcase type ;; only symbols and unquoted lists will be eval'd; not ht or str. ((or 'symbol 'cons) (eval lsf)) (_ lsf)))) (defun xht--form-last-sexp () "Return last sexp (the one immediately before point)." (read (xht--form-last-sexp-str))) (defun xht--form-last-sexp-str () "Return last sexp (the one immediately before point) as string. Note that ‘backward-sexp’ includes quotes when the sexp was a list — but not the #s of hash tables, which we do recover here." (save-excursion (let ((end (point))) (backward-sexp) (when (looking-back "#s" (- (point) 4)) (forward-char -2)) (s-trim (buffer-substring-no-properties (point) end))))) ;;;;;; Specialized quicker access (xht--describe "These commands pass pre-defined choices to the main dispatcher so you can more quickly do the things you do more often without having to answer all of the main dispatcher's questions.") ;;;###autoload (defun xht-do-convert-dwim () "Convert region, thing at point or last-sexp — try to guess it. - If you have a region selected, use that. - Otherwise, if you're inside a string, a vector, some sort of list, or #s(hash-table…), use that. - Otherwise, use last sexp Then interactively select where to, what to, and format. See also ‘xht-do’ for other specialized commands listed there." (interactive) (xht-do (h* :do-what ?c :where-from (xht--do-guess-where-from-option)))) ;;;###autoload (defun xht-do-h*-ify-dwim-to-clipboard () "First h*-ify region, thing at point or last-sexp; send it to clipboard. Like ‘xht-do-h*-ify-dwim-to-point’, but to clipboard instead of at point." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere (xht--do-guess-where-from-option) ?c)) ;;;###autoload (defun xht-do-h*-ify-dwim-to-file () "First h*-ify region, thing at point or last-sexp; write to file. Like ‘xht-do-h*-ify-dwim-to-point’, but to new file instead of at point." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere (xht--do-guess-where-from-option) ?f)) ;;;###autoload (defun xht-do-h*-ify-dwim-to-replace () "First h*-ify region, thing at point or last-sexp; replace source. Like ‘xht-do-h*-ify-dwim-to-point’, but replace source instead of inserting at point." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere (xht--do-guess-where-from-option) ?r)) ;;;###autoload (defun xht-do-h*-ify-dwim-to-new-buffer () "First h*-ify region, thing at point or last-sexp; insert at new buffer. Like ‘xht-do-h*-ify-dwim-to-point’, but to new buffer instead of at point." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere (xht--do-guess-where-from-option) ?b)) ;;;###autoload (defun xht-do-h*-ify-dwim-to-point () "First h*-ify region, thing at point or last-sexp; insert at point. Equivalent to calling ‘xht-do’ and then answering with ?c N ?i ?h 1 — where N represents the following do-what-I-mean autodetection: - If you have a region selected, ?r - Otherwise, if you're inside a string, a vector, some sort of list, or #s(hash-table…), ?t - Otherwise, ?x See also ‘xht-do’ for other specialized commands listed there." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere (xht--do-guess-where-from-option) ?i)) ;;;###autoload (defun xht-do-h*-ify-selected-region-to-point () "First h*-ify selected region; then insert results at point. Equivalent to calling ‘xht-do’ and then answering with ?c ?r ?i ?h 1. See also ‘xht-do’ for other specialized commands listed there." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere ?r ?i)) ;;;###autoload (defun xht-do-h*-ify-thing-at-point-to-point () "First h*-ify thing at point; then insert results at point. Equivalent to calling ‘xht-do’ and then answering with ?c ?t ?i ?h 1. See also ‘xht-do’ for other specialized commands listed there." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere ?t ?i)) ;;;###autoload (defun xht-do-h*-ify-last-sexp-to-point () "First h*-ify last-sexp; then insert results at point. Equivalent to calling ‘xht-do’ and then answering with ?c ?x ?i ?h 1. See also ‘xht-do’ for other specialized commands listed there." (interactive) (xht--do-h*-ify-from-somewhere-to-somewhere ?x ?i)) ;;;;;;; Helper functions (defun xht--do-h*-ify-from-somewhere-to-somewhere (from-char to-char) "First h*-ify from somewhere; then insert results at point. Helper for a few dispatcher commands. FROM-CHAR is the character that identifies where from. TO-CHAR is the character that identifies where to." (xht-do (h* :do-what ?c :where-from from-char :where-to to-char :what-to ?h :format-to ?1 :file-to nil))) (defun xht--do-guess-where-from-option () "Guess char option of where the thing you want to convert is at. - If you have a region selected, ?r - Otherwise, if you're inside a string, a vector, some sort of list, or #s(hash-table…), ?t - Otherwise, ?x Helper for a few dispatcher commands." (cond ((use-region-p) ?r) ((xht--do-type-of-thing-at-point) ?t) (t ?x))) ;;;;; Minor modes ;;;;;; xht-do minor mode (xht--describe "A minor mode for turning on keys for xht-do actions.") (defvar xht-do-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-x x d") 'xht-do) (define-key map (kbd "C-x x c") 'xht-do-convert-dwim) (define-key map (kbd "C-x x f") 'xht-do-h*-ify-dwim-to-file) ;; x because handier than i and should be frequent: (define-key map (kbd "C-x x x") 'xht-do-h*-ify-dwim-to-point) (define-key map (kbd "C-x x r") 'xht-do-h*-ify-dwim-to-replace) (define-key map (kbd "C-x x C") 'xht-do-h*-ify-dwim-to-clipboard) (define-key map (kbd "C-x x b") 'xht-do-h*-ify-dwim-to-new-buffer) map)) (defcustom xht-do-mode-lighter nil "Mode line lighter for ‘xht-do-mode’. Either a string to display in the mode line when ‘xht-do-mode’ is on, or nil to display nothing (the default)." :group 'xht :type '(choice (string :tag "Lighter" :value " xdo") (const :tag "Nothing" nil))) ;;;###autoload (define-minor-mode xht-do-mode "Enable keys for ‘xht-do’ actions. See also ‘xht-do-mode-lighter’ and ‘global-xht-do-mode’." :init-value nil :lighter xht-do-mode-lighter :keymap xht-do-mode-map :group 'xht) (defun xht--turn-on-do-mode () "Enable ‘xht-do-mode’ globally. Not restricted to Emacs Lisp, since you can use it for converting org tables anywhere ‘orgtbl-mode’ is on; TSVs in ‘csv-mode’; JSONs when in ‘js-mode’; etc." (or (derived-mode-p #'dired-mode) (derived-mode-p #'special-mode) (xht-do-mode))) ;;;###autoload (define-globalized-minor-mode global-xht-do-mode xht-do-mode xht--turn-on-do-mode :group 'xht) ;;;;;; Font lock minor mode (xht--describe "A minor mode for fontifying XHT's functions and equality operators.") (defcustom xht-fontlock-add-anaphoric-variables t "If non-nil, fontify the anaphoric variables key, field, and value." :group 'xht :type 'boolean) (defcustom xht-fontlock-add-equality-operators t "If non-nil, fontify equality-operators for ‘exemplify-ert’. These show up in the many examples found in xht's README.org, as well as in xht's ERT tests in dev/examples.el." :group 'xht :type 'boolean) (defcustom xht-fontlock-add-exemplify-ert-macros t "If non-nil, fontify the macro ‘exemplify-ert’. It shows up in xht's ERT tests in dev/examples.el." :group 'xht :type 'boolean) (defcustom xht-fontlock-add-ht-callables t "If non-nil, fontify public functions and macros from ‘ht’." :group 'xht :type 'boolean) (defcustom xht-fontlock-add-xht-callables t "If non-nil, fontify public functions and macros from ‘xht’." :group 'xht :type 'boolean) (defcustom xht-fontlock-add-xht-helpers nil "If non-nil, fontify private functions and macros from ‘xht’." :group 'xht :type 'boolean) ;; TODO: automate retrieval of these functions/macros/aliases names (defvar xht--callables-ht '(ht ht-clear ht-contains-p ht-delete-if ht-each ht-empty-p ht-equal-p ht-from-alist ht-from-plist ht-p ht-remove ht-set ht-to-alist ht-to-plist ht-update ht-update-with! ht? ht-clear! ht-contains? ht-create ht-empty? ht-get ht-get* ht-remove! ht-set! ht-size ht-aeach ht-amap ht->alist ht->plist ht-equal? ht-find ht-items ht-keys ht-map ht-merge ht-reject ht-reject! ht-select ht-select-keys ht-update! ht-values ht<-alist ht<-plist) "List of ‘ht’ public callables.") (defvar xht--callables-xht '(h-new h-empty-clone h-clr h-clr! h* h-s* h-t* h-st* h-reduce-r h--zip-vectors-with h-zip-vectors-with h-zip-vectors h--zip-lists-with h-zip-lists-with h-zip-lists h-let h-let-it h-htbl-form h-htbl-cm-str h-htbl-pp-str h-ht-form h-ht-cm-str h-ht-pp-str h-h*-form h-h*-cm-str h-h*-pp-str h-lambdify h-format h-vmap h--vmap h--vunzip-with h-vunzip-with h-vunzip h-vitems h-vkeys h-vvalues h-vrandom h-lmap h--lmap h-lkeep h--lkeep h--lunzip-with h-lunzip-with h-lunzip h-litems h-lkeys h-lvalues h-lrandom h-hmap h--hmap h-hmap! h--hmap! h-hmap* h--hmap* h-hmap*! h--hmap*! h-each h--each h-pop h-pop! h-pop-random h-pop-random! h-first h--first h-first! h--first! h-put! h-put h-put*! h-put* h-put-add! h-put-add h-put-add*! h-put-add* h-rem! h-rem h-rem*! h-rem* h-sel-keys h-sel-keys! h-sel h--sel h-sel! h--sel! h-sel* h--sel* h-sel*! h--sel*! h-rej-keys h-rej-keys! h-rej h--rej h-rej! h--rej! h-rej* h--rej* h-rej*! h--rej*! h-reverse h-reverse! h-reverse* h-reverse*! h-put-num-with* h-put-num-with*! h-put-inc* h-put-inc*! h-put-dec* h-put-dec*! h-has-key? h-has-key*? h-has-value? h-has-value*? h-has-pair? h-has-pair*? h-has-keys? h-has-values? h-has-pairs? h-mix! h-mix h-mix*! h-mix* h-dif! h-dif h-dif*! h-dif* h-cmn! h-cmn h-cmn*! h-cmn* h-kvl= h-pr== h-pr= h== h= h_= h~= h-props= h-alist= h-plist= h-json= h-lol= h-orgtbl= h-tsv= h-csv= h-ssv= h-type h-type= h-it= h-it~ h-clone* h<-ht h-2d<-1d h-1d<-2d h<-vector h->vector h<-list h->list h<-lines h->lines h<-kvl h->kvl h<-cons-pair h->cons-pair h<-alist h<-alist* h->alist h->alist* h<-plist h<-plist* h->plist h->plist* h<-json* h->json* h<-lol h->lol h<-orgtbl h->orgtbl h<-tsv h->tsv h<-ssv h->ssv h->csv h<-it h-kvl? h-alist? h-plist? h-lol? h-orgtbl? h-json? h-tsv? h-csv? h-ssv? h-prop h-props h-dim h-1d? h-2d? h-nested? h-it-dim h-it-empty? h-it-1d? h-it-2d? h-it-nested? h-write! h-write-ht-like! h-write-htbl-cm! h-write-htbl-pp! h-write-h*-cm! h-write-ht-cm! h-write-h*-pp! h-write-ht-pp! h-read h-read-ht-like h-read-htbl h-read-s h-read-tsv h-read-csv h-read-ssv h-read-json h-read-orgtbl h-read-lol h-read-alist h-read-plist h-read-list h-read-vector h-read-kvl h-read-lines h-read-cons-pair h-as-symbol h-as-string h-as-keyword h-as-number h-2d-new h-2d-header h-2d-idcol h-2d-id h-2d-row h-2d-col h-2d-hmap h--2d-hmap h-2d-hmap! h--2d-hmap! h-2d-sel-cols h-2d-sel-cols! h-2d-rej-cols h-2d-rej-cols! h-2d-transpose h-2d-transpose! h-as-set<-vector h-as-set<-list h-as-set<-lines h-as-set<-words h-count<-vector h-count<-list h-count<-lines h-count<-words h-count-put h-count-put! h? h-size h-copy h-get h-get* h-empty? h-has-key-p h-has-key*-p h-has-keys-p h-has-value-p h-has-value*-p h-has-values-p h-has-pair-p h-has-pair*-p h-has-pairs-p h-1d-p h-2d-p h-nested-p h-it-1d-p h-it-2d-p h-it-nested-p h-csv-p h-ssv-p h-tsv-p h-kvl-p h-lol-p h-json-p h-alist-p h-plist-p h-orgtbl-p h-p h-empty-p h-random h-unzip-with h--unzip-with h-unzip h-items h-keys h-values h-write-h*! h-write-ht! h-write-htbl! h-read-h h-2d-keys h-1d->2d h-2d->1d h<-csv ;; Commands xht-see-readme xht-see-function-in-readme xht-see-news xht-do-mode global-xht-do-mode xht-fontify-mode global-xht-fontify-mode xht-do xht-do-convert-dwim xht-do-h*-ify-dwim-to-clipboard xht-do-h*-ify-dwim-to-file xht-do-h*-ify-dwim-to-replace xht-do-h*-ify-dwim-to-new-buffer xht-do-h*-ify-dwim-to-point xht-do-h*-ify-selected-region-to-point xht-do-h*-ify-thing-at-point-to-point xht-do-h*-ify-last-sexp-to-point) "List of ‘xht’ public callables.") (defvar xht--helpers-xht '(xht--init-size xht--init-size-exact xht--init-size-fixed xht--let-list-to-sexp xht--let-access-sexp xht--let-remove-dot xht--let-deep-dot-search xht--ht-like-format-type xht--put-add xht--reverse*! xht--number-with xht--number-with-1+ xht--number-with-1- xht--has-x*? xht--mix! xht--mix*! xht--dif! xht--dif*! xht--cmn! xht--cmn*! xht--equal? xht--props= xht---pr== xht---pr= xht--== xht--= xht--_= xht--~= xht--type= xht--it= xht--it~ xht--equal-many xht--thing-to-nat xht<--alist* xht-->alist* xht<--plist* xht-->plist* xht--list-to-orgtbl-row xht--list-to-tsv-row xht--2d-col-width xht--list-to-ssv-row xht<--*sv xht<--tsv xht<--csv xht<--ssv xht--*sv? xht--itify xht--ht-and-empty-or-2d+-or-error xht--count-put-ht! xht--count-put-vector! xht--count-put-list! xht--count-put-elem! xht--string->symbol xht--keyword->symbol xht--keyword->string xht--symbol->string xht--string->keyword xht--symbol->keyword xht--string->number xht--symbol->number xht--keyword->number xht--number->string xht--number->keyword xht--number->symbol xht--to-type xht--htstr-to-htbl xht--str-to-htbl xht--htbl-or-error xht--str-to-alist xht--canonicalize-alist xht--alist-or-error xht--str-to-plist xht--canonicalize-plist xht--plist-or-error xht--str-to-lol xht--canonicalize-lol xht--lol-or-error xht--dequote-list xht--str-to-list xht--list-or-error xht--cons-pair-or-error xht--str-to-vector xht--vector-or-error xht--str-nw? xht--quote xht--non-aggressively-insert xht--file-str-no-prop xht--buff-str-no-prop xht--remove-nested-nils xht--misc-examples xht--describe ;; Helpers for commands xht--display-org-subtree xht--goto-org-heading xht--goto-org-heading-pos xht--see-library-readme xht--locate-library-readme xht--libname->libfile xht--display-org-subtree xht--turn-on-do-mode xht--do-ht-opts-cleanup xht--do-input xht--do-convert-or-as-is xht--do-where-from xht--do-where-to xht--do-what-to-0D xht--do-what-to-1D-e xht--do-what-to-1D-i xht--do-what-to-2D xht--do-what-to-nested xht--do-format-to-ht-all xht--do-format-to-ht-write xht--do-format-to-non-ht-all xht--do-format-to-non-ht-write xht--do-make-final-string xht--do-read-from xht--do-read-file xht--do-read-clipboard xht--do-read-last-sexp xht--do-read-whole-buffer xht--do-read-selected-region xht--do-read-thing-at-point xht--do-type-of-thing-at-point xht--do-type-of-lstvec-at-point-maybe xht--do-as-string-orgtbl-at-point xht--do-as-string-string-at-point xht--do-as-string-lstvec-at-point xht--do-as-string-hshtbl-at-point xht--do-at-hash-table? xht--do-goto-hash-table-beginning-maybe xht--do-at-beginning-of-hash-table? xht--do-boundaries-of-hash-table-at-point xht--do-boundaries-of-last-sexp xht--do-boundaries-of-thing-at-point xht--do-boundaries-of-selected-region xht--do-json-format xht--do-json-cm-ify xht--do-json-pp-ify xht--do-string-cm-ify xht--do-string-escape xht--do-string-escape-ws xht--insert-str-from-here xht--non-aggressively-insert-str-from-here xht--if-with-spacing xht--to-display-buffer xht--form-last-sexp-eval xht--form-last-sexp xht--form-last-sexp-str xht--do-h*-ify-from-somewhere-to-somewhere xht--do-guess-where-from-option xht--fontlock-make-keywords xht--fontlock-rx-anaphoric-variables xht--fontlock-rx-exemplify-ert-macros xht--fontlock-make-rx xht--turn-on-fontify-mode) "List of ‘xht’ helpers.") (defvaralias 'xht--equality-operators 'xht--ops) (defvar xht--ops '(!!> => ~> Hp==> Hp=> H==> H=> H_=> H~=> K=> L=> T=> C=> S=> A=> P=> J=> O=>) "List of equality operators used by h-defs — as symbols.") (defun xht--fontlock-make-keywords () "Make keywords for fontlock." (-non-nil (list (when xht-fontlock-add-anaphoric-variables (xht--fontlock-rx-anaphoric-variables)) (when xht-fontlock-add-exemplify-ert-macros (xht--fontlock-rx-exemplify-ert-macros)) (when xht-fontlock-add-equality-operators (xht--fontlock-make-rx xht--equality-operators)) (when xht-fontlock-add-ht-callables (xht--fontlock-make-rx xht--callables-ht)) (when xht-fontlock-add-xht-callables (xht--fontlock-make-rx xht--callables-xht)) (when xht-fontlock-add-xht-helpers (xht--fontlock-make-rx xht--helpers-xht))))) (defun xht--fontlock-rx-anaphoric-variables () ;; TODO: Do not fontify these ^ globally; ;; detect and limit to their local anaphoric scope. "The rx for anaphoric variables." `(,(rx symbol-start (| "key" "field" "value") symbol-end) 0 font-lock-variable-name-face)) (defun xht--fontlock-rx-exemplify-ert-macros () ;; Macros in dev/examples.el. Based on ‘lisp-mode-symbol-regexp’. "The rx for ‘exemplify-ert’ macros." `(,(rx ?\( (group (| "exemplify-ert")) symbol-end (+ (in "\t ")) (group (* (| (syntax word) (syntax symbol) (: ?\\ nonl))))) (1 font-lock-keyword-face) (2 font-lock-function-name-face))) (defun xht--fontlock-make-rx (symbols-list) "Make rx using SYMBOLS-LIST." (let* ((strings (-map #'symbol-name (-uniq symbols-list))) (rx-1 `(| ,@strings)) (rx `(rx symbol-start ,rx-1 symbol-end))) (eval rx))) (defcustom xht-fontify-mode-lighter nil "Mode line lighter for ‘xht-fontify-mode’. Either a string to display in the mode line when ‘xht-fontify-mode’ is on, or nil to display nothing (the default)." :group 'xht :type '(choice (string :tag "Lighter" :value " xhtf") (const :tag "Nothing" nil))) ;;;###autoload (define-minor-mode xht-fontify-mode "Toggle fontification of XHT/HT special variables. This is a buffer-local minor mode intended for Emacs Lisp buffers. Enabling it causes syntax highlighting of: - xht's callables (functions, macros, aliases) - ht's callables (functions, macros, aliases) - anaphoric variables such as ‘key’ and ‘value’. - exemplify-ert's macros and equality operators (for testing and examples — see ./dev folder) They are all turned on when ‘global-xht-fontify-mode’ is on. If you wish, you can selectively disable any of them, through the customize interface: \\[customize-group] xht RET. See there the variables starting with \"xht-fontlock-add-\". See also ‘xht-fontify-mode-lighter’ and ‘global-xht-fontify-mode’." :group 'xht :lighter xht-fontify-mode-lighter (if xht-fontify-mode (font-lock-add-keywords nil (xht--fontlock-make-keywords) t) (font-lock-remove-keywords nil (xht--fontlock-make-keywords))) (cond ((fboundp 'font-lock-flush) ;; Added in Emacs 25. (font-lock-flush)) ;; ‘font-lock-fontify-buffer’ unconditionally enables ;; ‘font-lock-mode’ and is marked ‘interactive-only’ in later ;; Emacs versions which have ‘font-lock-flush’, so we guard ;; and pacify as needed, respectively. (font-lock-mode (with-no-warnings (font-lock-fontify-buffer))))) (defun xht--turn-on-fontify-mode () "Enable ‘xht-fontify-mode’ if in an Emacs Lisp buffer." (when (derived-mode-p #'emacs-lisp-mode) (xht-fontify-mode))) ;;;###autoload (define-globalized-minor-mode global-xht-fontify-mode xht-fontify-mode xht--turn-on-fontify-mode :group 'xht) (defcustom xht-enable-fontlock nil "If non-nil, fontify XHT macro calls and special variables." :group 'xht :set (lambda (sym val) (set-default sym val) (global-xht-fontify-mode (if val 1 0))) :type 'boolean) ;;;;; See README (xht--describe "Commands to open xht's README.org. Optionally, find things in it.") ;;;###autoload (defun xht-see-readme (&optional heading narrow) "Open xht's README.org file. Search for the file in xht.el's directory. If found, open it read-only. If optional argument HEADING is passed, try to navigate to the heading after opening it. HEADING should be a string. If prefixed by \"cid:\", search it by CUSTOM_ID. If optional argument NARROW is non-nil, narrow to that heading. This argument has no effect if HEADING is nil or not found." (interactive) (let ((readme xht--readme-org)) (if (file-exists-p readme) (let ((pr (make-progress-reporter (format "Opening %s ... " (abbreviate-file-name readme))))) (find-file-read-only readme) (when heading (xht--goto-org-heading heading narrow)) (progress-reporter-done pr)) (message "Couldn't find %s's README.org" xht--name)))) ;;;###autoload (defun xht-see-function-in-readme (&optional function) "See FUNCTION in xht's README.org. When called interactively, select function from minibuffer." (interactive) (let* ((fun (h-as-string (if function (if (memq (h-as-symbol function) xht--callables-xht) function (error "%s isn't a public xht function" function)) (completing-read "Choose a function: " xht--callables-xht)))) (pr (make-progress-reporter (format "Looking for #'%s ... " fun)))) ;; ‘regex-quote’ needed to escape asterisks in function names (xht-see-readme (format "cid:%s" (regexp-quote fun))) (progress-reporter-done pr))) ;;;###autoload (defun xht-see-news () "See the News in xht's README.org file." (interactive) (xht-see-readme "News" 'narrow) (xht--display-org-subtree)) ;;;;;; Helper functions (defun xht--display-org-subtree () "Selectively display org subtree." (let ((cmds '(outline-hide-subtree outline-show-children outline-next-heading outline-show-branches))) (and (fboundp (nth 0 cmds)) (fboundp (nth 1 cmds)) (fboundp (nth 2 cmds)) (fboundp (nth 3 cmds)) (mapc #'funcall cmds)))) (defun xht--goto-org-heading (heading &optional narrow) "Navigate to org HEADING and optionally NARROW to it. If HEADING is prefixed by \"cid:\", search it by CUSTOM_ID." (when-let ((pos (xht--find-org-heading-pos heading))) (widen) (goto-char pos) (when (fboundp 'outline-show-subtree) (outline-show-subtree)) (if (and narrow (fboundp 'org-narrow-to-subtree)) (org-narrow-to-subtree) (recenter-top-bottom 0)) (when (fboundp 'org-flag-drawer) (save-excursion (forward-line 1) (org-flag-drawer t))))) (defun xht--find-org-heading-pos (heading) "Find position of org HEADING in current org file. If HEADING is prefixed by \"cid:\", search it by CUSTOM_ID. If found, return position. Otherwise nil." (unless (stringp heading) (error "Heading must be a string")) (let* ((cid? (s-starts-with? "cid:" heading)) (hdn (s-chop-prefix "cid:" heading)) (frx (if cid? "^:CUSTOM_ID: +%s *$" "^[*]+ %s")) (hrx (format frx hdn))) (save-match-data (save-excursion (save-restriction (widen) (goto-char (point-max)) (re-search-backward hrx nil t 1) (if cid? (re-search-backward "^[*]+ " nil t 1) (point))))))) ;;;;;;; old helpers ;; These helpers are no longer needed, but are kept here anyway: (defun xht--see-library-readme (libname) "Open library LIBNAME's README file. Load LIBNAME, if not yet loaded, then search for README.org in the directory from which it has been loaded. If not found, look for README.md, README.markdown, README.rst, README.txt, and README. The first one found, if any, is opened read-only." (--> (xht--locate-library-readme libname) (if (not it) (message "Couldn't find a README file for %s" libname) (let ((pr (make-progress-reporter (format "Opening %s ... " (abbreviate-file-name it))))) (find-file-read-only it) (progress-reporter-done pr))))) (defun xht--locate-library-readme (libname) "Locate library LIBNAME's README file. Load LIBNAME, if not yet loaded, then search for README.org in the directory from which it has been loaded. If not found, look for README.md, README.markdown, README.rst, README.txt, and README. Return the filename if found, nil otherwise." (--> (xht--libname->libfile libname) (expand-file-name "README" (file-name-directory it)) (let* ((exts '(".org" ".md" ".markdown" ".rst" ".txt" "")) (all (-map (lambda (ext) (format "%s%s" it ext)) exts))) (car (-filter #'file-exists-p all))))) (defun xht--libname->libfile (libname) "Given LIBNAME, load library and return filename it loaded from." (require (if (symbolp libname) libname (intern libname))) (--> load-history (-map #'car it) (car (-filter (lambda (lib) (when (string-match (format "/%s.el[cn]*$" libname) lib) lib)) it)))) ;;;; Aliases ;;;;; For those who prefer to use a single namespace ;;;;;; To ht functions ;; These are aliases to current functions of the ht package that we haven't so ;; far proposed to modify or extend. (defalias 'h-get 'ht-get) (defalias 'h-get* 'ht-get*) (defalias 'h-empty? 'ht-empty?) ;;;;;; To native functions (defalias 'h? 'hash-table-p) ;; same as ‘ht?’ (defalias 'h-size 'hash-table-count) ;; like ‘ht-size’ (defalias 'h-copy 'copy-hash-table) ;; like ‘ht-copy’ ;;;;; Preds: users of 'p' for y/n questions will be pleased — amiritep ;;;;;; To h functions (defalias 'h-has-key-p 'h-has-key?) (defalias 'h-has-key*-p 'h-has-key*?) (defalias 'h-has-keys-p 'h-has-keys?) (defalias 'h-has-value-p 'h-has-value?) (defalias 'h-has-value*-p 'h-has-value*?) (defalias 'h-has-values-p 'h-has-values?) (defalias 'h-has-pair-p 'h-has-pair?) (defalias 'h-has-pair*-p 'h-has-pair*?) (defalias 'h-has-pairs-p 'h-has-pairs?) (defalias 'h-1d-p 'h-1d?) (defalias 'h-2d-p 'h-2d?) (defalias 'h-nested-p 'h-nested?) (defalias 'h-it-1d-p 'h-it-1d?) (defalias 'h-it-2d-p 'h-it-2d?) (defalias 'h-it-nested-p 'h-it-nested?) (defalias 'h-csv-p 'h-csv?) (defalias 'h-ssv-p 'h-ssv?) (defalias 'h-tsv-p 'h-tsv?) (defalias 'h-kvl-p 'h-kvl?) (defalias 'h-lol-p 'h-lol?) (defalias 'h-json-p 'h-json?) (defalias 'h-alist-p 'h-alist?) (defalias 'h-plist-p 'h-plist?) (defalias 'h-orgtbl-p 'h-orgtbl?) ;;;;;; To ht functions (defalias 'h-empty-p 'ht-empty-p) ;;;;;; To native functions ;; Here the Emacs standard recommendation of 'hp' would get us out of h's ;; namespace. It could also displease certain printer manufacturers... (defalias 'h-p 'hash-table-p) ;;;;; DWIM ;;;;;; because lists are what you usually want (defalias 'h-random 'h-lrandom) (defalias 'h-unzip-with 'h-lunzip-with) (defalias 'h--unzip-with 'h--lunzip-with) (defalias 'h-unzip 'h-lunzip) (defalias 'h-items 'h-litems) (defalias 'h-keys 'h-lkeys) (defalias 'h-values 'h-lvalues) ;; Note however that ‘h-lmap’ and ‘h-lkeep’ are NOT aliased, and that ;; functions exist that map to hash table: ‘h-hmap’, ‘h-hmap!’, ‘h-hmap*’, ;; ‘h-hmap*!’. ;;;;;; because you probably want it in pretty-printed format (defalias 'h-write-h*! 'h-write-h*-pp!) (defalias 'h-write-ht! 'h-write-ht-pp!) (defalias 'h-write-htbl! 'h-write-htbl-pp!) ;;;;; Just shorter (defalias 'h-read-h 'h-read-ht-like) ;; Note: no need for a corresponding 'h-write-h!' — just use one of the ;; specific ones for each format: ‘h-write-ht-cm!’, ‘h-write-h*!’, ;; ‘h-write-htbl!’, etc. ;;;;; Consistency (defalias 'h-2d-keys 'h-lkeys) ;;;;; Directional: make any order work (defalias 'h-1d->2d 'h-2d<-1d) (defalias 'h-2d->1d 'h-1d<-2d) ;;;;; Provisional: until other functions have been implemented ;; It'd be good to have a direct conversion not dependent on org. ;; Aliasing to the org-based version until then. (defalias 'h<-csv 'xht<--csv "Provisional aliasing to ‘xht<--csv’ until implementation.") ;;;; Wrapping up (provide 'xht) ;; Local Variables: ;; coding: utf-8 ;; indent-tabs-mode: nil ;; sentence-end-double-space: nil ;; outline-regexp: ";;;;* " ;; End: ;;; xht.el ends here