Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Reimplement dictionaries as hashtables
[simgrid.git] / src / xbt / dict.c
1 /* $Id$ */
2
3 /* dict - a generic dictionnary, variation over the B-tree concept          */
4
5 /* Copyright (c) 2003,2004 Martin Quinson. All rights reserved.             */
6
7 /* This program is free software; you can redistribute it and/or modify it
8  * under the terms of the license (GNU LGPL) which comes with this package. */
9
10 #include <string.h>
11 #include "xbt/ex.h"
12 #include "xbt/log.h"
13 #include "dict_private.h"
14
15 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(xbt_dict,xbt,
16    "Dictionaries provide the same functionnalities than hash tables");
17 /*####[ Private prototypes ]#################################################*/
18
19 /*####[ Code ]###############################################################*/
20
21 /**
22  * \brief Constructor
23  * \return pointer to the destination
24  * \see xbt_dict_new_ext(), xbt_dict_free()
25  *
26  * Creates and initialize a new dictionnary with a default hashtable size.
27  */
28 xbt_dict_t xbt_dict_new(void) {
29   return xbt_dict_new_ext(256);
30 }
31
32 /**
33  * \brief Create a new dictionary with the specified hashtable size
34  * \param hashsize the hashtable size
35  * \return a pointer to the created object
36  * \see xbt_dict_new(), xbt_dict_free()
37  */
38 xbt_dict_t xbt_dict_new_ext(int hashsize) {
39   int i;
40   xbt_dict_t dict = xbt_new0(s_xbt_dict_t, 1);
41   dict->table_size = hashsize;
42   dict->table = xbt_new0(xbt_dictelm_t, dict->table_size);
43
44   for (i = 0; i < hashsize; i++) {
45     dict->table[i] = NULL;
46   }
47
48   return dict;
49 }
50
51 /**
52  * \brief Destructor
53  * \param dict the dictionnary to be freed
54  *
55  * Frees a dictionary with all the data
56  */
57 void xbt_dict_free(xbt_dict_t *dict) {
58   int i;
59   xbt_dictelm_t current, previous;
60   if (dict != NULL && *dict != NULL) {
61     for (i = 0; i < (*dict)->table_size; i++) {
62       current = (*dict)->table[i];
63       while (current != NULL) {
64         previous = current;
65         current = current->next;
66         xbt_dictelm_free(previous);
67       }
68     }
69     xbt_free((*dict)->table);
70     xbt_free(*dict);
71     *dict = NULL;
72   }
73 }
74
75 /**
76  * \brief Change the hashtable size
77  * \param dict a dictionary
78  * \param hashsize the new hashtable size
79  *
80  * Change the hashtable size is a long operation, so it's better
81  * to use xbt_dict_new_ext or to call xbt_dict_hashsize_set when
82  * the dictionary is empty. 
83  */
84 void xbt_dict_hashsize_set(xbt_dict_t dict, int hashsize) {
85   xbt_dict_t new_dict = xbt_dict_new_ext(hashsize);
86   xbt_dictelm_t element, next;
87   int i;
88
89   for (i = 0; i < dict->table_size; i++) {
90     element = dict->table[i];
91     while (element != NULL) {
92       next = element->next; /* save the next because it will be lost */
93       xbt_dict_add_element(new_dict, element); /* no new xbt_dictelm_t is mallocated */
94       element = next;
95     }
96   }
97
98   xbt_free(dict->table);
99   dict->table = new_dict->table;
100   dict->table_size = hashsize;
101   xbt_free(new_dict);
102 }
103
104 /**
105  * Returns the hash code of a string.
106  */
107 unsigned int xbt_dict_hash(const char *str) {
108   /* fast implementation of djb2 algorithm */
109   unsigned int hash = 5381;
110   int c;
111   
112   while ((c = *str++)) {
113     hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
114   }
115   
116   return hash;
117 }
118
119 /**
120  * \brief Add data to the dict (arbitrary key)
121  * \param dict the container
122  * \param key the key to set the new data
123  * \param key_len the size of the \a key
124  * \param data the data to add in the dict
125  * \param free_ctn function to call with (\a key as argument) when 
126  *        \a key is removed from the dictionnary
127  *
128  * Set the \a data in the structure under the \a key, which can be any kind 
129  * of data, as long as its length is provided in \a key_len.
130  */
131 void xbt_dict_set_ext(xbt_dict_t      dict,
132                       const char      *key,
133                       int              key_len,
134                       void            *data,
135                       void_f_pvoid_t  *free_ctn) {
136   xbt_assert(dict);
137
138   unsigned int hash_code = xbt_dict_hash(key) % dict->table_size;
139   xbt_dictelm_t current, previous = NULL;
140
141   current = dict->table[hash_code];
142   while (current != NULL &&
143          (key_len != current->key_len || strncmp(key, current->key, key_len))) {
144     previous = current;
145     current = current->next;
146   }
147
148   if (current == NULL) {
149     /* this key doesn't exist yet */
150     current = xbt_dictelm_new(key, key_len, data, free_ctn, NULL);
151     if (previous == NULL) {
152       dict->table[hash_code] = current;
153     }
154     else {
155       previous->next = current;
156     }
157   }
158   else {
159     /* there is already an element with the same key: we overwrite it */
160     if (current->content != NULL && current->free_f != NULL) {
161       current->free_f(current->content);
162     }
163     current->content = data;
164     current->free_f = free_ctn;
165   }
166 }
167
168 /**
169  * \brief Add data to the dict (null-terminated key)
170  *
171  * \param dict the head of the dict
172  * \param key the key to set the new data
173  * \param data the data to add in the dict
174  * \param free_ctn function to call with (\a key as argument) when 
175  *        \a key is removed from the dictionnary
176  *
177  * set the \a data in the structure under the \a key, which is a 
178  * null terminated string.
179  */
180 void xbt_dict_set(xbt_dict_t     dict,
181                   const char     *key,
182                   void           *data,
183                   void_f_pvoid_t *free_ctn) {
184
185   xbt_assert(dict);
186   
187   xbt_dict_set_ext(dict, key, strlen(key), data, free_ctn);
188 }
189
190 /**
191  * \brief Retrieve data from the dict (arbitrary key)
192  *
193  * \param dict the dealer of data
194  * \param key the key to find data
195  * \param key_len the size of the \a key
196  * \return the data that we are looking for
197  *
198  * Search the given \a key. Throws not_found_error when not found.
199  */
200 void *xbt_dict_get_ext(xbt_dict_t      dict,
201                        const char     *key,
202                        int             key_len) {
203   xbt_assert(dict);
204
205   unsigned int hash_code = xbt_dict_hash(key) % dict->table_size;
206   xbt_dictelm_t current;
207
208   current = dict->table[hash_code];
209   while (current != NULL &&
210          (key_len != current->key_len || strncmp(key, current->key, key_len))) {
211     current = current->next;
212   }
213
214   if (current == NULL) {
215     THROW2(not_found_error, 0, "key %.*s not found", key_len, key);
216   }
217
218   return current->content;
219 }
220
221 /**
222  * \brief Retrieve data from the dict (null-terminated key) 
223  *
224  * \param dict the dealer of data
225  * \param key the key to find data
226  * \return the data that we are looking for
227  *
228  * Search the given \a key. Throws not_found_error when not found. 
229  * Check xbt_dict_get_or_null() for a version returning NULL without exception when 
230  * not found.
231  */
232 void *xbt_dict_get(xbt_dict_t dict,
233                    const char *key) {
234   xbt_assert(dict);
235
236   return xbt_dict_get_ext(dict, key, strlen(key));
237 }
238
239 /**
240  * \brief like xbt_dict_get(), but returning NULL when not found
241  */
242 void *xbt_dict_get_or_null(xbt_dict_t     dict,
243                      const char     *key) {
244   xbt_ex_t e;
245   void *result = NULL;
246   TRY {
247     result = xbt_dict_get(dict, key);
248   } CATCH(e) {
249     if (e.category != not_found_error) 
250       RETHROW;
251     xbt_ex_free(e);
252     result = NULL;
253   }
254   return result;
255 }
256
257
258 /**
259  * \brief Remove data from the dict (arbitrary key)
260  *
261  * \param dict the trash can
262  * \param key the key of the data to be removed
263  * \param key_len the size of the \a key
264  *
265  * Remove the entry associated with the given \a key (throws not_found)
266  */
267 void xbt_dict_remove_ext(xbt_dict_t  dict,
268                          const char  *key,
269                          int          key_len) {
270   xbt_assert(dict);
271
272   unsigned int hash_code = xbt_dict_hash(key) % dict->table_size;
273   xbt_dictelm_t current, previous = NULL;
274
275   current = dict->table[hash_code];
276   while (current != NULL &&
277          (key_len != current->key_len || strncmp(key, current->key, key_len))) {
278     previous = current; /* save the previous node */
279     current = current->next;
280   }
281
282   if (current == NULL) {
283     THROW2(not_found_error, 0, "key %.*s not found", key_len, key);
284   }
285
286   if (previous != NULL) {
287     xbt_assert0(previous->next == current, "previous-next != current");
288     previous->next = current->next;
289   }
290   else {
291     dict->table[hash_code] = current->next;
292   }
293
294   xbt_dictelm_free(current);
295 }
296
297 /**
298  * \brief Remove data from the dict (null-terminated key)
299  *
300  * \param dict the head of the dict
301  * \param key the key of the data to be removed
302  *
303  * Remove the entry associated with the given \a key
304  */
305 void xbt_dict_remove(xbt_dict_t  dict,
306                      const char  *key) {
307   if (!dict)
308     THROW1(arg_error, 0, "Asked to remove key %s from NULL dict", key);
309
310   xbt_dict_remove_ext(dict, key, strlen(key));
311 }
312
313 /*
314  * Add an already mallocated element to a dictionary.
315  */
316 void xbt_dict_add_element(xbt_dict_t dict, xbt_dictelm_t element) {
317   xbt_assert(dict);
318
319   int hashcode = xbt_dict_hash(element->key) % dict->table_size;
320   element->next = dict->table[hashcode];
321   dict->table[hashcode] = element;
322 }
323
324 /**
325  * \brief Outputs the content of the structure (debuging purpose) 
326  *
327  * \param dict the exibitionist
328  * \param output a function to dump each data in the tree
329  *
330  * Ouputs the content of the structure. (for debuging purpose). \a ouput is a
331  * function to output the data. If NULL, data won't be displayed.
332  */
333
334 void xbt_dict_dump(xbt_dict_t     dict,
335                    void_f_pvoid_t *output) {
336   int i;
337   xbt_dictelm_t element;
338   printf("Dict %p:\n", dict);
339   if (dict != NULL) {
340     for (i = 0; i < dict->table_size; i++) {
341       element = dict->table[i];
342       while (element != NULL) {
343         printf("%s -> ", element->key);
344         if (output != NULL) {
345           output(element->content);
346         }
347         printf("\n");
348         element = element->next;
349       }
350     }
351   }
352 }
353
354 #ifdef SIMGRID_TEST
355 #include "xbt.h"
356 #include "xbt/ex.h"
357 #include "portable.h"
358
359 XBT_LOG_EXTERNAL_CATEGORY(xbt_dict);
360 XBT_LOG_DEFAULT_CATEGORY(xbt_dict);
361
362 XBT_TEST_SUITE("dict","Dict data container");
363
364 static void print_str(void *str) {
365   printf("%s",(char*)PRINTF_STR(str));
366 }
367
368 static void debuged_add(xbt_dict_t head,const char*key)
369 {
370   char *data=xbt_strdup(key);
371
372   xbt_test_log1("Add %s",PRINTF_STR(key));
373
374   xbt_dict_set(head,key,data,&free);
375   if (XBT_LOG_ISENABLED(xbt_dict,xbt_log_priority_debug)) {
376     xbt_dict_dump(head,(void (*)(void*))&printf);
377     fflush(stdout);
378   }
379 }
380
381 static void fill(xbt_dict_t *head) {
382   xbt_test_add0("Fill in the dictionnary");
383
384   *head = xbt_dict_new();
385   debuged_add(*head,"12");
386   debuged_add(*head,"12a");
387   debuged_add(*head,"12b");
388   debuged_add(*head,"123");
389   debuged_add(*head,"123456");
390   /* Child becomes child of what to add */
391   debuged_add(*head,"1234");
392   /* Need of common ancestor */
393   debuged_add(*head,"123457");
394 }
395
396 static void search(xbt_dict_t head,const char*key) {
397   void *data;
398   
399   xbt_test_add1("Search %s",key);
400   data=xbt_dict_get(head,key);
401   xbt_test_log1("Found %s",(char *)data);
402   if (data)
403     xbt_test_assert0(!strcmp((char*)data,key),"Key and data do not match");
404 }
405
406 static void debuged_remove(xbt_dict_t head,const char*key) {
407
408   xbt_test_add1("Remove '%s'",PRINTF_STR(key));
409   xbt_dict_remove(head,key);
410   /*  xbt_dict_dump(head,(void (*)(void*))&printf); */
411 }
412
413
414 static void traverse(xbt_dict_t head) {
415   xbt_dict_cursor_t cursor=NULL;
416   char *key;
417   char *data;
418
419   xbt_dict_foreach(head,cursor,key,data) {
420     xbt_test_log2("Seen:  %s->%s",PRINTF_STR(key),PRINTF_STR(data));
421     xbt_test_assert2(!data || !strcmp(key,data),
422                      "Key(%s) != value(%s). Abording\n",key,data);
423   }
424 }
425
426 static void search_not_found(xbt_dict_t head, const char *data) {
427   xbt_ex_t e;
428
429   xbt_test_add1("Search %s (expected not to be found)",data);
430
431   TRY {    
432     data = xbt_dict_get(head,"Can't be found");
433     THROW1(unknown_error,0,"Found something which shouldn't be there (%s)",data);
434   } CATCH(e) {
435     if (e.category != not_found_error) 
436       xbt_test_exception(e);
437     xbt_ex_free(e);
438   }
439 }
440
441 xbt_ex_t e;
442 xbt_dict_t head=NULL;
443 char *data;
444
445
446 XBT_TEST_UNIT("basic",test_dict_basic,"Basic usage: change, retrieve, traverse"){
447   xbt_test_add0("Traversal the empty dictionnary");
448   traverse(head);
449
450   xbt_test_add0("Traverse the full dictionnary");
451   fill(&head);
452
453   search(head,"12a");
454   traverse(head);
455
456   xbt_test_add0("Free the dictionnary (twice)");
457   xbt_dict_free(&head);
458   xbt_dict_free(&head);
459
460   /* CHANGING */
461   fill(&head);
462   xbt_test_add0("Change 123 to 'Changed 123'");
463   xbt_dict_set(head,"123",strdup("Changed 123"),&free);
464
465   xbt_test_add0("Change 123 back to '123'");
466   xbt_dict_set(head,"123",strdup("123"),&free);
467
468   xbt_test_add0("Change 12a to 'Dummy 12a'");
469   xbt_dict_set(head,"12a",strdup("Dummy 12a"),&free);
470
471   xbt_test_add0("Change 12a to '12a'");
472   xbt_dict_set(head,"12a",strdup("12a"),&free);
473
474   xbt_test_add0("Traverse the resulting dictionnary");
475   traverse(head);
476   
477   /* RETRIEVE */
478   xbt_test_add0("Search 123");
479   data = xbt_dict_get(head,"123");
480   xbt_test_assert(data);
481   xbt_test_assert(!strcmp("123",data));
482
483   search_not_found(head,"Can't be found");
484   search_not_found(head,"123 Can't be found");
485   search_not_found(head,"12345678 NOT");
486
487   search(head,"12a");
488   search(head,"12b");
489   search(head,"12");
490   search(head,"123456");
491   search(head,"1234");
492   search(head,"123457");
493
494   xbt_test_add0("Traverse the resulting dictionnary");
495   traverse(head);
496
497   /*  xbt_dict_dump(head,(void (*)(void*))&printf); */
498
499   xbt_test_add0("Free the dictionnary twice");
500   xbt_dict_free(&head);
501   xbt_dict_free(&head);
502
503   xbt_test_add0("Traverse the resulting dictionnary");
504   traverse(head);
505 }
506
507 XBT_TEST_UNIT("remove",test_dict_remove,"Removing some values"){
508   fill(&head);
509   xbt_test_add0("Remove non existing data");
510   TRY {
511     debuged_remove(head,"Does not exist");
512   } CATCH(e) {
513     if (e.category != not_found_error) 
514       xbt_test_exception(e);
515     xbt_ex_free(e);
516   }
517   traverse(head);
518
519   xbt_dict_free(&head);
520
521   xbt_test_add0("Remove data from the NULL dict");
522   TRY {
523     debuged_remove(head,"12345");
524   } CATCH(e) {
525     if (e.category != arg_error) 
526       xbt_test_exception(e);
527     xbt_ex_free(e);
528   } 
529
530   xbt_test_add0("Remove each data manually (traversing the resulting dictionnary each time)");
531   fill(&head);
532   debuged_remove(head,"12a");    traverse(head);
533   debuged_remove(head,"12b");    traverse(head);
534   debuged_remove(head,"12");     traverse(head);
535   debuged_remove(head,"123456"); traverse(head);
536   TRY {
537     debuged_remove(head,"12346");
538   } CATCH(e) {
539     if (e.category != not_found_error) 
540       xbt_test_exception(e);
541     xbt_ex_free(e);         
542     traverse(head);
543   } 
544   debuged_remove(head,"1234");   traverse(head);
545   debuged_remove(head,"123457"); traverse(head);
546   debuged_remove(head,"123");    traverse(head);
547   TRY {
548     debuged_remove(head,"12346");
549   } CATCH(e) {
550     if (e.category != not_found_error) 
551       xbt_test_exception(e);
552     xbt_ex_free(e);
553   }                              traverse(head);
554   
555   xbt_test_add0("Free the dictionnary twice");
556   xbt_dict_free(&head);
557   xbt_dict_free(&head);      
558 }
559
560 XBT_TEST_UNIT("nulldata",test_dict_nulldata,"NULL data management"){
561   fill(&head);
562
563   xbt_test_add0("Store NULL under 'null'");
564   xbt_dict_set(head,"null",NULL,NULL);
565   search(head,"null");
566
567   xbt_test_add0("Check whether I see it while traversing...");
568   {
569     xbt_dict_cursor_t cursor=NULL;
570     char *key;
571     int found=0;
572
573     xbt_dict_foreach(head,cursor,key,data) {
574       xbt_test_log2("Seen:  %s->%s",PRINTF_STR(key),PRINTF_STR(data));
575       if (!strcmp(key,"null"))
576         found = 1;
577     }
578     xbt_test_assert0(found,"the key 'null', associated to NULL is not found");
579   }
580   xbt_dict_free(&head);
581 }
582
583 #define NB_ELM 20000
584 #define SIZEOFKEY 1024
585 static int countelems(xbt_dict_t head) {
586   xbt_dict_cursor_t cursor;
587   char *key;
588   void *data;
589   int res = 0;
590
591   xbt_dict_foreach(head,cursor,key,data) {
592     res++;
593   }
594   return res;
595 }
596
597 XBT_TEST_UNIT("crash",test_dict_crash,"Crash test"){
598   xbt_dict_t head=NULL;
599   int i,j,k, nb;
600   char *key;
601   void *data;
602
603   srand((unsigned int)time(NULL));
604
605   xbt_test_add0("CRASH test");
606   xbt_test_log0("Fill the struct, count its elems and frees the structure (x10)");
607   xbt_test_log1("using 1000 elements with %d chars long randomized keys.",SIZEOFKEY);
608
609   for (i=0;i<10;i++) {
610     head=xbt_dict_new();
611     //    if (i%10) printf("."); else printf("%d",i/10); fflush(stdout);
612     nb=0;
613     for (j=0;j<1000;j++) {
614       key=xbt_malloc(SIZEOFKEY);
615
616       for (k=0;k<SIZEOFKEY-1;k++)
617         key[k]=rand() % ('z' - 'a') + 'a';
618       key[k]='\0';
619       /*      printf("[%d %s]\n",j,key); */
620       xbt_dict_set(head,key,key,&free);
621     }
622     /*    xbt_dict_dump(head,(void (*)(void*))&printf); */
623     nb = countelems(head);
624     xbt_test_assert1(nb == 1000,"found %d elements instead of 1000",nb);
625     traverse(head);
626     xbt_dict_free(&head);
627     xbt_dict_free(&head);
628   }
629
630
631   head=xbt_dict_new();
632   xbt_test_add1("Fill %d elements, with keys being the number of element",NB_ELM);
633   for (j=0;j<NB_ELM;j++) {
634     //    if (!(j%1000)) { printf("."); fflush(stdout); }
635
636     key = xbt_malloc(10);
637     
638     sprintf(key,"%d",j);
639     xbt_dict_set(head,key,key,&free);
640   }
641
642   xbt_test_add0("Count the elements (retrieving the key and data for each)");
643   i = countelems(head);
644   xbt_test_log1("There is %d elements",i);
645
646   xbt_test_add1("Search my %d elements 20 times",NB_ELM);
647   key=xbt_malloc(10);
648   for (i=0;i<20;i++) {
649     //    if (i%10) printf("."); else printf("%d",i/10); fflush(stdout);
650     for (j=0;j<NB_ELM;j++) {
651       
652       sprintf(key,"%d",j);
653       data = xbt_dict_get(head,key);
654       xbt_test_assert2(!strcmp(key,(char*)data),
655                        "key=%s != data=%s\n",key,(char*)data);
656     }
657   }
658   free(key);
659
660   xbt_test_add1("Remove my %d elements",NB_ELM);
661   key=xbt_malloc(10);
662   for (j=0;j<NB_ELM;j++) {
663     //if (!(j%10000)) printf("."); fflush(stdout);
664     
665     sprintf(key,"%d",j);
666     xbt_dict_remove(head,key);
667   }
668   free(key);
669
670   
671   xbt_test_add0("Free the structure (twice)");
672   xbt_dict_free(&head);
673   xbt_dict_free(&head);
674 }
675
676 static void str_free(void *s) {
677   char *c=*(char**)s;
678   free(c);
679 }
680
681 XBT_TEST_UNIT("multicrash",test_dict_multicrash,"Multi-dict crash test"){
682
683 #undef NB_ELM
684 #define NB_ELM 100 /*00*/
685 #define DEPTH 5
686 #define KEY_SIZE 512
687 #define NB_TEST 20 /*20*/
688 int verbose=0;
689
690   xbt_dict_t mdict = NULL;
691   int i,j,k,l;
692   xbt_dynar_t keys = xbt_dynar_new(sizeof(char*),str_free);
693   void *data;
694   char *key;
695
696
697   xbt_test_add0("Generic multicache CRASH test");
698   xbt_test_log4(" Fill the struct and frees it %d times, using %d elements, "
699                 "depth of multicache=%d, key size=%d",
700                 NB_TEST,NB_ELM,DEPTH,KEY_SIZE);
701
702   for (l=0 ; l<DEPTH ; l++) {
703     key=xbt_malloc(KEY_SIZE);
704     xbt_dynar_push(keys,&key);
705   }     
706
707   for (i=0;i<NB_TEST;i++) {
708     mdict = xbt_dict_new();
709     VERB1("mdict=%p",mdict);
710     if (verbose>0)
711       printf("Test %d\n",i);
712     /* else if (i%10) printf("."); else printf("%d",i/10);*/
713     
714     for (j=0;j<NB_ELM;j++) {
715       if (verbose>0) printf ("  Add {");
716       
717       for (l=0 ; l<DEPTH ; l++) {
718         key=*(char**)xbt_dynar_get_ptr(keys,l);
719         
720         for (k=0;k<KEY_SIZE-1;k++) 
721           key[k]=rand() % ('z' - 'a') + 'a';
722           
723         key[k]='\0';
724         
725         if (verbose>0) printf("%p=%s %s ",key, key,(l<DEPTH-1?";":"}"));
726       }
727       if (verbose>0) printf("in multitree %p.\n",mdict);
728                                                         
729       xbt_multidict_set(mdict,keys,xbt_strdup(key),free);
730
731       data = xbt_multidict_get(mdict,keys);
732
733       xbt_test_assert2(data && !strcmp((char*)data,key),
734                        "Retrieved value (%s) does not match the entrered one (%s)\n",
735                        (char*)data,key);
736     }
737     xbt_dict_free(&mdict);
738   }
739   
740   xbt_dynar_free(&keys);
741
742 /*  if (verbose>0)
743     xbt_dict_dump(mdict,&xbt_dict_print);*/
744     
745   xbt_dict_free(&mdict);
746   xbt_dynar_free(&keys);
747
748 }
749 #endif /* SIMGRID_TEST */