3 * File name: ir/adt/set.c
4 * Purpose: Set --- collection of entries that are unique wrt to a key.
5 * Author: Markus Armbruster
7 * Created: 1999 by getting from fiasco
9 * Copyright: (c) 1995, 1996 Markus Armbruster
10 * Licence: This file protected by GPL - GNU GENERAL PUBLIC LICENSE.
13 /* This code is derived from:
15 From: ejp@ausmelb.oz.AU (Esmond Pitt)
16 Date: Tue, 7 Mar 1989 22:06:26 GMT
17 Subject: v06i042: dynamic hashing version of hsearch(3)
18 Message-ID: <1821@basser.oz>
19 Newsgroups: comp.sources.misc
20 Sender: msgs@basser.oz
22 Posting-number: Volume 6, Issue 42
23 Submitted-By: Esmond Pitt <ejp@ausmelb.oz.AU>
24 Archive-name: dynamic-hash
26 * Dynamic hashing, after CACM April 1988 pp 446-457, by Per-Ake Larson.
27 * Coded into C, with minor code improvements, and with hsearch(3) interface,
28 * by ejp@ausmelb.oz, Jul 26, 1988: 13:16;
30 TODO: Fix Esmond's ugly MixedCapsIdentifiers ;->
47 /* bcopy is not ISO C *
48 #define bcopy(X, Y, Z) memcpy((Y), (X), (Z))
53 # define PMANGLE(pre) pre##_pset
54 # define MANGLEP(post) pset_##post
55 # define MANGLE(pre, post) pre##pset##post
56 # define EQUAL(cmp, elt, key, siz) (!(cmp) ((elt)->entry.dptr, (key)))
59 # define PMANGLE(pre) pre##_set
60 # define MANGLEP(post) set_##post
61 # define MANGLE(pre, post) pre##set##post
62 # define EQUAL(cmp, elt, key, siz) \
63 (((elt)->entry.size == (siz)) && !(cmp) ((elt)->entry.dptr, (key), (siz)))
79 #define TOBSTACK_ID MANGLEP(tag)
83 #define SEGMENT_SIZE_SHIFT 8
84 #define SEGMENT_SIZE (1 << SEGMENT_SIZE_SHIFT)
85 #define DIRECTORY_SIZE_SHIFT 8
86 #define DIRECTORY_SIZE (1 << DIRECTORY_SIZE_SHIFT)
87 #define MAX_LOAD_FACTOR 4
90 typedef struct element {
91 struct element *chain;
92 MANGLEP (entry) entry;
97 short p; /* Next bucket to be split */
98 short maxp; /* upper bound on p during expansion */
99 int nkey; /* current # keys */
100 short nseg; /* current # segments */
101 Segment *dir[DIRECTORY_SIZE];
102 MANGLEP(cmp_fun) cmp; /* function comparing entries */
104 Element *iter_tail; /* non-NULL while iterating over elts */
110 int naccess, ncollision, ndups;
122 MANGLEP(stats) (SET *table)
126 Element *q = table->free_list;
127 while (q) { q = q->chain; ++nfree; }
129 printf (" accesses collisions keys duplicates longest wasted\n%12d%12d%12d%12d%12d%12d\n",
130 table->naccess, table->ncollision, table->nkey, table->ndups, table->max_chain_len, nfree);
134 stat_chain_len (SET *table, int chain_len)
136 table->ncollision += chain_len;
137 if (table->max_chain_len < chain_len) table->max_chain_len = chain_len;
140 # define stat_access(table) (++(table)->naccess)
141 # define stat_dup(table) (++(table)->ndups)
145 # define stat_chain_len(table, chain_len) ((void)0)
146 # define stat_access(table) ((void)0)
147 # define stat_dup(table) ((void)0)
153 const char *MANGLEP(tag);
157 MANGLEP(describe) (SET *table)
163 printf ("p=%d maxp=%d nkey=%d nseg=%d\n",
164 table->p, table->maxp, table->nkey, table->nseg);
165 for (i = 0; i < table->nseg; i++) {
167 for (j = 0; j < SEGMENT_SIZE; j++) {
171 if (collide) printf ("<%3d>", collide);
172 else printf ("table");
173 printf ("[%d][%3d]: %u %p\n", i, j, ptr->entry.hash, ptr->entry.dptr);
185 (PMANGLE(new)) (MANGLEP(cmp_fun) cmp, int nslots)
188 SET *table = xmalloc (sizeof (SET));
190 /* Adjust nslots up to next power of 2, minimum SEGMENT_SIZE */
191 assert (nslots >= 0);
192 for (i = SEGMENT_SIZE; i < nslots; i <<= 1) assert (i < (i << 1));
193 nslots = i >> SEGMENT_SIZE_SHIFT;
195 table->nseg = table->p = table->nkey = 0;
196 table->maxp = nslots << SEGMENT_SIZE_SHIFT;
198 table->iter_tail = NULL;
200 table->free_list = NULL;
202 obstack_init (&table->obst);
205 for (i = 0; i < nslots; ++i) {
206 table->dir[i] = (Segment *)obstack_alloc (&table->obst,
207 sizeof (Segment) * SEGMENT_SIZE);
209 memset (table->dir[i], 0, sizeof (Segment) * SEGMENT_SIZE);
214 table->naccess = table->ncollision = table->ndups = 0;
215 table->max_chain_len = 0;
218 table->tag = MANGLEP(tag);
225 PMANGLE(del) (SET *table)
228 MANGLEP(tag) = table->tag;
230 obstack_free (&table->obst, NULL);
236 iter_step (SET *table)
238 if (++table->iter_j >= SEGMENT_SIZE) {
240 if (++table->iter_i >= table->nseg) {
250 MANGLEP(first) (SET *table)
252 assert (!table->iter_tail);
255 while (!table->dir[table->iter_i][table->iter_j]) {
256 if (!iter_step (table)) return NULL;
258 table->iter_tail = table->dir[table->iter_i][table->iter_j];
259 assert (table->iter_tail->entry.dptr);
260 return table->iter_tail->entry.dptr;
265 MANGLEP(next) (SET *table)
267 assert (table->iter_tail);
268 table->iter_tail = table->iter_tail->chain;
269 if (!table->iter_tail) {
271 if (!iter_step (table)) return NULL;
272 } while (!table->dir[table->iter_i][table->iter_j]);
273 table->iter_tail = table->dir[table->iter_i][table->iter_j];
275 assert (table->iter_tail->entry.dptr);
276 return table->iter_tail->entry.dptr;
280 MANGLEP(break) (SET *table)
282 assert (table->iter_tail);
283 table->iter_tail = NULL;
287 static INLINE unsigned
288 Hash (SET *table, unsigned h)
292 address = h & (table->maxp - 1);
293 if (address < (unsigned)table->p)
294 address = h & ((table->maxp << 1) - 1); /* h % (2*table->maxp) */
302 return ( ++table->nkey
303 > (table->nseg << SEGMENT_SIZE_SHIFT) * MAX_LOAD_FACTOR);
308 expand_table (SET *table)
311 int OldSegmentIndex, NewSegmentIndex;
312 int OldSegmentDir, NewSegmentDir;
319 if (table->maxp + table->p < (DIRECTORY_SIZE << SEGMENT_SIZE_SHIFT)) {
320 /* Locate the bucket to be split */
321 OldSegmentDir = table->p >> SEGMENT_SIZE_SHIFT;
322 OldSegment = table->dir[OldSegmentDir];
323 OldSegmentIndex = table->p & (SEGMENT_SIZE-1);
325 /* Expand address space; if necessary create a new segment */
326 NewAddress = table->maxp + table->p;
327 NewSegmentDir = NewAddress >> SEGMENT_SIZE_SHIFT;
328 NewSegmentIndex = NewAddress & (SEGMENT_SIZE-1);
329 if (NewSegmentIndex == 0) {
330 table->dir[NewSegmentDir] =
331 (Segment *)obstack_alloc (&table->obst,
332 sizeof(Segment) * SEGMENT_SIZE);
334 NewSegment = table->dir[NewSegmentDir];
336 /* Adjust state variables */
338 if (table->p == table->maxp) {
339 table->maxp <<= 1; /* table->maxp *= 2 */
344 /* Relocate records to the new bucket */
345 Previous = &OldSegment[OldSegmentIndex];
347 LastOfNew = &NewSegment[NewSegmentIndex];
349 while (Current != NULL) {
350 if (Hash (table, Current->entry.hash) == NewAddress) {
351 /* move to new chain */
352 *LastOfNew = Current;
353 *Previous = Current->chain;
354 LastOfNew = &Current->chain;
355 Current = Current->chain;
358 /* leave on old chain */
359 Previous = &Current->chain;
360 Current = Current->chain;
368 MANGLE(_,_search) (SET *table,
374 MANGLE(_,_action) action)
377 Segment *CurrentSegment;
379 MANGLEP(cmp_fun) cmp = table->cmp;
384 assert (!table->iter_tail);
387 MANGLEP(tag) = table->tag;
391 /* Find collision chain */
392 h = Hash (table, hash);
393 SegmentIndex = h & (SEGMENT_SIZE-1);
394 CurrentSegment = table->dir[h >> SEGMENT_SIZE_SHIFT];
395 assert (CurrentSegment != NULL);
396 q = CurrentSegment[SegmentIndex];
398 /* Follow collision chain */
399 while (q && !EQUAL (cmp, q, key, size)) {
404 stat_chain_len (table, chain_len);
406 if (!q && (action != MANGLE(_,_find))) { /* not found, insert */
407 if (CurrentSegment[SegmentIndex]) stat_dup (table);
410 if (table->free_list) {
411 q = table->free_list;
412 table->free_list = table->free_list->chain;
414 q = obstack_alloc (&table->obst, sizeof (Element));
416 q->entry.dptr = (void *)key;
418 obstack_blank (&table->obst, offsetof (Element, entry.dptr));
419 if (action == _set_hinsert0)
420 obstack_grow0 (&table->obst, key, size);
422 obstack_grow (&table->obst, key, size);
423 q = obstack_finish (&table->obst);
424 q->entry.size = size;
426 q->chain = CurrentSegment[SegmentIndex];
427 q->entry.hash = hash;
428 CurrentSegment[SegmentIndex] = q;
430 if (loaded (table)) {
431 expand_table(table); /* doesn't affect q */
437 if (action == _pset_hinsert) return &q->entry;
439 if (action == _set_hinsert || action == _set_hinsert0) return &q->entry;
441 return q->entry.dptr;
448 pset_remove (SET *table, const void *key, unsigned hash)
451 Segment *CurrentSegment;
453 pset_cmp_fun cmp = table->cmp;
458 assert (table && !table->iter_tail);
461 /* Find collision chain */
462 h = Hash (table, hash);
463 SegmentIndex = h & (SEGMENT_SIZE-1);
464 CurrentSegment = table->dir[h >> SEGMENT_SIZE_SHIFT];
465 assert (CurrentSegment != NULL);
466 p = &CurrentSegment[SegmentIndex];
468 /* Follow collision chain */
469 while (!EQUAL (cmp, *p, key, size)) {
475 stat_chain_len (table, chain_len);
479 q->chain = table->free_list;
480 table->free_list = q;
482 return q->entry.dptr;
487 (pset_find) (SET *se, const void *key, unsigned hash)
489 return pset_find (se, key, hash);
494 (pset_insert) (SET *se, const void *key, unsigned hash)
496 return pset_insert (se, key, hash);
501 (pset_hinsert) (SET *se, const void *key, unsigned hash)
503 return pset_hinsert (se, key, hash);
509 (set_find) (set *se, const void *key, size_t size, unsigned hash)
511 return set_find (se, key, size, hash);
516 (set_insert) (set *se, const void *key, size_t size, unsigned hash)
518 return set_insert (se, key, size, hash);
523 (set_hinsert) (set *se, const void *key, size_t size, unsigned hash)
525 return set_hinsert (se, key, size, hash);