kv.el (13866B)
1 ;;; kv.el --- key/value data structure functions 2 3 ;; Copyright (C) 2012 Nic Ferrier 4 5 ;; Author: Nic Ferrier <nferrier@ferrier.me.uk> 6 ;; Keywords: lisp 7 ;; Package-Version: 20140108.1534 8 ;; Package-Commit: 721148475bce38a70e0b678ba8aa923652e8900e 9 ;; Version: 0.0.19 10 ;; Maintainer: Nic Ferrier <nferrier@ferrier.me.uk> 11 ;; Created: 7th September 2012 12 13 ;; This program is free software; you can redistribute it and/or modify 14 ;; it under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation, either version 3 of the License, or 16 ;; (at your option) any later version. 17 18 ;; This program is distributed in the hope that it will be useful, 19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 ;; GNU General Public License for more details. 22 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 25 26 ;;; Commentary: 27 28 ;; Some routines for working with key/value data structures like 29 ;; hash-tables and alists and plists. 30 31 ;; This also takes over the dotassoc stuff and provides it separately. 32 33 ;;; Code: 34 35 (eval-when-compile (require 'cl)) 36 37 38 (defun kvalist->hash (alist &rest hash-table-args) 39 "Convert ALIST to a HASH. 40 41 HASH-TABLE-ARGS are passed to the hash-table creation." 42 (let ((table (apply 'make-hash-table hash-table-args))) 43 (mapc 44 (lambda (pair) 45 (puthash (car pair) (cdr pair) table)) 46 alist) 47 table)) 48 49 (defun kvhash->alist (hash &optional func) 50 "Convert HASH to an ALIST. 51 52 Optionally filter through FUNC, only non-nil values returned from 53 FUNC are stored as the resulting value against the converted 54 key." 55 (when hash 56 (let (store) 57 (maphash 58 (lambda (key value) 59 (when key 60 (if (and (functionp func)) 61 (let ((res (funcall func key value))) 62 (when res 63 (setq store (acons key res store)))) 64 ;; else no filtering, just return 65 (setq store (acons key value store))))) 66 hash) 67 store))) 68 69 (defun kvfa (key alist receive) 70 "Call RECEIVE with whatever comes out of ALIST for KEY. 71 72 RECEIVE can do whatever destructuring you want, the first 73 argument is always the car of the alist pair." 74 (apply receive (let ((a (assoc key alist))) 75 (append (list (car a)) 76 (if (listp (cdr a))(cdr a)(list (cdr a))))))) 77 78 (defun kva (key alist) 79 "Retrieve the value assigned to KEY in ALIST. 80 81 This uses `assoc' as the lookup mechanism." 82 (cdr (assoc key alist))) 83 84 (defun kvaq (key alist) 85 "Retrieve the value assigned to KEY in ALIST. 86 87 This uses `assq' as the lookup mechanism." 88 (cdr (assq key alist))) 89 90 (defun kvaqc (key alist) 91 "Retrieve the value assigned to KEY in ALIST. 92 93 This uses first the `assq' and then `assoc' as the lookup 94 mechanism." 95 (cdr (or (assq key alist) 96 (assoc key alist)))) 97 98 (defun kvassoc= (key value alist) 99 "Is the value assocd to KEY in ALIST equal to VALUE? 100 101 Returns the value looked up by KEY that passes, so normally: 102 103 KEY . VALUE 104 " 105 (let ((v (assoc key alist))) 106 (and v (equal (cdr v) value) v))) 107 108 (defun kvassoqc (key alist) 109 "String or symbol assoc." 110 (let ((v (or 111 (assq (if (symbolp key) key (intern key)) alist) 112 (or (assoc key alist) 113 ;; not sure about this behaviour... see test 114 (assoc (symbol-name key) alist))))) v)) 115 116 (defun kvassoq= (key value alist) 117 "Test the VALUE with the value bound to KEY in ALIST. 118 119 The lookup mechanism is to ensure the key is a symbol and then 120 use assq. Hence the name of the function being a mix of assoc 121 and assq. 122 123 Returns the value looked up by KEY that passes, so normally: 124 125 KEY . VALUE 126 " 127 (let ((v (kvassoqc key alist))) 128 (and v (equal (cdr v) value) v))) 129 130 (defun kvmatch (key regex alist) 131 "Test the value with KEY in ALIST matches REGEX." 132 (let ((v (kvassoqc key alist))) 133 (and v (string-match regex (cdr v)) v))) 134 135 (defun* kvquery->func (query &key 136 (equal-func 'kvassoc=) 137 (match-func 'kvmatch)) 138 "Turn a simple QUERY expression into a filter function. 139 140 EQUAL-FUNC is the function that implements the equality 141 predicate. 142 143 MATCH-FUNC is the function that implements the match predicate. 144 145 The query language is: 146 147 | a b - true if a or b is true 148 & a b - true only if a and b is true 149 = a b - true if a equals b as per the EQUAL-FUNC 150 ~ a b - true if a matches b as per the MATCH-FUNC 151 152 So, for example: 153 154 (|(= a b)(= c d)) 155 156 Means: if `a' equals `b', or if `c' equals `d' then the 157 expression is true." 158 (flet ((query-parse (query) 159 (let ((part (car query)) 160 (rest (cdr query))) 161 (cond 162 ((eq part '|) 163 (cons 'or 164 (loop for i in rest 165 collect (query-parse i)))) 166 ((eq part '&) 167 (cons 'and 168 (loop for i in rest 169 collect (query-parse i)))) 170 ((eq part '~) 171 (destructuring-bind (field value) rest 172 (list match-func field value (quote record)))) 173 ((eq part '=) 174 (destructuring-bind (field value) rest 175 (list equal-func field value (quote record)))))))) 176 (eval `(lambda (record) ,(query-parse query))))) 177 178 (defun kvplist2get (plist2 keyword value) 179 "Get the plist with KEYWORD / VALUE from the list of plists." 180 (loop for plist in plist2 181 if (equal (plist-get plist keyword) value) 182 return plist)) 183 184 (defun kvthing->keyword (str-or-symbol) 185 "Convert STR-OR-SYMBOL into a keyword symbol." 186 (let ((str 187 (cond 188 ((symbolp str-or-symbol) (symbol-name str-or-symbol)) 189 ((stringp str-or-symbol) str-or-symbol)))) 190 (intern 191 (if (eq (aref str 0) ?:) str (concat ":" str))))) 192 193 (defun kvalist->plist (alist) 194 "Convert an alist to a plist." 195 ;; Why doesn't elisp provide this? 196 (loop for pair in alist 197 append (list 198 (kvthing->keyword 199 (car pair)) 200 (cdr pair)))) 201 202 (defun kvacons (&rest args) 203 "Make an alist from the plist style args." 204 (kvplist->alist args)) 205 206 (defun keyword->symbol (keyword) 207 "A keyword is a symbol leading with a :. 208 209 Converting to a symbol means dropping the :." 210 (if (keywordp keyword) 211 (intern (substring (symbol-name keyword) 1)) 212 keyword)) 213 214 (defun kvplist->alist (plist &optional keys-are-keywords) 215 "Convert PLIST to an alist. 216 217 The keys are expected to be :prefixed and the colons are removed 218 unless KEYS-ARE-KEYWORDS is `t'. 219 220 The keys in the resulting alist are always symbols." 221 (when plist 222 (loop for (key value . rest) on plist by 'cddr 223 collect 224 (cons (if keys-are-keywords 225 key 226 (keyword->symbol key)) 227 value)))) 228 229 (defun kvalist2->plist (alist2) 230 "Convert a list of alists too a list of plists." 231 (loop for alist in alist2 232 append 233 (list (kvalist->plist alist)))) 234 235 (defun kvalist->keys (alist) 236 "Get just the keys from the alist." 237 (mapcar (lambda (pair) (car pair)) alist)) 238 239 (defun kvalist->values (alist) 240 "Get just the values from the alist." 241 (mapcar (lambda (pair) (cdr pair)) alist)) 242 243 (defun kvalist-sort (alist pred) 244 "Sort ALIST (by key) with PRED." 245 (sort alist (lambda (a b) (funcall pred (car a) (car b))))) 246 247 (defun kvalist-sort-by-value (alist pred) 248 "Sort ALIST by value with PRED." 249 (sort alist (lambda (a b) (funcall pred (cdr a) (cdr b))))) 250 251 (defun kvalist->filter-keys (alist &rest keys) 252 "Return the ALIST filtered to the KEYS list. 253 254 Only pairs where the car is a `member' of KEYS will be returned." 255 (loop for a in alist 256 if (member (car a) keys) 257 collect a)) 258 259 (defun kvplist->filter-keys (plist &rest keys) 260 "Filter the plist to just those matching KEYS. 261 262 `kvalist->filter-keys' is actually used to do this work." 263 (let ((symkeys 264 (loop for k in keys 265 collect (let ((strkey (symbol-name k))) 266 (if (equal (substring strkey 0 1) ":") 267 (intern (substring strkey 1)) 268 k))))) 269 (kvalist->plist 270 (apply 271 'kvalist->filter-keys 272 (cons (kvplist->alist plist) symkeys))))) 273 274 (defun kvplist2->filter-keys (plist2 &rest keys) 275 "Return the PLIST2 (a list of plists) filtered to the KEYS." 276 (loop for plist in plist2 277 collect (apply 'kvplist->filter-keys (cons plist keys)))) 278 279 (defun kvalist2->filter-keys (alist2 &rest keys) 280 "Return the ALIST2 (a list of alists) filtered to the KEYS." 281 (loop for alist in alist2 282 collect (apply 'kvalist->filter-keys (cons alist keys)))) 283 284 (defun kvalist2->alist (alist2 car-key cdr-key &optional proper) 285 "Reduce the ALIST2 (a list of alists) to a single alist. 286 287 CAR-KEY is the key of each alist to use as the resulting key and 288 CDR-KEY is the key of each alist to user as the resulting cdr. 289 290 For example, if CAR-KEY is `email' and CDR-KEY is `name' the 291 records: 292 293 '((user . \"nic\")(name . \"Nic\")(email . \"nic@domain\") 294 (user . \"jim\")(name . \"Jim\")(email . \"jim@domain\")) 295 296 could be reduced to: 297 298 '((\"nic@domain\" . \"Nic\") 299 (\"jim@domain\" . \"Jic\")) 300 301 If PROPER is `t' then the alist is a list of proper lists, not 302 cons cells." 303 (loop for alist in alist2 304 collect (apply (if proper 'list 'cons) 305 (list 306 (assoc-default car-key alist) 307 (assoc-default cdr-key alist))))) 308 309 (defun kvalist-keys->* (alist fn) 310 "Convert the keys of ALIST through FN." 311 (mapcar 312 (lambda (pair) 313 (cons 314 (funcall fn (car pair)) 315 (cdr pair))) 316 alist)) 317 318 (defun* kvalist-keys->symbols (alist &key (first-fn 'identity)) 319 "Convert the keys of ALIST into symbols. 320 321 If key parameter FIRST-FN is present it should be a function 322 which will be used to first transform the string key. A popular 323 choice might be `downcase' for example, to cause all symbol keys 324 to be lower-case." 325 (kvalist-keys->* 326 alist 327 (lambda (key) 328 (intern (funcall first-fn (format "%s" key)))))) 329 330 (defun kvalist2-filter (alist2 fn) 331 "Filter the list of alists with FN." 332 (let (value) 333 (loop for rec in alist2 334 do (setq value (funcall fn rec)) 335 if value 336 collect rec))) 337 338 (defun kvidentity (a b) 339 "Returns a cons of A B." 340 (cons a b)) 341 342 (defun kvcar (a b) 343 "Given A B returns A." 344 a) 345 346 (defun kvcdr (a b) 347 "Given A B returns B." 348 b) 349 350 (defun kvcmp (a b) 351 "Do a comparison of the two values using printable syntax. 352 353 Use this as the function to pass to `sort'." 354 (string-lessp (if a (format "%S" a) "") 355 (if b (format "%S" b) ""))) 356 357 (defun kvqsort (lst) 358 "Do a sort using `kvcmp'." 359 (sort lst 'kvcmp)) 360 361 (progn 362 (put 'kvalist-key 363 'error-conditions 364 '(error)) 365 (put 'kvalist-key 366 'error-message 367 "No such key found in alist.")) 368 369 (defun kvalist-set-value! (alist key value) 370 "Destructively set the value of KEY to VALUE in ALIST. 371 372 If the assoc is not found this adds it to alist." 373 (let ((cell (assoc key alist))) 374 (if (consp cell) 375 (setcdr cell value) 376 ;; Else what to do? 377 (signal 'kvalist-key (list alist key))))) 378 379 (defun kvdotassoc-fn (expr table func) 380 "Use the dotted EXPR to access deeply nested data in TABLE. 381 382 EXPR is a dot separated expression, either a symbol or a string. 383 For example: 384 385 \"a.b.c\" 386 387 or: 388 389 'a.b.c 390 391 If the EXPR is a symbol then the keys of the alist are also 392 expected to be symbols. 393 394 TABLE is expected to be an alist currently. 395 396 FUNC is some sort of `assoc' like function." 397 (let ((state table) 398 (parts 399 (if (symbolp expr) 400 (mapcar 401 'intern 402 (split-string (symbol-name expr) "\\.")) 403 ;; Else it's a string 404 (split-string expr "\\.")))) 405 (catch 'break 406 (while (listp parts) 407 (let ((traverse (funcall func (car parts) state))) 408 (setq parts (cdr parts)) 409 (if parts 410 (setq state (cdr traverse)) 411 (throw 'break (cdr traverse)))))))) 412 413 (defun kvdotassoc (expr table) 414 "Dotted expression handling with `assoc'." 415 (kvdotassoc-fn expr table 'assoc)) 416 417 (defun kvdotassq (expr table) 418 "Dotted expression handling with `assq'." 419 (kvdotassoc-fn expr table 'assq)) 420 421 (defun kvdotassoc= (expr value table) 422 (let ((v (kvdotassoc expr table))) 423 (and v (equal v value) v))) 424 425 (defalias 'dotassoc 'kvdotassoc) 426 (defalias 'dotassq 'kvdotassq) 427 428 ;; Thank you taylanub for this wonderful abstraction. 429 (defmacro kv--destructuring-map (map-function args sequence &rest body) 430 "Helper macro for `destructuring-mapcar' and `destructuring-map'." 431 (declare (indent 3)) 432 (let ((entry (gensym))) 433 `(,map-function (lambda (,entry) 434 (destructuring-bind ,args ,entry ,@body)) 435 ,sequence))) 436 437 (defmacro kvmap-bind (args sexp seq) 438 "A hybrid of `destructuring-bind' and `mapcar' 439 ARGS shall be of the form used with `destructuring-bind' 440 441 Unlike most other mapping forms this is a macro intended to be 442 used for structural transformations, so the expected usage will 443 be that ARGS describes the structure of the items in SEQ, and 444 SEXP will describe the structure desired." 445 (declare (indent 2)) 446 `(kv--destructuring-map mapcar ,args ,seq ,sexp)) 447 448 (defalias 'map-bind 'kvmap-bind) 449 450 (defun kvplist-merge (&rest plists) 451 "Merge the 2nd and subsequent plists into the first. 452 453 Values set by lists to the left are clobbered." 454 (let ((result (car plists)) 455 (plists (cdr plists))) 456 (loop for plist in plists do 457 (loop for (key val) on plist by 'cddr do 458 (setq result (plist-put result key val)))) 459 result)) 460 461 (provide 'kv) 462 (provide 'dotassoc) 463 464 ;;; kv.el ends here