Hash Tables

The Eina_Hash provides a way to store values associated with a key. For example, you can use it to store tuples in a table.

The Eina_Hash is implemented using an array of buckets. Each bucket is a pointer to a structure that is the head of a red-black tree. This implementation makes it very robust against weak keys as in the worst case scenario, you can still depend on an efficient binary tree implementation.

Creating a Hash Table

To create the hash table, use the eina_hash_new() function:

  • The first parameter is the function called when obtaining the key size.
  • The second parameter is the function called when comparing keys.
  • The third parameter is the function called when reading values.
  • The fourth parameter is the function called on each value when the hash table is freed, or when an item is deleted from it. NULL can be passed as the callback.
  • The last parameter is the size of the buckets.

When you create an Eina_Hash instance, you have to create four potentially long callback functions. Eina_Hash has some predefined functions to make these shorter. These are used to create various types of hash tables:

  • eina_hash_string_djb2_new() creates a new hash table using the djb2 algorithm for strings.
  • eina_hash_string_superfast_new() creates a new hash table for use with strings. This is best for long strings.
  • eina_hash_string_small_new() creates a new hash table for use with strings with a small bucket size.
  • eina_hash_int32_new() and eina_hash_int64_new() create a new hash table for use with 32-bit and 64-bit integers.
  • eina_hash_pointer_new() creates a new hash table for use with pointers.
  • eina_hash_stringshared_new() creates a new hash table for use with shared strings.

All these predefined functions require only one parameter: the function to free the data you've stored in the hash table.

The following example shows how to manage a small phone book using the eina_hash_string_superfast_new() function to create a hash table.

Create the phone book structure and some static data:

struct _Phone_Entry
{
   const char *name; // Full name
   const char *number; // Phone number
};
 
typedef struct _Phone_Entry Phone_Entry;
 
static Phone_Entry _start_entries[] =
{
   { "Wolfgang Amadeus Mozart", "+01 23 456-78910" },
   { "Ludwig van Beethoven", "+12 34 567-89101" },
   { "Richard Georg Strauss", "+23 45 678-91012" },
   { "Heitor Villa-Lobos", "+34 56 789-10123" },
   { NULL, NULL }
};

Create the callback to free the data:

static void
_phone_entry_free_cb(void *data)
{
   free(data);
}

The free callback can be changed later using the function eina_hash_free_cb_set(). You need to pass the hash and new callback function.

The eina_hash_free_buckets() function frees all hash table buckets. It empties the hash but does not destroy it. You may still use it for another purpose if you wish. When you calleina_hash_free() the space allocated to the hash is freed.

Create and destroy a hash table as follows:

int free_data()
{
   Eina_Hash *phone_book = NULL;
   phone_book = eina_hash_string_superfast_new(_phone_entry_free_cb);
 
   // Empty the phone book without destroying it
   eina_hash_free_buckets(phone_book);
   eina_hash_free(phone_book);
}

Modifying Hash Table Content

Add data to a hash

Use the eina_hash_add() function to add data. This function takes the hash, the key to access the data and the data itself as its parameters. The following example shows how to add the initial data declared earlier to the hash:

for (i = 0; _start_entries[i].name != NULL; i++)
  {
     eina_hash_add(phone_book, _start_entries[i].name, strdup(_start_entries[i].number));
  }

The Eina_Hash offers various ways to add elements to a hash such as the function eina_hash_direct_add(). This adds an entry without duplicating the string key. The key is stored in the struct, so this function can be used with eina_stringshare to avoid key data duplication.

for (i = 0; _start_entries[i].name != NULL; i++)
  {
     // Allocating memory for the phone entry
     Phone_Entry *e = malloc(sizeof(Phone_Entry));
 
     // Creating an eina_stringshare for the name and the phone number
     e->name = eina_stringshare_add(_start_entries[i].name);
     e->number = eina_stringshare_add(_start_entries[i].number);
 
     // Adding the entry to the hash
     eina_hash_direct_add(phone_book, e->name, e);
  }

Modify an entry

Use the eina_hash_modify() function to modify an entry, passing the hash, the key of the data to change and the new data. This function returns the old data on completion.

The eina_hash_set() function does the same work as eina_hash_modify() but if an entry doesn't exist, the function creates a new one.

char *old_phone = NULL;
// Replace the phone number of Richard Strauss
old_phone = eina_hash_modify(phone_book, "Richard Georg Strauss", strdup("+23 45 111-11111"));
[...]
old_phone = eina_hash_set(phone_book, "Philippe de Magalhães", strdup("+33 6 111-11111"));
[...]
old_phone = eina_hash_set(phone_book, "Richard Georg Strauss", strdup("+23 45 111-117711"));
[...]

NOTE: Remember to check the return value of eina_hash_modify() and eina_hash_set() and free it if required, or you might be leaking the previous entry in the hash!

Change the key associated with data

Use the eina_hash_move() function to change the key without freeing and creating a new entry . You need only pass the hash, the old key and the new key. If the operation succeeds, the function returns EINA_TRUE, if not it returns EINA_FALSE.

Eina_Bool res;
res = eina_hash_move(phone_book, "Philippe de Magalhães", "Filipe de Magalhães");

Delete entries from a hash table

Use the eina_hash_del() function to remove an entry identified by a key or data from the given hash table:

Eina_Bool r;
const char *entry_name = "Heitor Villa-Lobos";
r = eina_hash_del(phone_book, entry_name, NULL);

Use the eina_hash_del_by_key() function to remove an entry based on the key:

r = eina_hash_del_by_key(phone_book, "Richard Georg Strauss");

Use the eina_hash_del_by_data() function to remove an entry based on data:

r = eina_hash_del_by_data(phone_book, "+12 34 567-89101");

Accessing Hash Table Data

To find hash table elements and get data based on the key name:

To retrieve an entry based on its key

Use the eina_hash_find() function by passing the hash and the key you're searching for:

char *phone = NULL;
const char *entry_name = "Heitor Villa-Lobos";
 
// Look for a specific entry and get its phone number
phone = eina_hash_find(phone_book, entry_name);

Retrieve the number of entries in a hash

Use the eina_hash_population() function to see the number of entries. Pass the hash as the only argument:

unsigned int nb_elm;
nb_elm = eina_hash_population(phone_book);

Iterate through a hash table

There are several ways to iterate through a hash table. One is to use the eina_hash_foreach() function. The first parameter is the hash. The second parameter is the callback function called on each iteration. The callback function must return EINA_TRUE if the iteration has to continue and EINA_FALSE if the iteration must end. The last parameter one is the data passed to the callback function.

The following example prints the key and the data of the hash entry (name and phone number):

static Eina_Bool
pb_foreach_cb(const Eina_Hash *phone_book, const void *key, void *data, void *fdata)
{
   const char *name = key;
   const char *number = data;
   printf("%s: %s\n", name, number);
 
   // Return EINA_FALSE to stop this callback from being called
   return EINA_TRUE;
}
 
printf("List of phones:\n");
 
// Running the callback on the hash called phone_book
eina_hash_foreach(phone_book, pb_foreach_cb, NULL);
printf("\n");

Iterate over keys

Use the ''eina_hash_iterator_key_new()'' function to iterate over keys:

// Declaration of the Eina_Iterator
Eina_Iterator *it;
 
// Variable to handle the current iteration "data"
void *data;
 
// Iterate over the keys (names)
printf("List of names in the phone book:\n");
 
it = eina_hash_iterator_key_new(phone_book);
 
// Use the generic eina_iterator_next()
while (eina_iterator_next(it, &data))
{
   const char *name = data;
   printf("%s\n", name);
}
 
// Free the iterator
eina_iterator_free(it);

Iterate over hash data

Use the eina_hash_iterator_data_new() function in the same way as eina_hash_iterator_key_new() to iterate over hash data:

// Declaration of the Eina_Iterator
Eina_Iterator *it;
 
// Variable to handle the current iteration "data"
void *data;
 
// Iterate over hash data
printf("List of numbers in the phone book:\n");
 
it = eina_hash_iterator_data_new(phone_book);
while (eina_iterator_next(it, &data))
  {
     const char *number = data;
     printf("%s\n", number);
  }
 
// Free the iterator
eina_iterator_free(it);

Iterate over a tuple composed of keys and data

Use the eina_hash_iterator_tuple_new() function to iterate in this way:

// Declaration of the Eina_Iterator
Eina_Iterator *tit;
 
// Variable to handle the current iteration "data"
void *tuple;
 
printf("List of phones:\n");
tit = eina_hash_iterator_tuple_new(phone_book);
while (eina_iterator_next(tit, &tuple))
  {
     Eina_Hash_Tuple *t = tuple;
     const char *name = t->key;
     const char *number = t->data;
     printf("%s: %s\n", name, number);
  }
 
// Always free the iterator after its use
eina_iterator_free(tit);

Further Reading

Hash Table API
A reference for the Hash Table API.
Hash Table Example
An example use of the Hash Table API.