#ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "Ecore_Data.h" #define PRIME_TABLE_MAX 21 #define PRIME_MIN 17 #define PRIME_MAX 16777213 #define ECORE_HASH_CHAIN_MAX 3 #define ECORE_COMPUTE_HASH(hash, key) hash->hash_func(key) % \ ecore_prime_table[hash->size]; #define ECORE_HASH_INCREASE(hash) ((hash && ecore_prime_table[hash->size] < \ PRIME_MAX) ? \ (hash->nodes / \ ecore_prime_table[hash->size]) > \ ECORE_HASH_CHAIN_MAX : FALSE) #define ECORE_HASH_REDUCE(hash) ((hash && ecore_prime_table[hash->size] > \ PRIME_MIN) ? \ (double)hash->nodes / \ (double)ecore_prime_table[hash->size - 1] \ < ((double)ECORE_HASH_CHAIN_MAX * \ 0.375) : FALSE) static const unsigned int ecore_prime_table[] = { 17, 31, 61, 127, 257, 509, 1021, 2053, 4093, 8191, 16381, 32771, 65537, 131071, 262147, 524287, 1048573, 2097143, 4194301, 8388617, 16777213 }; /* Private hash manipulation functions */ static int _ecore_hash_node_add(Ecore_Hash *hash, Ecore_Hash_Node *node); static Ecore_Hash_Node * _ecore_hash_node_get(Ecore_Hash *hash, const void *key); static int _ecore_hash_increase(Ecore_Hash *hash); static int _ecore_hash_decrease(Ecore_Hash *hash); static inline int _ecore_hash_rehash(Ecore_Hash *hash, Ecore_Hash_Node **old_table, int old_size); static int _ecore_hash_bucket_destroy(Ecore_Hash_Node *list, Ecore_Free_Cb keyd, Ecore_Free_Cb valued); static inline Ecore_Hash_Node *_ecore_hash_bucket_get(Ecore_Hash *hash, Ecore_Hash_Node *bucket, const void *key); static Ecore_Hash_Node * _ecore_hash_node_new(void *key, void *value); static int _ecore_hash_node_init(Ecore_Hash_Node *node, void *key, void *value); static int _ecore_hash_node_destroy(Ecore_Hash_Node *node, Ecore_Free_Cb keyd, Ecore_Free_Cb valued); /** * @defgroup Ecore_Data_Hash_ADT_Creation_Group Hash Creation Functions * * Functions that create hash tables. */ /** * Creates and initializes a new hash * @param hash_func The function for determining hash position. * @param compare The function for comparing node keys. * @return @c NULL on error, a new hash on success. * @ingroup Ecore_Data_Hash_ADT_Creation_Group */ EAPI Ecore_Hash * ecore_hash_new(Ecore_Hash_Cb hash_func, Ecore_Compare_Cb compare) { Ecore_Hash *new_hash = (Ecore_Hash *)malloc(sizeof(Ecore_Hash)); if (!new_hash) return NULL; if (!ecore_hash_init(new_hash, hash_func, compare)) { FREE(new_hash); return NULL; } return new_hash; } /** * Initializes the given hash. * @param hash The given hash. * @param hash_func The function used for hashing node keys. * @param compare The function used for comparing node keys. * @return @c TRUE on success, @c FALSE on an error. * @ingroup Ecore_Data_Hash_ADT_Creation_Group */ EAPI int ecore_hash_init(Ecore_Hash *hash, Ecore_Hash_Cb hash_func, Ecore_Compare_Cb compare) { CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); memset(hash, 0, sizeof(Ecore_Hash)); hash->hash_func = hash_func; hash->compare = compare; hash->buckets = (Ecore_Hash_Node **)calloc(ecore_prime_table[0], sizeof(Ecore_Hash_Node *)); return TRUE; } /** * @defgroup Ecore_Data_Hash_ADT_Destruction_Group Hash Destruction Functions * * Functions that destroy hash tables and their contents. */ /** * Sets the function to destroy the keys of the given hash. * @param hash The given hash. * @param function The function used to free the node keys. NULL is a * valid value and means that no function will be called. * @return @c TRUE on success, @c FALSE on error. * @ingroup Ecore_Data_Hash_ADT_Destruction_Group */ EAPI int ecore_hash_free_key_cb_set(Ecore_Hash *hash, Ecore_Free_Cb function) { CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); hash->free_key = function; return TRUE; } /** * Sets the function to destroy the values in the given hash. * @param hash The given hash. * @param function The function that will free the node values. NULL is a * valid value and means that no function will be called. * @return @c TRUE on success, @c FALSE on error * @ingroup Ecore_Data_Hash_ADT_Destruction_Group */ EAPI int ecore_hash_free_value_cb_set(Ecore_Hash *hash, Ecore_Free_Cb function) { CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); hash->free_value = function; return TRUE; } /** * @defgroup Ecore_Data_Hash_ADT_Data_Group Hash Data Functions * * Functions that set, access and delete values from the hash tables. */ /** * Sets a key-value pair in the given hash table. * @param hash The given hash table. * @param key The key. * @param value The value. * @return @c TRUE if successful, @c FALSE if not. * @ingroup Ecore_Data_Hash_ADT_Data_Group */ EAPI int ecore_hash_set(Ecore_Hash *hash, void *key, void *value) { int ret = FALSE; Ecore_Hash_Node *node; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); node = _ecore_hash_node_get(hash, key); if (node) { if (hash->free_key) hash->free_key(key); if (node->value && hash->free_value) hash->free_value(node->value); node->value = value; ret = TRUE; } else { node = _ecore_hash_node_new(key, value); if (node) ret = _ecore_hash_node_add(hash, node); } return ret; } /** * Sets all key-value pairs from set in the given hash table. * @param hash The given hash table. * @param set The hash table to import. * @return @c TRUE if successful, @c FALSE if not. * @ingroup Ecore_Data_Hash_ADT_Data_Group */ EAPI int ecore_hash_hash_set(Ecore_Hash *hash, Ecore_Hash *set) { unsigned int i; Ecore_Hash_Node *node, *old; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); CHECK_PARAM_POINTER_RETURN("set", set, FALSE); for (i = 0; i < ecore_prime_table[set->size]; i++) { /* Hash into a new list to avoid loops of rehashing the same nodes */ while ((old = set->buckets[i])) { set->buckets[i] = old->next; old->next = NULL; node = _ecore_hash_node_get(hash, old->key); if (node) { /* This key already exists. Delete the old and add the new * value */ if (hash->free_key) hash->free_key(node->key); if (hash->free_value) hash->free_key(node->value); node->key = old->key; node->value = old->value; free(old); } else _ecore_hash_node_add(hash, old); } } FREE(set->buckets); ecore_hash_init(set, set->hash_func, set->compare); return TRUE; } /** * Frees the hash table and the data contained inside it. * @param hash The hash table to destroy. * @return @c TRUE on success, @c FALSE on error. * @ingroup Ecore_Data_Hash_ADT_Destruction_Group */ EAPI void ecore_hash_destroy(Ecore_Hash *hash) { unsigned int i = 0; CHECK_PARAM_POINTER("hash", hash); if (hash->buckets) { while (i < ecore_prime_table[hash->size]) { if (hash->buckets[i]) { Ecore_Hash_Node *bucket; /* * Remove the bucket list to avoid possible recursion * on the free callbacks. */ bucket = hash->buckets[i]; hash->buckets[i] = NULL; _ecore_hash_bucket_destroy(bucket, hash->free_key, hash->free_value); } i++; } FREE(hash->buckets); } FREE(hash); return; } /** * @defgroup Ecore_Data_Hash_ADT_Traverse_Group Hash Traverse Functions * * Functions that iterate through hash tables. */ /** * Counts the number of nodes in a hash table. * @param hash The hash table to count current nodes. * @return The number of nodes in the hash. * @ingroup Ecore_Data_Hash_ADT_Destruction_Group */ EAPI int ecore_hash_count(Ecore_Hash *hash) { CHECK_PARAM_POINTER_RETURN("hash", hash, 0); return hash->nodes; } /** * Runs the @p for_each_func function on each entry in the given hash. * @param hash The given hash. * @param for_each_func The function that each entry is passed to. * @param user_data a pointer passed to calls of for_each_func * @return TRUE on success, FALSE otherwise. * @ingroup Ecore_Data_Hash_ADT_Traverse_Group */ EAPI int ecore_hash_for_each_node(Ecore_Hash *hash, Ecore_For_Each for_each_func, void *user_data) { unsigned int i = 0; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); CHECK_PARAM_POINTER_RETURN("for_each_func", for_each_func, FALSE); while (i < ecore_prime_table[hash->size]) { if (hash->buckets[i]) { Ecore_Hash_Node *node; for (node = hash->buckets[i]; node; node = node->next) { for_each_func(node, user_data); } } i++; } return TRUE; } /** * Retrieves an ecore_list of all keys in the given hash. * @param hash The given hash. * @return new ecore_list on success, NULL otherwise * @ingroup Ecore_Data_Hash_ADT_Traverse_Group */ EAPI Ecore_List * ecore_hash_keys(Ecore_Hash *hash) { unsigned int i = 0; Ecore_List *keys; CHECK_PARAM_POINTER_RETURN("hash", hash, NULL); keys = ecore_list_new(); while (i < ecore_prime_table[hash->size]) { if (hash->buckets[i]) { Ecore_Hash_Node *node; for (node = hash->buckets[i]; node; node = node->next) { ecore_list_append(keys, node->key); } } i++; } ecore_list_first_goto(keys); return keys; } /** * Prints the distribution of the given hash table for graphing. * @param hash The given hash table. */ EAPI void ecore_hash_dump_graph(Ecore_Hash *hash) { unsigned int i; for (i = 0; i < ecore_prime_table[hash->size]; i++) if (hash->buckets[i]) { int n = 0; Ecore_Hash_Node *node; for (node = hash->buckets[i]; node; node = node->next) n++; printf("%d\t%u", i, n); } else printf("%d\t0", i); } /** * Prints the distribution of the given hash table for graphing. * @param hash The given hash table. */ EAPI void ecore_hash_dump_stats(Ecore_Hash *hash) { unsigned int i; double variance, sum_n_2 = 0, sum_n = 0; for (i = 0; i < ecore_prime_table[hash->size]; i++) { if (hash->buckets[i]) { int n = 0; Ecore_Hash_Node *node; for (node = hash->buckets[i]; node; node = node->next) n++; sum_n_2 += ((double)n * (double)n); sum_n += (double)n; } } variance = (sum_n_2 - ((sum_n * sum_n) / (double)i)) / (double)i; printf("Average length: %f\n\tvariance^2: %f", (sum_n / (double)i), variance); } static int _ecore_hash_bucket_destroy(Ecore_Hash_Node *list, Ecore_Free_Cb keyd, Ecore_Free_Cb valued) { Ecore_Hash_Node *node; CHECK_PARAM_POINTER_RETURN("list", list, FALSE); for (node = list; node; node = list) { list = list->next; _ecore_hash_node_destroy(node, keyd, valued); } return TRUE; } /* * @brief Add the node to the hash table * @param hash: the hash table to add the key * @param node: the node to add to the hash table * @return Returns FALSE on error, TRUE on success */ static int _ecore_hash_node_add(Ecore_Hash *hash, Ecore_Hash_Node *node) { unsigned long hash_val; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); CHECK_PARAM_POINTER_RETURN("node", node, FALSE); /* Check to see if the hash needs to be resized */ if (ECORE_HASH_INCREASE(hash)) _ecore_hash_increase(hash); /* Compute the position in the table */ if (!hash->hash_func) hash_val = (unsigned long)node->key % ecore_prime_table[hash->size]; else hash_val = ECORE_COMPUTE_HASH(hash, node->key); /* Prepend the node to the list at the index position */ node->next = hash->buckets[hash_val]; hash->buckets[hash_val] = node; hash->nodes++; return TRUE; } /** * Retrieves the value associated with the given key from the given hash * table. * @param hash The given hash table. * @param key The key to search for. * @return The value corresponding to key on success, @c NULL otherwise. * @ingroup Ecore_Data_Hash_ADT_Data_Group */ EAPI void * ecore_hash_get(Ecore_Hash *hash, const void *key) { void *data; Ecore_Hash_Node *node; CHECK_PARAM_POINTER_RETURN("hash", hash, NULL); node = _ecore_hash_node_get(hash, key); if (!node) return NULL; data = node->value; return data; } /** * Removes the value associated with the given key in the given hash * table. * @param hash The given hash table. * @param key The key to search for. * @return The value corresponding to the key on success. @c NULL is * returned if there is an error. * @ingroup Ecore_Data_Hash_ADT_Data_Group */ EAPI void * ecore_hash_remove(Ecore_Hash *hash, const void *key) { Ecore_Hash_Node *node = NULL; Ecore_Hash_Node *list; unsigned long hash_val; void *ret = NULL; CHECK_PARAM_POINTER_RETURN("hash", hash, NULL); /* Compute the position in the table */ if (!hash->hash_func) hash_val = (unsigned long )key % ecore_prime_table[hash->size]; else hash_val = ECORE_COMPUTE_HASH(hash, key); /* * If their is a list that could possibly hold the key/value pair * traverse it and remove the hash node. */ if (hash->buckets[hash_val]) { list = hash->buckets[hash_val]; /* * Traverse the list to find the specified key */ node = list; if (hash->compare) while ((node) && (hash->compare(node->key, key) != 0)) { list = node; node = node->next; } else while ((node) && (node->key != key)) { list = node; node = node->next; } /* * Remove the node with the matching key and free it's memory */ if (node) { if (list == node) hash->buckets[hash_val] = node->next; else list->next = node->next; ret = node->value; node->value = NULL; _ecore_hash_node_destroy(node, hash->free_key, NULL); hash->nodes--; } } if (ECORE_HASH_REDUCE(hash)) _ecore_hash_decrease(hash); return ret; } /** * Retrieves the first value that matches * table. * @param hash The given hash table. * @param key The key to search for. * @return The value corresponding to key on success, @c NULL otherwise. * @ingroup Ecore_Data_Hash_ADT_Data_Group */ EAPI void * ecore_hash_find(Ecore_Hash *hash, Ecore_Compare_Cb compare, const void *value) { unsigned int i = 0; CHECK_PARAM_POINTER_RETURN("hash", hash, NULL); CHECK_PARAM_POINTER_RETURN("compare", compare, NULL); CHECK_PARAM_POINTER_RETURN("value", value, NULL); while (i < ecore_prime_table[hash->size]) { if (hash->buckets[i]) { Ecore_Hash_Node *node; for (node = hash->buckets[i]; node; node = node->next) { if (!compare(node->value, value)) return node->value; } } i++; } return NULL; } /* * @brief Retrieve the node associated with key * @param hash: the hash table to search for the key * @param key: the key to search for in the hash table * @return Returns NULL on error, node corresponding to key on success */ static Ecore_Hash_Node * _ecore_hash_node_get(Ecore_Hash *hash, const void *key) { unsigned long hash_val; Ecore_Hash_Node *node = NULL; CHECK_PARAM_POINTER_RETURN("hash", hash, NULL); if (!hash->buckets) return NULL; /* Compute the position in the table */ if (!hash->hash_func) hash_val = (unsigned long)key % ecore_prime_table[hash->size]; else hash_val = ECORE_COMPUTE_HASH(hash, key); /* Grab the bucket at the specified position */ if (hash->buckets[hash_val]) { node = _ecore_hash_bucket_get(hash, hash->buckets[hash_val], key); /* * Move matched node to the front of the list as it's likely * to be searched for again soon. */ if (node && node != hash->buckets[hash_val]) { node->next = hash->buckets[hash_val]; hash->buckets[hash_val] = node; } } return node; } /* * @brief Search the hash bucket for a specified key * @param hash: the hash table to retrieve the comparison function * @param bucket: the list to search for the key * @param key: the key to search for in the list * @return Returns NULL on error or not found, the found node on success */ static inline Ecore_Hash_Node * _ecore_hash_bucket_get(Ecore_Hash *hash, Ecore_Hash_Node *bucket, const void *key) { Ecore_Hash_Node *prev = NULL; Ecore_Hash_Node *node = NULL; /* * Traverse the list to find the desired node, if the node is in the * list, then return the node. */ if (hash->compare) for (node = bucket; node; node = node->next) { if (hash->compare(node->key, key) == 0) break; prev = node; } else for (node = bucket; node; node = node->next) { if (node->key == key) break; prev = node; } /* * Remove node from the list to replace it at the beginning. */ if (node && prev) { prev->next = node->next; node->next = NULL; } return node; } /* * @brief Increase the size of the hash table by approx. 2 * current size * @param hash: the hash table to increase the size of * @return Returns TRUE on success, FALSE on error */ static int _ecore_hash_increase(Ecore_Hash *hash) { void *old; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); /* Max size reached so return FALSE */ if ((ecore_prime_table[hash->size] == PRIME_MAX) || (hash->size == PRIME_TABLE_MAX)) return FALSE; /* * Increase the size of the hash and save a pointer to the old data */ hash->size++; old = hash->buckets; /* * Allocate a new bucket area, of the new larger size */ hash->buckets = calloc(ecore_prime_table[hash->size], sizeof(Ecore_Hash_Node *)); /* * Make sure the allocation succeeded, if not replace the old data and * return a failure. */ if (!hash->buckets) { hash->buckets = old; hash->size--; return FALSE; } hash->nodes = 0; /* * Now move all of the old data into the new bucket area */ if (_ecore_hash_rehash(hash, old, hash->size - 1)) { FREE(old); return TRUE; } /* * Free the old buckets regardless of success. */ FREE(old); return FALSE; } /* * @brief Decrease the size of the hash table by < 1/2 * current size * @param hash: the hash table to decrease the size of * @return Returns TRUE on success, FALSE on error */ static int _ecore_hash_decrease(Ecore_Hash *hash) { Ecore_Hash_Node **old; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); if (ecore_prime_table[hash->size] == PRIME_MIN) return FALSE; /* * Decrease the hash size and store a pointer to the old data */ hash->size--; old = hash->buckets; /* * Allocate a new area to store the data */ hash->buckets = (Ecore_Hash_Node **)calloc(ecore_prime_table[hash->size], sizeof(Ecore_Hash_Node *)); /* * Make sure allocation succeeded otherwise rreturn to the previous * state */ if (!hash->buckets) { hash->buckets = old; hash->size++; return FALSE; } hash->nodes = 0; if (_ecore_hash_rehash(hash, old, hash->size + 1)) { FREE(old); return TRUE; } return FALSE; } /* * @brief Rehash the nodes of a table into the hash table * @param hash: the hash to place the nodes of the table * @param table: the table to remove the nodes from and place in hash * @return Returns TRUE on success, FALSE on error */ static inline int _ecore_hash_rehash(Ecore_Hash *hash, Ecore_Hash_Node **old_table, int old_size) { unsigned int i; Ecore_Hash_Node *old; CHECK_PARAM_POINTER_RETURN("hash", hash, FALSE); CHECK_PARAM_POINTER_RETURN("old_table", old_table, FALSE); for (i = 0; i < ecore_prime_table[old_size]; i++) { /* Hash into a new list to avoid loops of rehashing the same nodes */ while ((old = old_table[i])) { old_table[i] = old->next; old->next = NULL; _ecore_hash_node_add(hash, old); } } return TRUE; } /* * @brief Create a new hash node for key and value storage * @param key: the key for this node * @param value: the value that the key references * @return Returns NULL on error, a new hash node on success */ static Ecore_Hash_Node * _ecore_hash_node_new(void *key, void *value) { Ecore_Hash_Node *node; node = (Ecore_Hash_Node *)malloc(sizeof(Ecore_Hash_Node)); if (!node) return NULL; if (!_ecore_hash_node_init(node, key, value)) { FREE(node); return NULL; } return node; } /* * @brief Initialize a hash node to some sane default values * @param node: the node to set the values * @param key: the key to reference this node * @param value: the value that key refers to * @return Returns TRUE on success, FALSE on error */ static int _ecore_hash_node_init(Ecore_Hash_Node *node, void *key, void *value) { CHECK_PARAM_POINTER_RETURN("node", node, FALSE); node->key = key; node->value = value; return TRUE; } /* * @brief Destroy a node and call the specified callbacks to free data * @param node: the node to be destroyed * @param keyd: the function to free the key * @param valued: the function to free the value * @return Returns TRUE on success, FALSE on error */ static int _ecore_hash_node_destroy(Ecore_Hash_Node *node, Ecore_Free_Cb keyd, Ecore_Free_Cb valued) { CHECK_PARAM_POINTER_RETURN("node", node, FALSE); if (keyd) keyd(node->key); if (valued) valued(node->value); FREE(node); return TRUE; } int ecore_str_compare(const void *key1, const void *key2) { const char *k1, *k2; if (!key1 || !key2) return ecore_direct_compare(key1, key2); else if (key1 == key2) return 0; k1 = key1; k2 = key2; return strcmp(k1, k2); } unsigned int ecore_str_hash(const void *key) { int i; unsigned int mask; unsigned int value = 0; const char *k = key; if (!k) return 0; mask = (sizeof(unsigned int) * 8) - 1; for (i = 0; k[i] != '\0'; i++) { value ^= ((unsigned int)k[i] << ((i * 5) & mask)); } return value; }