forked from mihai-negru/c-language-data-structures
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscl_hash_table.c
2149 lines (1735 loc) · 70.6 KB
/
scl_hash_table.c
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
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @file scl_hash_table.c
* @author Mihai Negru (determinant289@gmail.com)
* @version 1.0.0
* @date 2022-06-21
*
* @copyright Copyright (C) 2022-2023 Mihai Negru <determinant289@gmail.com>
* This file is part of C-language-Data-Structures.
*
* C-language-Data-Structures is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* C-language-Data-Structures is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with C-language-Data-Structures. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "./include/scl_hash_table.h"
#include "./include/scl_queue.h"
#define DEFAULT_HASH_CAPACITY 50
#define DEFAULT_HASH_LOAD_FACTOR 0.75
#define DEFAULT_HASH_CAPACITY_RATIO 2
/**
* @brief Create a hash table object. Allocation may fail if there is not enough
* memory on heap, compare or hash functions are not valid.
*
* @param init_capacity initial capacity for the hash table (should be >= |All data| * 0.75)
* @param hash pointer to a function to hash the key into a size_t type (should not apply modulo)
* @param cmp_key pointer to a function to compare two sets of key
* @param cmp_dt pointer to a function to compare two sets of data
* @param frd_key pointer to a function to free memory allocated for the CONTENT of the key pointer
* @param frd_dt pointer to a function to free memory allocated for the CONTENT of the data pointer
* @param key_size length in bytes of the key data type
* @param data_size length in bytes of the data data type
* @return hash_table_t* a new allocated hash table object or `NULL` (if function fails)
*/
hash_table_t* create_hash_table(size_t init_capacity, hash_func hash, compare_func cmp_key, compare_func cmp_dt, free_func frd_key, free_func frd_dt, size_t key_size, size_t data_size) {
/* Check if hash function and compare function are valid */
if ((NULL == hash) || (NULL == cmp_key) || (NULL == cmp_dt)) {
errno = EINVAL;
perror("Compare or hash functions undefined in hash_table");
return NULL;
}
/* Check if data and key sizes are valid */
if ((0 == key_size) || (0 == data_size)) {
errno = EINVAL;
perror("Key or data size are zero");
return NULL;
}
/* Check if initial capacity is valid if not set it as default value */
if (10 >= init_capacity) {
init_capacity = DEFAULT_HASH_CAPACITY;
}
/* Allocate a new hash table object on heap */
hash_table_t *new_hash_table = malloc(sizeof(*new_hash_table));
/* Check if hash table was allocated successfully */
if (NULL != new_hash_table) {
/* Set function pointers of the hash table */
new_hash_table->hash = hash;
new_hash_table->cmp_key = cmp_key;
new_hash_table->cmp_dt = cmp_dt;
new_hash_table->frd_key = frd_key;
new_hash_table->frd_dt = frd_dt;
/* Set capacity and default size of the hash table */
new_hash_table->key_size = key_size;
new_hash_table->data_size = data_size;
new_hash_table->capacity = init_capacity;
new_hash_table->size = 0;
/* Create the black hole node */
new_hash_table->nil = malloc(sizeof(*new_hash_table->nil));
/* Check if black hole node was created */
if (NULL != new_hash_table->nil) {
/* Set functionality for black hole node */
new_hash_table->nil->key = NULL;
new_hash_table->nil->data = NULL;
new_hash_table->nil->color = HASH_BLACK;
new_hash_table->nil->count = 1;
new_hash_table->nil->left = new_hash_table->nil->right = new_hash_table->nil;
new_hash_table->nil->parent = new_hash_table->nil;
/* Allocate all buckets from hash table */
new_hash_table->buckets = malloc(sizeof(*new_hash_table->buckets) * init_capacity);
/* Check if buckets were allocated successfully */
if (NULL != new_hash_table->buckets) {
/* Set every bucket to point to black hole node */
for (size_t iter = 0; iter < init_capacity; ++iter) {
new_hash_table->buckets[iter] = new_hash_table->nil;
}
} else {
/* Trees were not allocated wipe hash table's memory */
free(new_hash_table->nil);
free(new_hash_table);
new_hash_table = NULL;
errno = ENOMEM;
perror("Not enough memory for buckets of hash table");
}
} else {
/* Black hole node was not allocated wipe hash table's mempry */
free(new_hash_table);
new_hash_table = NULL;
errno = ENOMEM;
perror("Not enough memory for nil hash table allocation");
}
} else {
/* Hash table was not allocated return `NULL` */
errno = ENOMEM;
perror("Not enough memory for hash table allocation");
}
/* Return an allocated hash table or `NULL` */
return new_hash_table;
}
/**
* @brief Function to delete memory of selected node of the
* hash table from heap zone
*
* @param ht pointer to an allocated hash table memory location
* @param free_node address of a pointer to a memory location of a hash table node to delete
*/
static void free_hash_table_node(const hash_table_t * const __restrict__ ht, hash_table_node_t ** const __restrict__ free_node) {
/* Check if input data is valid */
if ((NULL != ht) && (NULL != free_node) && (ht->nil != *free_node)) {
/* Check if content of the data node was allocated dynamically */
if ((NULL != ht->frd_dt) && (NULL != (*free_node)->data)) {
ht->frd_dt((*free_node)->data);
}
/* Free data pointer of the node */
if (NULL != (*free_node)->data) {
free((*free_node)->data);
}
/* Point to default value */
(*free_node)->data = NULL;
/* Check if content of the data node was allocated dynamically */
if ((NULL != ht->frd_key) && (NULL != (*free_node)->key)) {
ht->frd_key((*free_node)->key);
}
/* Free key pointer of the node */
if (NULL != (*free_node)->key) {
free((*free_node)->key);
}
/* Point to default value */
(*free_node)->key = NULL;
/* Free hash table node from heap */
if (ht->nil != *free_node) {
free(*free_node);
}
/* Point to default value */
*free_node = ht->nil;
}
}
/**
* @brief Helper function to delete all nodes from a
* hash table bucket.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket address of a pointer to a memory location of a hash table node to delete
*/
static void free_hash_table_helper(const hash_table_t * const __restrict__ ht, hash_table_node_t ** const __restrict__ bucket) {
/* Check if node can be freed */
if ((NULL == bucket) || (ht->nil == *bucket)) {
return;
}
/* Free left node child */
free_hash_table_helper(ht, &(*bucket)->left);
/* Free right node child */
free_hash_table_helper(ht, &(*bucket)->right);
/* Free current working node */
free_hash_table_node(ht, bucket);
}
/**
* @brief Function to delete all memory allocated for one hash table.
* Function will not automatically move hash table pointer to `NULL`.
*
* @param ht pointer to an allocated hash table memory location
* @return scl_error_t enum object for handling errors
*/
scl_error_t free_hash_table(hash_table_t * const __restrict__ ht) {
/* Check if hash table can be freed */
if (NULL != ht) {
/* Check if hash table roots are allocated */
if (NULL != ht->buckets) {
/* Free every tree from the hash table */
for (size_t iter = 0; iter < ht->capacity; ++iter) {
free_hash_table_helper(ht, &ht->buckets[iter]);
}
/* Free memory for the black hole node */
free(ht->nil);
ht->nil = NULL;
/* Free memory for roots array */
free(ht->buckets);
ht->buckets = NULL;
}
/* Free memory of the hash table */
free(ht);
/* All good, go sleep */
return SCL_OK;
}
/* `NULL` hash table sent to delete */
return SCL_NULL_HASH_TABLE;
}
/**
* @brief Create a hash table node object. Allocation may fail if address of data
* or key pointers are `NULL` or if not enough memory is left on the heap zone.
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of the hash
* @param data pointer to a location of a value representing data of a node
* @return hash_table_node_t* a new allocated hash table node object or `nil`
*/
static hash_table_node_t* create_hash_table_node(const hash_table_t * const __restrict__ ht, const void *key, const void *data) {
/* Check if data and key pointer are not `NULL` */
if ((NULL == data) || (NULL == key)) {
return ht->nil;
}
/* Allocate a new hash table node */
hash_table_node_t *new_node = malloc(sizeof(*new_node));
/* Check if node was allocated successfully */
if (NULL != new_node) {
/* Set default values of one node */
new_node->left = new_node->right = ht->nil;
new_node->parent = ht->nil;
new_node->count = 1;
new_node->color = HASH_RED;
/* Allocate memory for data value */
new_node->data = malloc(ht->data_size);
/* Check if data memory was allocated */
if (NULL != new_node->data) {
/*
* Copy all bytes from data pointer
* to memory allocated on heap
*/
memcpy(new_node->data, data, ht->data_size);
} else {
/* Data memory was not allocated wipe node's memory */
free(new_node);
new_node = ht->nil;
errno = ENOMEM;
perror("Not enough memory for node hash table data allocation");
}
/* Allocate memory for key value */
new_node->key = malloc(ht->key_size);
/* Check if key memory was allocated */
if (NULL != new_node->key) {
/*
* Copy all bytes from key pointer
* to memory allocated on heap
*/
memcpy(new_node->key, key, ht->key_size);
} else {
/* Key memory was not allocated wipe node's memory */
free(new_node->data);
new_node->data = NULL;
free(new_node);
new_node = ht->nil;
}
} else {
/* Node memory was not allocated, set default value */
new_node = ht->nil;
errno = ENOMEM;
perror("Not enough memory for node hash table allocation");
}
/* Return an allocated hash table node object or `nil` */
return new_node;
}
/**
* @brief Function to check if hash table needs to be rehashed.
* Function will check if hash table's load factor is greater than
* 0.75.
*
* @param ht pointer to an allocated hash table memory location
* @return uint8_t 0 if hash tables does not need to be rehashed or
* 1 otherwise
*/
static uint8_t hash_table_need_to_rehash(const hash_table_t * const __restrict__ ht) {
/* Check if hash table is allocated */
if (NULL == ht) {
return 0;
}
/* Check if current load factor is less or greater than 0.75 */
return ((1.0 * ht->size) / ht->capacity > DEFAULT_HASH_LOAD_FACTOR);
}
/**
* @brief Function to rotate to left a bucket node starting
* from fix_node hash table node object. Function may fail
* if hash table object is not allocated or hash table node
* object is nil.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket_index index of the working bucket from hash table
* @param fix_node pointer to a hash table node to rotate
*/
static void hash_table_rotate_left(const hash_table_t * const __restrict__ ht, size_t bucket_index, hash_table_node_t * const fix_node) {
/* Check if input data is valid */
if ((NULL == ht) || (NULL == ht->buckets) || (ht->nil == fix_node)) {
return;
}
/* Check if rotation may happen */
if (ht->nil == fix_node->right) {
return;
}
/* Set new rotated sub-root */
hash_table_node_t * const rotate_node = fix_node->right;
/* Update child of fix_node */
fix_node->right = rotate_node->left;
/* Update child parent to fix_node */
if (ht->nil != rotate_node->left) {
rotate_node->left->parent = fix_node;
}
/* Rotation to left */
rotate_node->left = fix_node;
/* Update new sub-root parent */
rotate_node->parent = fix_node->parent;
/* Update fix_node parent to new sub-root */
fix_node->parent = rotate_node;
/* Update new sub-root links to the rest of the tree */
if (ht->nil != rotate_node->parent) {
if (ht->cmp_key(rotate_node->key, rotate_node->parent->key) >= 1) {
rotate_node->parent->right = rotate_node;
} else {
rotate_node->parent->left = rotate_node;
}
} else {
ht->buckets[bucket_index] = rotate_node;
}
}
/**
* @brief Function to rotate to right a bucket node starting
* from fix_node hash table node object. Function may fail
* if hash table object is not allocated or hash table node
* object is nil.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket_index index of the working bucket from hash table
* @param fix_node pointer to a hash table node to rotate
*/
static void hash_table_rotate_right(const hash_table_t * const __restrict__ ht, size_t bucket_index, hash_table_node_t * const fix_node) {
/* Check if input data is valid */
if ((NULL == ht) || (NULL == ht->buckets) || (ht->nil == fix_node)) {
return;
}
/* Check if rotation may happen */
if (ht->nil == fix_node->left) {
return;
}
/* Set new rotated sub-root */
hash_table_node_t * const rotate_node = fix_node->left;
/* Update child of fix_node */
fix_node->left = rotate_node->right;
/* Update child parent to fix_node */
if (ht->nil != rotate_node->right) {
rotate_node->right->parent = fix_node;
}
/* Rotation to right */
rotate_node->right = fix_node;
/* Update new sub-root parent */
rotate_node->parent = fix_node->parent;
/* Update fix_node parent to new sub-root */
fix_node->parent = rotate_node;
/* Update new sub-root links to the rest of tree */
if (ht->nil != rotate_node->parent) {
if (ht->cmp_key(rotate_node->key, rotate_node->parent->key) >= 1) {
rotate_node->parent->right = rotate_node;
} else {
rotate_node->parent->left = rotate_node;
}
} else {
ht->buckets[bucket_index] = rotate_node;
}
}
/**
* @brief Helper function to fix up the balance of a bucket represented as
* a red black binary tree from a hash table after insertion of one node.
* Function may fail if current working bucket and node are `nil`.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket_index index of the working red black tree from hash table
* @param fix_node pointer to a hash table node to start fixing the balance
* @return scl_error_t enum object for handling errors
*/
static scl_error_t hash_table_insert_fix_node_up(const hash_table_t * const __restrict__ ht, size_t bucket_index, hash_table_node_t *fix_node) {
/* Check if hash table is allocated */
if (NULL == ht) {
return SCL_NULL_HASH_TABLE;
}
/* Check if hash table roots are allocated */
if (NULL == ht->buckets) {
return SCL_NULL_HASH_ROOTS;
}
/* Check if fixing node is not `nil` */
if (ht->nil == fix_node) {
return SCL_FIXING_NULL_TREE_NODE;
}
/* Set parent node pointer as default value */
hash_table_node_t *parent_fix_node = ht->nil;
/* Fix up the bucket from hash table */
while ((ht->buckets[bucket_index] != fix_node) && (HASH_BLACK != fix_node->color) && (HASH_BLACK != fix_node->parent->color)) {
/* Selected node is not root so check brother color */
/* Set initial data */
parent_fix_node = fix_node->parent;
hash_table_node_t *brother_node = ht->nil;
/* Find brother node */
if (parent_fix_node->parent->left == parent_fix_node) {
brother_node = parent_fix_node->parent->right;
} else {
brother_node = parent_fix_node->parent->left;
}
/* Fix tree according to brother's color */
if (HASH_BLACK == brother_node->color) {
/* Brother's color is black check what rotations we should make */
if (parent_fix_node->left == fix_node) {
if (parent_fix_node->parent->left == parent_fix_node) {
/* Left-Left rotation case*/
/* Recolouring nodes */
parent_fix_node->color = HASH_BLACK;
parent_fix_node->parent->color = HASH_RED;
/* Rotation */
hash_table_rotate_right(ht, bucket_index, parent_fix_node->parent);
/* Repoint selected node*/
fix_node = parent_fix_node;
} else {
/* Right-Left Rotation */
/* Recolouring nodes */
fix_node->color = HASH_BLACK;
parent_fix_node->parent->color = HASH_RED;
/* Rotation */
hash_table_rotate_right(ht, bucket_index, parent_fix_node);
hash_table_rotate_left(ht, bucket_index, fix_node->parent);
}
} else {
if (parent_fix_node->parent->left == parent_fix_node) {
/* Left-Right Rotation */
/* Recolouring nodes */
fix_node->color = HASH_BLACK;
parent_fix_node->parent->color = HASH_RED;
/* Rotation */
hash_table_rotate_left(ht, bucket_index, parent_fix_node);
hash_table_rotate_right(ht, bucket_index, fix_node->parent);
} else {
/* Right-Right Rotation */
/* Recolouring nodes */
parent_fix_node->color = HASH_BLACK;
parent_fix_node->parent->color = HASH_RED;
/* Rotation */
hash_table_rotate_left(ht, bucket_index, parent_fix_node->parent);
/* Repoint selected node */
fix_node = parent_fix_node;
}
}
} else if (HASH_RED == brother_node->color) {
/* Brother's color is red so recolor the nodes */
parent_fix_node->parent->color = HASH_RED;
brother_node->color = HASH_BLACK;
parent_fix_node->color = HASH_BLACK;
/* Repoint selected node */
fix_node = parent_fix_node->parent;
} else {
/* Color is not RED or BLACK something went wrong */
return SCL_UNKNOWN_HASH_NODE_COLOR;
}
}
/* Make sure root is black */
ht->buckets[bucket_index]->color = HASH_BLACK;
/* All good */
return SCL_OK;
}
/**
* @brief Function to rehash a hash table and to double the capacity
* of the table (double number of bucket). Function may fail
* if hash table is not allocated or has `NULL` buckets or not enough
* memory is left on heap to allocate new red black trees.
*
* @param ht pointer to an allocated hash table memory location
* @return scl_error_t enum object for handling errors
*/
static scl_error_t hash_table_rehash(hash_table_t * const __restrict__ ht);
/**
* @brief
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of the hash
* @param data pointer to a location of a value representing data of a node
* @return scl_error_t enum object for handling errors
*/
scl_error_t hash_table_insert(hash_table_t * const __restrict__ ht, const void *key, const void *data) {
/* Check if hash table is allocated */
if (NULL == ht) {
return SCL_NULL_HASH_TABLE;
}
/* Check if hash table roots are allocated */
if (NULL == ht->buckets) {
return SCL_NULL_HASH_ROOTS;
}
/* Check if key pointer is not `NULL` */
if (NULL == key) {
return SCL_INVALID_KEY;
}
/* Check if data pointer is not `NULL` */
if (NULL == data) {
return SCL_INVALID_DATA;
}
/* Compute index of the current working tree */
size_t bucket_index = ht->hash(key) % ht->capacity;
/* Set iterator pointers */
hash_table_node_t *iterator = ht->buckets[bucket_index];
hash_table_node_t *parent_iterator = ht->nil;
/* Find a valid position for insertion */
while (ht->nil != iterator) {
parent_iterator = iterator;
if (ht->cmp_key(iterator->key, key) >= 1) {
iterator = iterator->left;
} else if (ht->cmp_key(iterator->key, key) <= -1) {
iterator = iterator->right;
} else {
/*
* Node already exists in current red-black tree
* increment count value of node
*/
++(iterator->count);
return 0;
}
}
/* Create a new bucket node object */
hash_table_node_t *new_node = create_hash_table_node(ht, key, data);
/* Check if new bucket(hash table) node was created */
if (ht->nil == new_node) {
return SCL_NOT_ENOUGHT_MEM_FOR_NODE;
}
scl_error_t err = SCL_OK;
if (ht->nil != parent_iterator) {
/* Update parent links */
new_node->parent = parent_iterator;
/* Update children links */
if (ht->cmp_key(parent_iterator->key, new_node->key) >= 1) {
parent_iterator->left = new_node;
} else {
parent_iterator->right = new_node;
}
/* Fix the bucket */
err = hash_table_insert_fix_node_up(ht, bucket_index, new_node);
} else {
/* Created node is root node */
ht->buckets[bucket_index] = new_node;
new_node->color = HASH_BLACK;
}
/* Increase hash table size */
++(ht->size);
/* Check if has table needs to be rehashed */
if (1 == hash_table_need_to_rehash(ht)) {
return hash_table_rehash(ht);
}
/* Insertion in hash table went successfully, or not */
return err;
}
/**
* @brief Subroutine function of hash_table rehash, to traverse all
* nodes from a bucket as in a red black tree and to insert all nodes
* into the new buckets.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket pointer to current working hash table(red black tree) node to re-insert
*/
static void hash_table_rehash_helper(hash_table_t * const __restrict__ ht, const hash_table_node_t * const __restrict__ bucket) {
/* Check if current root can be inserted */
if (ht->nil == bucket) {
return;
}
/* Reinsert current node into the hash table */
hash_table_insert(ht, bucket->key, bucket->data);
/* Reinsert left node child */
hash_table_rehash_helper(ht, bucket->left);
/* Reinsert right node child */
hash_table_rehash_helper(ht, bucket->right);
}
/**
* @brief Function to rehash a hash table and to double the capacity
* of the table (double number of red black trees). Function may fail
* if hash table is not allocated or has NULL trees or not enough
* memory is left on heap to allocate new red black trees.
*
* @param ht pointer to an allocated hash table memory location
* @return scl_error_t enum object for handling errors
*/
static scl_error_t hash_table_rehash(hash_table_t * const __restrict__ ht) {
/* Check if hash table is allocated */
if (NULL == ht) {
return SCL_NULL_HASH_TABLE;
}
/* Check if hash table roots are allocated */
if (NULL == ht->buckets) {
return SCL_NULL_HASH_ROOTS;
}
/* Check if hash function is valid */
if (NULL == ht->hash) {
return SCL_NULL_HASH_FUNCTION;
}
/* Remember old capacity and compute new capacity */
size_t old_capacity = ht->capacity;
ht->capacity *= DEFAULT_HASH_CAPACITY_RATIO;
/* Allocate new array of tree roots */
hash_table_node_t **new_buckets = malloc(sizeof(*new_buckets) * ht->capacity);
/* Check if roots were allocated */
if (NULL == new_buckets) {
ht->capacity = old_capacity;
errno = ENOMEM;
perror("Not enough memory for buckets of hash table");
return SCL_REHASHING_FAILED;
}
/* Change old roots pointer to new roots pointer */
hash_table_node_t **old_buckets = ht->buckets;
ht->buckets = new_buckets;
/* Set all buckets to black hole node */
for (size_t iter = 0; iter < ht->capacity; ++iter) {
new_buckets[iter] = ht->nil;
}
/* Set size of the hash table as default */
ht->size = 0;
/*
* Insert one bucket at a time in the new allocated trees
* and delete one bucket at a time from old roots pointer
*/
for (size_t iter = 0; iter < old_capacity; ++iter) {
if (ht->nil != old_buckets[iter]) {
hash_table_rehash_helper(ht, old_buckets[iter]);
free_hash_table_helper(ht, &old_buckets[iter]);
}
}
/* Free pointer of the old roots pointer */
free(old_buckets);
old_buckets = NULL;
/* All good */
return SCL_OK;
}
/**
* @brief Subroutine function to search one node having as node value
* current key specified in input. If no key is found function will
* return a pointer to the black hole node.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket_index index of the working red black tree from hash table
* @param data pointer to a location of a value representing data of a node
* @return hash_table_node_t* an allocated hash table node object containing
* desired data or `nil` id such data does not exists in the current tree
*/
static hash_table_node_t* hash_table_find_node(const hash_table_t * const __restrict__ ht, const void * const __restrict__ key) {
/* Check if input data is valid */
if ((NULL == ht) || (NULL == ht->buckets) || (NULL == key)) {
return ht->nil;
}
/* Make sure that current working tree exists */
size_t bucket_index = ht->hash(key) % ht->capacity;
/* Set iterator pointer */
hash_table_node_t *iterator = ht->buckets[bucket_index];
/* Search for input data (void *data) in all tree */
while (ht->nil != iterator) {
if (ht->cmp_key(iterator->key, key) <= -1) {
iterator = iterator->right;
} else if (ht->cmp_key(iterator->key, key) >= 1) {
iterator = iterator->left;
} else {
return iterator;
}
}
/* Data was not found */
return ht->nil;
}
/**
* @brief Function to find the pair {key, data} from hash table.
* However function will return a pointer to memory location just
* for data type not for key type.
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of the hash
* @param data pointer to a location of a value representing data of a node
* @return const void* pointer to memory location of the data pointer from the input
* or `NULL` is no such data exists in the hash tree
*/
const void* hash_table_find_key_data(const hash_table_t * const __restrict__ ht, const void * const key, const void * const data) {
/* Check if input data is valid */
if ((NULL == ht) || (NULL == ht->buckets) || (NULL == key) || (NULL == data)) {
return NULL;
}
/* Get the node data or `NULL` if node is `nil` */
const void * const search_data = hash_table_find_node(ht, key)->data;
if (ht->cmp_dt(search_data, data) == 0) {
return search_data;
}
return NULL;
}
/**
* @brief Function to find first occurence of the key node
* from hash table. However function will return a pointer to memory
* location just for data type.
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of a node
* @return const void* pointer to memory location of the data pointer
* or `NULL` is no such key exists in the hash tree
*/
const void* hash_table_find_data(const hash_table_t * const __restrict__ ht, const void * const __restrict__ key) {
/* Check if input data is valid */
if ((NULL == ht) || (NULL == ht->buckets) || (NULL == key)) {
return NULL;
}
/* Key was not found it means no data */
return hash_table_find_node(ht, key)->data;
}
/**
* @brief Function to check if hash table contains the {key, data} pair.
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of the hash
* @param data pointer to a location of a value representing data of a node
* @return uint8_t 0 if pair {key, data} is not found in hash table or 1 otherwise
*/
uint8_t hash_table_contains_key_data(const hash_table_t * const __restrict__ ht, const void * const key, const void * const data) {
/* Pair is not in the current hash table */
if (NULL == hash_table_find_key_data(ht, key, data)) {
return 0;
}
/* Pair is in the current hash table */
return 1;
}
/**
* @brief Function to check if hash table is empty or not.
*
* @param ht pointer to an allocated hash table memory location
* @return uint8_t 0 if hash table is not empty or 1 otherwise
*/
uint8_t is_hash_table_empty(const hash_table_t * const __restrict__ ht) {
/* Hash table is empty */
if ((NULL == ht) || (NULL == ht->buckets) || (0 == ht->capacity) || (0 == ht->size)) {
return 1;
}
/* Hash table is not empty */
return 0;
}
/**
* @brief Function to check if hash table bucket according to key
* type value is empty or not.
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of the hash
* @return uint8_t 0 if hash table bucket is not empty or 1 otherwise
*/
uint8_t is_hash_table_bucket_key_empty(const hash_table_t * const __restrict__ ht, const void * const __restrict__ key) {
/* Compute the curent bucket index */
size_t bucket_index = ht->hash(key) % ht->capacity;
/* Hast table bucket is empty */
if ((NULL == ht) || (NULL == ht->buckets) || (0 == ht->capacity) ||
(0 == ht->size) || (ht->nil == ht->buckets[bucket_index])) {
return 1;
}
/* Hash table bucket is not empty */
return 0;
}
/**
* @brief Get the current hash table size.
*
* @param ht pointer to an allocated hash table memory location
* @return size_t SIZE_MAX if hash table is not allocated or
* hash table's size.
*/
size_t get_hash_table_size(const hash_table_t * const __restrict__ ht) {
if (NULL == ht) {
return SIZE_MAX;
}
return ht->size;
}
/**
* @brief Get the current hash table capacity.
*
* @param ht pointer to an allocated hash table memory location
* @return size_t SIZE_MAX if hash table is not allocated or
* hash table's capacity.
*/
size_t get_hash_table_capacity(const hash_table_t * const __restrict__ ht) {
if (NULL == ht) {
return SIZE_MAX;
}
return ht->capacity;
}
/**
* @brief Helper function to compute the size of one bucket
* according to key type value.
*
* @param ht pointer to an allocated hash table memory location
* @param bucket pointer to current hash table node to start counting size
* @return size_t size of one bucket(red black tree) from hash table
*/
static size_t hash_table_count_bucket_elements_helper(const hash_table_t * const __restrict__ ht, const hash_table_node_t * const __restrict__ bucket) {
/* Check if node can be counted */
if (ht->nil == bucket) {
return 0;
}
/* Count current node */
size_t total_nodes = 1;
/* Count nodes from left subtree */
total_nodes += hash_table_count_bucket_elements_helper(ht, bucket->left);
/* Count nodes from right subtree */
total_nodes += hash_table_count_bucket_elements_helper(ht, bucket->right);
/* Return total count of the all tree(bucket) */
return total_nodes;
}
/**
* @brief Function to compute the size of one bucket according to key type
* value. Bucket index is computed with hashing function of the current
* hast table.
*
* @param ht pointer to an allocated hash table memory location
* @param key pointer to a location of a value representing key of the hash
* @return size_t SIZE_MAX if hash table is not valid or size of desired bucket
*/
size_t hash_table_count_bucket_elements(const hash_table_t * const __restrict__ ht, const void * const __restrict__ key) {
/* Check if input data is valid */
if ((NULL == ht) || (NULL == ht->buckets)) {
return SIZE_MAX;
}
/* Compute the bucket index */
size_t bucket_index = ht->hash(key) % ht->capacity;