Source

oo-browser / hasht.el

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
;;!emacs
;;
;; FILE:         hasht.el
;; SUMMARY:      Create hash tables from lists and operate on them.
;; USAGE:        GNU Emacs Lisp Library
;; KEYWORDS:     extensions, tools
;;
;; AUTHOR:       Bob Weiner
;; ORG:          BeOpen.com
;;
;; ORIG-DATE:    16-Mar-90 at 03:38:48
;; LAST-MOD:      9-Jun-99 at 18:06:06 by Bob Weiner
;;
;; Copyright (C) 1990-1995, 1997  BeOpen.com
;; See the file BR-COPY for license information.
;;
;; This file is part of the OO-Browser.
;;
;; DESCRIPTION:  
;;
;;   Featureful set of hash table operators for use in personal programs.
;;
;;   `hash-make' creates a hash table from an association list, `hash-add'
;;   adds a value-key pair to a hash table, and `hash-lookup' finds the value
;;   associated with a given key in a hash table, if any.
;;
;;   `hash-map' does the same thing as `mapcar' but operates on hash tables
;;   instead.
;;
;;   For a list of 300 items, these hash tables improve lookup times by a
;;   factor of between 8 and 10 to 1 over those for an unsorted list.
;;
;;   Public and private function names are alphabetized for easy location.
;;
;; DESCRIP-END.

;;; ************************************************************************
;;; Public variables
;;; ************************************************************************

(defvar hash-merge-values-function 'hash-merge-values
  "*Function to call in hash-merge to merge the values from 2 hash tables that contain the same key.
It is sent the two values as arguments.")

;;; ************************************************************************
;;; Public functions
;;; ************************************************************************

(defun hash-add (value key hash-table)
  "Add VALUE, any lisp object, referenced by KEY, a string, to HASH-TABLE.
Replaces any VALUE previously referenced by KEY."
  (if (hashp hash-table)
      (let* ((obarray (hash-obarray hash-table))
	     (sym (intern key obarray)))
	(if sym (set sym value)))))

(defun hash-copy (hash-table)
  "Return a copy of HASH-TABLE, list and vector elements are shared across both tables."
  (if (not (hashp hash-table))
      (error "(hash-copy): Invalid hash-table: `%s'" hash-table))
  (let ((htable-copy (hash-make (length (hash-obarray hash-table)))))
    (hash-map
     (function (lambda (elt) (hash-add (car elt) (cdr elt) htable-copy)))
     hash-table)
    htable-copy))

(defun hash-count (hash-table)
  "Return number of elements stored in HASH-TABLE or nil if not a valid hash table."
  (if (hashp hash-table)
      (let ((obarray (hash-obarray hash-table))
	    (count 0))
	(mapatoms (function
		    (lambda (sym)
		      (and (boundp sym) sym (setq count (1+ count)))))
		  obarray)
	count)))

(defun hash-delete (key hash-table)
  "Delete element referenced by KEY, a string, from HASH-TABLE.
Return nil if KEY is not in HASH-TABLE or non-nil otherwise."
  (if (hashp hash-table)
      (let* ((obarray (hash-obarray hash-table))
	     (sym (intern-soft key obarray)))
	(if sym
	    (progn (makunbound sym)
		   (unintern sym))))))

(defun hash-deep-copy (obj)
  "Return a copy of OBJ with new copies of all elements, except symbols."
  (cond ((null obj) nil)
	((stringp obj)
	 (copy-sequence obj))
	((hashp obj)
	 (let ((htable-copy (hash-make (length (hash-obarray obj)))))
	   (mapcar
	    (function
	     (lambda (elt) (hash-add (car elt) (cdr elt) htable-copy)))
	    (hash-map 'hash-deep-copy obj))
	   htable-copy))
	((vectorp obj)
	 ;; convert to list for mapping
	 (setq obj (append obj nil))
	 ;; Return as a vector
	 (vconcat (mapcar 'hash-deep-copy obj)))
	((atom obj) obj)
	((nlistp obj)
	 (error "(hash-deep-copy): Invalid type, `%s'" obj))
	(t ;; list
	 (cons (hash-deep-copy (car obj)) (hash-deep-copy (cdr obj))))))

(defun hash-empty-p (hash-table)
  "Return t if HASH-TABLE is empty, else nil."
  (and (hashp hash-table) (equal hash-table hash-empty-htable)))

(defalias  'hash-get  'hash-lookup)

(defun hash-key-p (key hash-table)
  "Return non-nil iff KEY is in HASH-TABLE.  KEY's hash table symbol is returned."
  (if (hashp hash-table)
      (let* ((obarray (hash-obarray hash-table))
	     (sym (intern-soft key obarray)))
	 (if (boundp sym) sym))))

(defun hash-lookup (key hash-table)
  "Lookup KEY in HASH-TABLE and return associated value.
If value is nil, this function does not tell you whether or not KEY is in the
hash table.  Use `hash-key-p' instead for that function."
  (if (hashp hash-table)
      (let* ((obarray (hash-obarray hash-table))
	     (sym (intern-soft key obarray)))
	 (if (boundp sym) (symbol-value sym)))))

(defun hash-make (initializer &optional reverse)
  "Create a hash table from INITIALIZER.
INITIALIZER may be an alist with elements of the form (<value> . <key>) from
which the hash table is built.  Alternatively, it may be a non-negative
integer which is used as the minimum size of a new, empty hash table.
Optional non-nil second argument REVERSE means INITIALIZER has elements of
form (<key> . <value>)."
  (cond ((integerp initializer)
	 (if (>= initializer 0)
	     (cons 'hasht (make-vector (hash-next-prime initializer) 0))
	   (error "(hash-make): Initializer must be >= 0, not `%s'"
		  initializer)))
	((numberp initializer) 
	 (error "(hash-make): Initializer must be a positive integer, not `%f'"
		initializer))
	(t (let* ((vlen (hash-next-prime (length initializer)))
		  (obarray (make-vector vlen 0))
		  key value sym)
	     (mapcar
	      (function
	       (lambda (cns)
		 (if (consp cns)
		     (if reverse
			 (setq key (car cns) value (cdr cns))
		       (setq key (cdr cns) value (car cns))))
		 (if (setq sym (intern key))
		     (set sym value))))
	      initializer)
	     (cons 'hasht obarray)))))

(defun hash-make-prepend (initializer &optional reverse)
  "Create a hash table from INITIALIZER.
INITIALIZER may be an alist with elements of the form (<value> . <key>) from
which the hash table is built.  Optional non-nil second argument REVERSE
means INITIALIZER has elements of form (<key> . <value>).

The resultant value associated with a <key> is a list of all of the <values>
given in INITIALIZER entries which contain the <key>.  The values are listed
in reverse order of occurrence (they are prepended to the list)."
  (let* ((vlen (hash-next-prime (length initializer)))
	 (obarray (make-vector vlen 0))
	 key value sym)
    (mapcar
     (function
      (lambda (cns)
	(if (consp cns)
	    (if reverse
		(setq key (car cns) value (cdr cns))
	      (setq key (cdr cns) value (car cns))))
	(setq sym (intern key))
	(if (boundp sym)
	    (set sym (cons value (symbol-value sym)))
	  (set sym (cons value nil)))))
     initializer)
    (cons 'hasht obarray)))

(defun hash-map (func hash-table)
  "Return a list of the results of applying FUNC to each (<value> . <key>) element of HASH-TABLE."
  (if (not (hashp hash-table))
      (error "(hash-map): Invalid hash-table: `%s'" hash-table))
  (let ((result))
    (mapatoms (function
		(lambda (sym)
		  (and (boundp sym)
		       sym
		       (setq result (cons (funcall
					   func
					   (cons (symbol-value sym)
						 (symbol-name sym)))
					  result)))))
	      (hash-obarray hash-table))
    result))

(defun hash-merge (&rest hash-tables)
  "Merge any number of HASH-TABLES.  Return resultant hash table.
A single argument consisting of a list of hash tables may also be given.
Return an empty hash table if any argument from the merge list is other
than nil or a hash table.

Use the value of `hash-merge-values-function' to merge the values of entries
whose keys are the same."
  (let ((empty-ht (hash-make 1)))
    (and (not (hashp (car hash-tables)))
	 (listp (car hash-tables))
	 ;; Handle situation where a list of hash-tables is passed in as a
	 ;; single argument, rather than as multiple arguments.
	 (setq hash-tables (car hash-tables)))
    (if (memq nil (mapcar (function (lambda (ht) (or (null ht) (hashp ht))))
			  hash-tables))
	empty-ht
      (setq hash-tables
	    (delq nil (mapcar (function (lambda (ht)
					  (if (hash-empty-p ht) nil ht)))
			      hash-tables)))
      (let ((len (length hash-tables)))
	(cond ((= len 0) empty-ht)
	      ((= len 1) (car hash-tables))
	      ;; Make the merged hash-table be 20% larger than the number of
	      ;; entries filled in all hash-tables to be merged, so that
	      ;; hash misses are minimized.
	      (t (let ((htable (hash-make
				(ceiling
				 (* 1.2 (apply '+ (mapcar 'hash-count
							  hash-tables))))))
		       key value)
		   (mapcar
		     (function
		       (lambda (ht)
			 (hash-map (function
				     (lambda (val-key-cons)
				       (setq value (car val-key-cons)
					     key (cdr val-key-cons))
				       (if (not (hash-key-p key htable))
					   (hash-add value key htable)
					 ;; Merge values
					 (hash-add
					  (funcall hash-merge-values-function
						   (hash-get key htable)
						   value)
					  key htable))))
				   ht)))
		     hash-tables)
		   htable)))))))

(defun hash-merge-first-value (value1 value2)
  "Return a copy of VALUE1 for use in a hash table merge.

This is suitable for use as a value of `hash-merge-values-function'."
  ;; Copy list so that merged result does not share structure with the
  ;; hash tables being merged.
  (if (listp value1) (copy-sequence value1) value1))

(defun hash-merge-values (value1 value2)
  "Return a list from merging VALUE1 and VALUE2 or creating a new list.
Nil values are thrown away.  If both arguments are lists, their elements are
assumed to be strings and the result is a set of ordered strings.

This is suitable for use as a value of `hash-merge-values-function'."
  ;; Copy lists so that merged result does not share structure with the
  ;; hash tables being merged.
  (if (listp value1) (setq value1 (copy-sequence value1)))
  (if (listp value2) (setq value2 (copy-sequence value2)))
  (cond ((and (listp value1) (listp value2))
	 ;; Assume desired result is a set of strings.
	 (hash-set-of-strings (sort (append value1 value2) 'string-lessp)))
	((null value1)
	 value2)
	((null value2)
	 value1)
	((listp value1)
	 (cons value2 value1))
	((listp value2)
	 (cons value1 value2))
	(t (list value1 value2))))

(make-obsolete 'hash-new 'hash-make)
(defun hash-new (size)
  "Return a new hash table of SIZE elements.
This is obsolete.  Use `hash-make' instead."
  (hash-make size))

(defun hash-prepend (value key hash-table)
  "Prepend VALUE onto the list value referenced by KEY, a string, in HASH-TABLE.
If KEY is not found in HASH-TABLE, it is added with a value of (list VALUE)."
  (if (hashp hash-table)
      (let* ((obarray (hash-obarray hash-table))
	     (sym (intern key obarray)))
	(if (boundp sym)
	    (if (listp (symbol-value sym))
		(set sym (cons value (symbol-value sym)))
	      (error "(hash-prepend): `%s' key's value is not a list."
		     key))
	  (set sym (cons value nil))))))

(defun hash-prin1 (hash-table &optional stream)
  "Output the printed representation of HASH-TABLE as a list.
Quoting characters are printed when needed to make output that `read'
can handle, whenever this is possible.
Output stream is STREAM, or value of `standard-output'."
  (if (not (hashp hash-table))
      (progn (prin1 hash-table stream)
	     (princ "\n" stream))
    (princ "\(\n" stream)
    (hash-map
     (function (lambda (val-key-cons)
		 (prin1 val-key-cons stream)
		 (princ "\n" stream)))
     hash-table)
    (princ "\)\n" stream)))

(defun hash-replace (value key hash-table)
  "Replace VALUE referenced by KEY, a string, in HASH-TABLE.
An error will occur if KEY is not found in HASH-TABLE."
  (if (hashp hash-table)
      (let* ((obarray (hash-obarray hash-table))
	     (sym (intern-soft key obarray)))
	(if (and (boundp sym) sym)
	    (set sym value)
	  (error "(hash-replace): `%s' key not found in hash table." key)))))

(defun hash-resize (hash-table new-size)
  "Resize HASH-TABLE to NEW-SIZE without losing any elements and return new table.
NEW-SIZE must be greater than 0.  Hashing works best if NEW-SIZE is a prime
number.  See also `hash-next-prime'."
  (if (< new-size 1)
      (error "(hash-resize): Cannot resize hash table to size %d" new-size))
  (let ((htable (hash-make new-size)))
    (hash-map (function
		(lambda (elt)
		  (hash-add (car elt) (cdr elt) htable)))
	      hash-table)
    htable))

(defun hash-resize-p (hash-table)
  "Resizes HASH-TABLE to 1.5 times its size if above 80% full.
Returns new hash table when resized, else nil."
  (if (hashp hash-table)
      (let ((count (hash-count hash-table))
	    (size (length (hash-obarray hash-table))))
	(if (> (* count (/ count 5)) size)
	    (hash-resize hash-table (hash-next-prime (+ size (/ size 2))))))))

(defun hash-size (hash-table)
  "Return size of HASH-TABLE which is >= number of elements in the table.
Return nil if not a valid hash table."
  (if (hashp hash-table)
      (length (hash-obarray hash-table))))
(defalias 'hash-length 'hash-size)

(defun hashp (object)
  "Return non-nil if OBJECT is a hash-table."
  (and (listp object) (eq (car object) 'hasht)
       (vectorp (cdr object))))

;;; ************************************************************************
;;; Private functions
;;; ************************************************************************

(defun hash-next-prime (n)
  "Return next prime number >= N."
  (if (<= n 2)
      2
    (and (= (% n 2) 0) (setq n (1+ n)))
    (while (not (hash-prime-p n))
      (setq n (+ n 2)))
    n))

(defun hash-obarray (hash-table)
  "Return symbol table (object array) portion of HASH-TABLE."
  (cdr hash-table))

(defun hash-prime-p (n)
  "Return non-nil iff N is prime."
  (if (< n 0) (setq n (- n)))
  (let ((small-primes '(1 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67
			  71 73 79 83 89)))
    (cond ((< n 91) (memq n small-primes))
	  ((< n 7921)  ;; 89, max small-prime, squared
	   (let ((prime t)
		 (pr-list small-primes))
	     (while (and (setq pr-list (cdr pr-list))
			 (setq prime (/= (% n (car pr-list)) 0))))
	     prime))
	  ((or (= (% n 3) 0) (= (% n 2) 0)) nil)
	  ((let ((factor1 5)
		 (factor2 7)
		 (is-prime))
	     (while (and (<= (* factor1 factor1) n)
			 (setq is-prime (and (/= (% n factor1) 0)
					     (/= (% n factor2) 0))))
	       (setq factor1 (+ factor1 6)
		     factor2 (+ factor2 6)))
	     is-prime)))))

(defun hash-set-of-strings (sorted-strings &optional count)
  "Return SORTED-STRINGS list with any duplicate entries removed.
Optional COUNT conses number of duplicates on to front of list before return."
  (and count (setq count 0))
  (let ((elt1) (elt2) (lst sorted-strings)
	(test (if count
		  (function
		    (lambda (a b) (if (string-equal a b)
				      (setq count (1+ count)))))
		(function (lambda (a b) (string-equal a b))))))
    (while (setq elt1 (car lst) elt2 (car (cdr lst)))
      (if (funcall test elt1 elt2)
	  (setcdr lst (cdr (cdr lst)))
	(setq lst (cdr lst)))))
  (if count (cons count sorted-strings) sorted-strings))

;;; ************************************************************************
;;; Private variables
;;; ************************************************************************

(defvar hash-empty-htable (hash-make 1)
  "Empty hash table used to test whether other hash tables are empty.")

(provide 'hasht)