hash.c revision f14f4646
1/*
2 * Copyright (c) 2007,2008 Paulo Cesar Pereira de Andrade
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 * Author: Paulo Cesar Pereira de Andrade
24 */
25
26#include "util.h"
27#include <stdlib.h>
28#include <string.h>
29
30/*
31 *   This is a very simplified and adapted version of the hash tables I am
32 * using in a personal project. It was added to try to have a single hash
33 * table implementation in xedit. The lisp (for user code) version was not
34 * converted, but the following hastables have been converted:
35 *  ispell.c - list of replace and ignore words
36 *  hook.c   - list of auto replace words
37 *  internal lisp data structures:
38 *	atoms
39 *	strings
40 *	packages
41 *	opaque types
42 *   also, all code traversing hash tables is now using
43 *	hash_iter_first() and hash_iter_next()
44 *  conversion isn't as good as I originally wanted, code is using hash_check
45 *  instead of hash_get, but this is due to the code not having a basic
46 *  { void *data; int length; } object to store string like objects
47 *
48 *   Also, this hash table implementation was added mainly for the tags
49 * support.
50 */
51
52/*
53 * Prototypes
54 */
55static int hash_equal(hash_table *hash, hash_key *left, hash_key *right);
56static unsigned int hash_data(char *value, unsigned int length);
57static unsigned int hash_value(hash_key *key);
58
59
60/*
61 * Implementation
62 */
63static int
64hash_equal(hash_table *hash, hash_key *left, hash_key *right)
65{
66    if (left->length == right->length) {
67	if (left == right)
68	    return (1);
69	if (hash->compare)
70	    return (hash->compare(left, right));
71	return (memcmp(left->value, right->value, left->length) == 0);
72    }
73
74    return (0);
75}
76
77static unsigned int
78hash_data(char *value, unsigned int length)
79{
80    char		*ptr;
81    unsigned int	i, key;
82
83    for (i = key = 0, ptr = value; i < length; i++)
84	key = (key << (key & 1)) ^ ptr[i];
85
86    return (key);
87}
88
89static unsigned int
90hash_value(hash_key *key)
91{
92    return (hash_data(key->value, key->length));
93}
94
95hash_table *
96hash_new(unsigned int length, hash_compare compare)
97{
98    hash_table	*hash;
99
100    hash = calloc(1, sizeof(hash_table));
101    if (hash) {
102	hash->entries = calloc(length, sizeof(hash_entry *));
103	if (hash->entries) {
104	    hash->length = length;
105	    hash->compare = compare;
106	    hash->iter.offset = -1;
107	}
108	else {
109	    free(hash);
110	    hash = (hash_table *)0;
111	}
112    }
113
114    return (hash);
115}
116
117hash_entry *
118hash_put(hash_table *hash, hash_entry *entry)
119{
120    unsigned int	key;
121    hash_entry		*prev, *ptr;
122
123    /* Offset in hash table vector for this entry */
124    key = hash_value(entry->key) % hash->length;
125
126    /* We hope this is nil for most calls */
127    ptr = hash->entries[key];
128
129    /* Check if clashed with another entry */
130    for (prev = ptr; ptr; prev = ptr, ptr = ptr->next) {
131	/* Replace current entry */
132	if (hash_equal(hash, entry->key, ptr->key)) {
133	    /* If not trying to readd same value */
134	    if (entry != ptr) {
135		if (ptr == prev)
136		    hash->entries[key] = entry;
137		else
138		    prev->next = entry;
139		entry->next = ptr->next;
140		/* Finished */
141	    }
142	    else
143		ptr = (hash_entry *)0;
144	    goto hash_put_done;
145	}
146    }
147
148    /* Add new entry */
149    if (prev == (hash_entry *)0)
150	/* If no entry in offset */
151	hash->entries[key] = entry;
152    else
153	/* Add to end of clashing list */
154	prev->next = entry;
155    entry->next = (hash_entry *)0;
156
157    /* Increase sum of entries counter*/
158    ++hash->count;
159
160hash_put_done:
161    /* ptr will be nil if no entry was replaced, of tried to add
162     * again an entry already in the hash table */
163    return (ptr);
164}
165
166hash_entry *
167hash_get(hash_table *hash, hash_key *name)
168{
169    unsigned int	key;
170    hash_entry		*entry;
171
172    key = hash_value(name) % hash->length;
173    for (entry = hash->entries[key]; entry; entry = entry->next) {
174	if (hash_equal(hash, name, entry->key)) {
175
176	    return (entry);
177	}
178    }
179
180    return ((hash_entry *)0);
181}
182
183hash_entry *
184hash_check(hash_table *hash, char *name, unsigned int length)
185{
186    unsigned int	key;
187    hash_entry		*entry;
188
189    key = hash_data(name, length) % hash->length;
190    for (entry = hash->entries[key]; entry; entry = entry->next) {
191	if (length == entry->key->length &&
192	    memcmp(name, entry->key->value, length) == 0) {
193
194	    return (entry);
195	}
196    }
197
198    return ((hash_entry *)0);
199}
200
201hash_entry *
202hash_rem_no_free(hash_table *hash, hash_entry *entry)
203{
204    unsigned int	key;
205    hash_entry		*ptr, *prev;
206
207    key = hash_value(entry->key) % hash->length;
208    for (ptr = prev = hash->entries[key]; ptr; prev = ptr, ptr = ptr->next) {
209	if (ptr == entry) {
210	    --hash->count;
211	    if (ptr == prev)
212		hash->entries[key] = ptr->next;
213	    else
214		prev->next = ptr->next;
215	    break;
216	}
217    }
218
219    if (ptr && ptr == hash->iter.entry)
220	hash->iter.entry = ptr->next;
221
222    /* If entry wasn't in hash table ptr will be nil */
223    return (ptr);
224}
225
226void
227hash_rem(hash_table *hash, hash_entry *entry)
228{
229    entry = hash_rem_no_free(hash, entry);
230    if (entry) {
231	free(entry->key->value);
232	free(entry->key);
233	free(entry);
234    }
235}
236
237void
238hash_rehash(hash_table *hash, unsigned int length)
239{
240    unsigned int	i, key;
241    hash_entry		*entry, *next, **entries;
242
243    entries = (hash_entry **)calloc(length, sizeof(hash_entry *));
244    if (entries) {
245	/* Populate the new table, note that clashes are now in reverse order */
246	for (i = 0; i < hash->length; i++) {
247	    for (entry = hash->entries[i]; entry; entry = next) {
248		next = entry->next;
249		key = hash_value(entry->key) % length;
250		entry->next = entries[key];
251		entries[key] = entry;
252	    }
253	}
254
255	/* Finish updating hash table */
256	free(hash->entries);
257	hash->entries = entries;
258	hash->length = length;
259    }
260    hash->iter.offset = -1;
261}
262
263hash_entry *
264hash_iter_first(hash_table *hash)
265{
266    hash->iter.offset = 0;
267    hash->iter.entry = (hash_entry *)0;
268
269    return (hash_iter_next(hash));
270}
271
272hash_entry *
273hash_iter_next(hash_table *hash)
274{
275    if (hash->iter.offset >= 0) {
276	if (hash->iter.entry) {
277	    if ((hash->iter.entry = hash->iter.entry->next))
278		return (hash->iter.entry);
279	    ++hash->iter.offset;
280	}
281	for (; hash->iter.offset < hash->length; hash->iter.offset++) {
282	    if ((hash->iter.entry = hash->entries[hash->iter.offset]))
283		return (hash->iter.entry);
284	}
285	hash->iter.entry = (hash_entry *)0;
286	hash->iter.offset = -1;
287    }
288
289    return ((hash_entry *)0);
290}
291
292void
293hash_clr(hash_table *hash)
294{
295    unsigned int	i;
296    hash_entry		*entry, *next;
297
298    /* Extra data should be free'd with the iterator */
299    for (i = 0; i < hash->length; i++) {
300	entry = hash->entries[i];
301	if (entry) {
302	    for (next = entry; entry; entry = next) {
303		next = entry->next;
304		free(entry->key->value);
305		free(entry->key);
306		free(entry);
307	    }
308	    hash->entries[i] = (hash_entry *)0;
309	}
310    }
311
312    hash->count = 0;
313    hash->iter.offset = -1;
314}
315
316void
317hash_del(hash_table *hash)
318{
319    hash_clr(hash);
320    free(hash->entries);
321    free(hash);
322}
323