Initial Commit
[packages] / xemacs-packages / oo-browser / hasht.el
1 ;;!emacs
2 ;;
3 ;; FILE:         hasht.el
4 ;; SUMMARY:      Create hash tables from lists and operate on them.
5 ;; USAGE:        GNU Emacs Lisp Library
6 ;; KEYWORDS:     extensions, tools
7 ;;
8 ;; AUTHOR:       Bob Weiner
9 ;; ORG:          BeOpen.com
10 ;;
11 ;; ORIG-DATE:    16-Mar-90 at 03:38:48
12 ;; LAST-MOD:      9-Jun-99 at 18:06:06 by Bob Weiner
13 ;;
14 ;; Copyright (C) 1990-1995, 1997  BeOpen.com
15 ;; See the file BR-COPY for license information.
16 ;;
17 ;; This file is part of the OO-Browser.
18 ;;
19 ;; DESCRIPTION:  
20 ;;
21 ;;   Featureful set of hash table operators for use in personal programs.
22 ;;
23 ;;   `hash-make' creates a hash table from an association list, `hash-add'
24 ;;   adds a value-key pair to a hash table, and `hash-lookup' finds the value
25 ;;   associated with a given key in a hash table, if any.
26 ;;
27 ;;   `hash-map' does the same thing as `mapcar' but operates on hash tables
28 ;;   instead.
29 ;;
30 ;;   For a list of 300 items, these hash tables improve lookup times by a
31 ;;   factor of between 8 and 10 to 1 over those for an unsorted list.
32 ;;
33 ;;   Public and private function names are alphabetized for easy location.
34 ;;
35 ;; DESCRIP-END.
36
37 ;;; ************************************************************************
38 ;;; Public variables
39 ;;; ************************************************************************
40
41 (defvar hash-merge-values-function 'hash-merge-values
42   "*Function to call in hash-merge to merge the values from 2 hash tables that contain the same key.
43 It is sent the two values as arguments.")
44
45 ;;; ************************************************************************
46 ;;; Public functions
47 ;;; ************************************************************************
48
49 (defun hash-add (value key hash-table)
50   "Add VALUE, any lisp object, referenced by KEY, a string, to HASH-TABLE.
51 Replaces any VALUE previously referenced by KEY."
52   (if (hashp hash-table)
53       (let* ((obarray (hash-obarray hash-table))
54              (sym (intern key obarray)))
55         (if sym (set sym value)))))
56
57 (defun hash-copy (hash-table)
58   "Return a copy of HASH-TABLE, list and vector elements are shared across both tables."
59   (if (not (hashp hash-table))
60       (error "(hash-copy): Invalid hash-table: `%s'" hash-table))
61   (let ((htable-copy (hash-make (length (hash-obarray hash-table)))))
62     (hash-map
63      (function (lambda (elt) (hash-add (car elt) (cdr elt) htable-copy)))
64      hash-table)
65     htable-copy))
66
67 (defun hash-count (hash-table)
68   "Return number of elements stored in HASH-TABLE or nil if not a valid hash table."
69   (if (hashp hash-table)
70       (let ((obarray (hash-obarray hash-table))
71             (count 0))
72         (mapatoms (function
73                     (lambda (sym)
74                       (and (boundp sym) sym (setq count (1+ count)))))
75                   obarray)
76         count)))
77
78 (defun hash-delete (key hash-table)
79   "Delete element referenced by KEY, a string, from HASH-TABLE.
80 Return nil if KEY is not in HASH-TABLE or non-nil otherwise."
81   (if (hashp hash-table)
82       (let* ((obarray (hash-obarray hash-table))
83              (sym (intern-soft key obarray)))
84         (if sym
85             (progn (makunbound sym)
86                    (unintern sym))))))
87
88 (defun hash-deep-copy (obj)
89   "Return a copy of OBJ with new copies of all elements, except symbols."
90   (cond ((null obj) nil)
91         ((stringp obj)
92          (copy-sequence obj))
93         ((hashp obj)
94          (let ((htable-copy (hash-make (length (hash-obarray obj)))))
95            (mapcar
96             (function
97              (lambda (elt) (hash-add (car elt) (cdr elt) htable-copy)))
98             (hash-map 'hash-deep-copy obj))
99            htable-copy))
100         ((vectorp obj)
101          ;; convert to list for mapping
102          (setq obj (append obj nil))
103          ;; Return as a vector
104          (vconcat (mapcar 'hash-deep-copy obj)))
105         ((atom obj) obj)
106         ((nlistp obj)
107          (error "(hash-deep-copy): Invalid type, `%s'" obj))
108         (t ;; list
109          (cons (hash-deep-copy (car obj)) (hash-deep-copy (cdr obj))))))
110
111 (defun hash-empty-p (hash-table)
112   "Return t if HASH-TABLE is empty, else nil."
113   (and (hashp hash-table) (equal hash-table hash-empty-htable)))
114
115 (defalias  'hash-get  'hash-lookup)
116
117 (defun hash-key-p (key hash-table)
118   "Return non-nil iff KEY is in HASH-TABLE.  KEY's hash table symbol is returned."
119   (if (hashp hash-table)
120       (let* ((obarray (hash-obarray hash-table))
121              (sym (intern-soft key obarray)))
122          (if (boundp sym) sym))))
123
124 (defun hash-lookup (key hash-table)
125   "Lookup KEY in HASH-TABLE and return associated value.
126 If value is nil, this function does not tell you whether or not KEY is in the
127 hash table.  Use `hash-key-p' instead for that function."
128   (if (hashp hash-table)
129       (let* ((obarray (hash-obarray hash-table))
130              (sym (intern-soft key obarray)))
131          (if (boundp sym) (symbol-value sym)))))
132
133 (defun hash-make (initializer &optional reverse)
134   "Create a hash table from INITIALIZER.
135 INITIALIZER may be an alist with elements of the form (<value> . <key>) from
136 which the hash table is built.  Alternatively, it may be a non-negative
137 integer which is used as the minimum size of a new, empty hash table.
138 Optional non-nil second argument REVERSE means INITIALIZER has elements of
139 form (<key> . <value>)."
140   (cond ((integerp initializer)
141          (if (>= initializer 0)
142              (cons 'hasht (make-vector (hash-next-prime initializer) 0))
143            (error "(hash-make): Initializer must be >= 0, not `%s'"
144                   initializer)))
145         ((numberp initializer) 
146          (error "(hash-make): Initializer must be a positive integer, not `%f'"
147                 initializer))
148         (t (let* ((vlen (hash-next-prime (length initializer)))
149                   (obarray (make-vector vlen 0))
150                   key value sym)
151              (mapcar
152               (function
153                (lambda (cns)
154                  (if (consp cns)
155                      (if reverse
156                          (setq key (car cns) value (cdr cns))
157                        (setq key (cdr cns) value (car cns))))
158                  (if (setq sym (intern key))
159                      (set sym value))))
160               initializer)
161              (cons 'hasht obarray)))))
162
163 (defun hash-make-prepend (initializer &optional reverse)
164   "Create a hash table from INITIALIZER.
165 INITIALIZER may be an alist with elements of the form (<value> . <key>) from
166 which the hash table is built.  Optional non-nil second argument REVERSE
167 means INITIALIZER has elements of form (<key> . <value>).
168
169 The resultant value associated with a <key> is a list of all of the <values>
170 given in INITIALIZER entries which contain the <key>.  The values are listed
171 in reverse order of occurrence (they are prepended to the list)."
172   (let* ((vlen (hash-next-prime (length initializer)))
173          (obarray (make-vector vlen 0))
174          key value sym)
175     (mapcar
176      (function
177       (lambda (cns)
178         (if (consp cns)
179             (if reverse
180                 (setq key (car cns) value (cdr cns))
181               (setq key (cdr cns) value (car cns))))
182         (setq sym (intern key))
183         (if (boundp sym)
184             (set sym (cons value (symbol-value sym)))
185           (set sym (cons value nil)))))
186      initializer)
187     (cons 'hasht obarray)))
188
189 (defun hash-map (func hash-table)
190   "Return a list of the results of applying FUNC to each (<value> . <key>) element of HASH-TABLE."
191   (if (not (hashp hash-table))
192       (error "(hash-map): Invalid hash-table: `%s'" hash-table))
193   (let ((result))
194     (mapatoms (function
195                 (lambda (sym)
196                   (and (boundp sym)
197                        sym
198                        (setq result (cons (funcall
199                                            func
200                                            (cons (symbol-value sym)
201                                                  (symbol-name sym)))
202                                           result)))))
203               (hash-obarray hash-table))
204     result))
205
206 (defun hash-merge (&rest hash-tables)
207   "Merge any number of HASH-TABLES.  Return resultant hash table.
208 A single argument consisting of a list of hash tables may also be given.
209 Return an empty hash table if any argument from the merge list is other
210 than nil or a hash table.
211
212 Use the value of `hash-merge-values-function' to merge the values of entries
213 whose keys are the same."
214   (let ((empty-ht (hash-make 1)))
215     (and (not (hashp (car hash-tables)))
216          (listp (car hash-tables))
217          ;; Handle situation where a list of hash-tables is passed in as a
218          ;; single argument, rather than as multiple arguments.
219          (setq hash-tables (car hash-tables)))
220     (if (memq nil (mapcar (function (lambda (ht) (or (null ht) (hashp ht))))
221                           hash-tables))
222         empty-ht
223       (setq hash-tables
224             (delq nil (mapcar (function (lambda (ht)
225                                           (if (hash-empty-p ht) nil ht)))
226                               hash-tables)))
227       (let ((len (length hash-tables)))
228         (cond ((= len 0) empty-ht)
229               ((= len 1) (car hash-tables))
230               ;; Make the merged hash-table be 20% larger than the number of
231               ;; entries filled in all hash-tables to be merged, so that
232               ;; hash misses are minimized.
233               (t (let ((htable (hash-make
234                                 (ceiling
235                                  (* 1.2 (apply '+ (mapcar 'hash-count
236                                                           hash-tables))))))
237                        key value)
238                    (mapcar
239                      (function
240                        (lambda (ht)
241                          (hash-map (function
242                                      (lambda (val-key-cons)
243                                        (setq value (car val-key-cons)
244                                              key (cdr val-key-cons))
245                                        (if (not (hash-key-p key htable))
246                                            (hash-add value key htable)
247                                          ;; Merge values
248                                          (hash-add
249                                           (funcall hash-merge-values-function
250                                                    (hash-get key htable)
251                                                    value)
252                                           key htable))))
253                                    ht)))
254                      hash-tables)
255                    htable)))))))
256
257 (defun hash-merge-first-value (value1 value2)
258   "Return a copy of VALUE1 for use in a hash table merge.
259
260 This is suitable for use as a value of `hash-merge-values-function'."
261   ;; Copy list so that merged result does not share structure with the
262   ;; hash tables being merged.
263   (if (listp value1) (copy-sequence value1) value1))
264
265 (defun hash-merge-values (value1 value2)
266   "Return a list from merging VALUE1 and VALUE2 or creating a new list.
267 Nil values are thrown away.  If both arguments are lists, their elements are
268 assumed to be strings and the result is a set of ordered strings.
269
270 This is suitable for use as a value of `hash-merge-values-function'."
271   ;; Copy lists so that merged result does not share structure with the
272   ;; hash tables being merged.
273   (if (listp value1) (setq value1 (copy-sequence value1)))
274   (if (listp value2) (setq value2 (copy-sequence value2)))
275   (cond ((and (listp value1) (listp value2))
276          ;; Assume desired result is a set of strings.
277          (hash-set-of-strings (sort (append value1 value2) 'string-lessp)))
278         ((null value1)
279          value2)
280         ((null value2)
281          value1)
282         ((listp value1)
283          (cons value2 value1))
284         ((listp value2)
285          (cons value1 value2))
286         (t (list value1 value2))))
287
288 (make-obsolete 'hash-new 'hash-make)
289 (defun hash-new (size)
290   "Return a new hash table of SIZE elements.
291 This is obsolete.  Use `hash-make' instead."
292   (hash-make size))
293
294 (defun hash-prepend (value key hash-table)
295   "Prepend VALUE onto the list value referenced by KEY, a string, in HASH-TABLE.
296 If KEY is not found in HASH-TABLE, it is added with a value of (list VALUE)."
297   (if (hashp hash-table)
298       (let* ((obarray (hash-obarray hash-table))
299              (sym (intern key obarray)))
300         (if (boundp sym)
301             (if (listp (symbol-value sym))
302                 (set sym (cons value (symbol-value sym)))
303               (error "(hash-prepend): `%s' key's value is not a list."
304                      key))
305           (set sym (cons value nil))))))
306
307 (defun hash-prin1 (hash-table &optional stream)
308   "Output the printed representation of HASH-TABLE as a list.
309 Quoting characters are printed when needed to make output that `read'
310 can handle, whenever this is possible.
311 Output stream is STREAM, or value of `standard-output'."
312   (if (not (hashp hash-table))
313       (progn (prin1 hash-table stream)
314              (princ "\n" stream))
315     (princ "\(\n" stream)
316     (hash-map
317      (function (lambda (val-key-cons)
318                  (prin1 val-key-cons stream)
319                  (princ "\n" stream)))
320      hash-table)
321     (princ "\)\n" stream)))
322
323 (defun hash-replace (value key hash-table)
324   "Replace VALUE referenced by KEY, a string, in HASH-TABLE.
325 An error will occur if KEY is not found in HASH-TABLE."
326   (if (hashp hash-table)
327       (let* ((obarray (hash-obarray hash-table))
328              (sym (intern-soft key obarray)))
329         (if (and (boundp sym) sym)
330             (set sym value)
331           (error "(hash-replace): `%s' key not found in hash table." key)))))
332
333 (defun hash-resize (hash-table new-size)
334   "Resize HASH-TABLE to NEW-SIZE without losing any elements and return new table.
335 NEW-SIZE must be greater than 0.  Hashing works best if NEW-SIZE is a prime
336 number.  See also `hash-next-prime'."
337   (if (< new-size 1)
338       (error "(hash-resize): Cannot resize hash table to size %d" new-size))
339   (let ((htable (hash-make new-size)))
340     (hash-map (function
341                 (lambda (elt)
342                   (hash-add (car elt) (cdr elt) htable)))
343               hash-table)
344     htable))
345
346 (defun hash-resize-p (hash-table)
347   "Resizes HASH-TABLE to 1.5 times its size if above 80% full.
348 Returns new hash table when resized, else nil."
349   (if (hashp hash-table)
350       (let ((count (hash-count hash-table))
351             (size (length (hash-obarray hash-table))))
352         (if (> (* count (/ count 5)) size)
353             (hash-resize hash-table (hash-next-prime (+ size (/ size 2))))))))
354
355 (defun hash-size (hash-table)
356   "Return size of HASH-TABLE which is >= number of elements in the table.
357 Return nil if not a valid hash table."
358   (if (hashp hash-table)
359       (length (hash-obarray hash-table))))
360 (defalias 'hash-length 'hash-size)
361
362 (defun hashp (object)
363   "Return non-nil if OBJECT is a hash-table."
364   (and (listp object) (eq (car object) 'hasht)
365        (vectorp (cdr object))))
366
367 ;;; ************************************************************************
368 ;;; Private functions
369 ;;; ************************************************************************
370
371 (defun hash-next-prime (n)
372   "Return next prime number >= N."
373   (if (<= n 2)
374       2
375     (and (= (% n 2) 0) (setq n (1+ n)))
376     (while (not (hash-prime-p n))
377       (setq n (+ n 2)))
378     n))
379
380 (defun hash-obarray (hash-table)
381   "Return symbol table (object array) portion of HASH-TABLE."
382   (cdr hash-table))
383
384 (defun hash-prime-p (n)
385   "Return non-nil iff N is prime."
386   (if (< n 0) (setq n (- n)))
387   (let ((small-primes '(1 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67
388                           71 73 79 83 89)))
389     (cond ((< n 91) (memq n small-primes))
390           ((< n 7921)  ;; 89, max small-prime, squared
391            (let ((prime t)
392                  (pr-list small-primes))
393              (while (and (setq pr-list (cdr pr-list))
394                          (setq prime (/= (% n (car pr-list)) 0))))
395              prime))
396           ((or (= (% n 3) 0) (= (% n 2) 0)) nil)
397           ((let ((factor1 5)
398                  (factor2 7)
399                  (is-prime))
400              (while (and (<= (* factor1 factor1) n)
401                          (setq is-prime (and (/= (% n factor1) 0)
402                                              (/= (% n factor2) 0))))
403                (setq factor1 (+ factor1 6)
404                      factor2 (+ factor2 6)))
405              is-prime)))))
406
407 (defun hash-set-of-strings (sorted-strings &optional count)
408   "Return SORTED-STRINGS list with any duplicate entries removed.
409 Optional COUNT conses number of duplicates on to front of list before return."
410   (and count (setq count 0))
411   (let ((elt1) (elt2) (lst sorted-strings)
412         (test (if count
413                   (function
414                     (lambda (a b) (if (string-equal a b)
415                                       (setq count (1+ count)))))
416                 (function (lambda (a b) (string-equal a b))))))
417     (while (setq elt1 (car lst) elt2 (car (cdr lst)))
418       (if (funcall test elt1 elt2)
419           (setcdr lst (cdr (cdr lst)))
420         (setq lst (cdr lst)))))
421   (if count (cons count sorted-strings) sorted-strings))
422
423 ;;; ************************************************************************
424 ;;; Private variables
425 ;;; ************************************************************************
426
427 (defvar hash-empty-htable (hash-make 1)
428   "Empty hash table used to test whether other hash tables are empty.")
429
430 (provide 'hasht)