1

Hi everyone this is my first question here.

So, I'm trying to implement a very simple hash table in C. I have different functions to:

  • insert a node;
  • finding a node;
  • deleting a node;
  • hash a key (more or less, in fact I don't actually hash it);
  • create the hash table (first problem here);
  • display the hash table on screen (second problem here);

The first problem regards the hashtable *create_table(void); function. In particular, as the compiler is suggesting, during the for-loop I'm trying to assign from type void to type node_t. But that is the only way I can think of setting a node to "zero".

The second problem is related to the first, in void display_hashtable(hashtable *ht);, I can't use a binary operator on different types. But in order to check if a node is empty this is, again, the only method I can think of.

So, I really hope that someone will find the time to help me!

Thank you in advance :)

Plus:

By having a struct like this:

typedef struct hash_table{
  node_t table[SIZE];
}hashtable;

With an assignment like this: ht->table = malloc(sizeof(node_t) * SIZE); I got error: assignment to expression with array type (this, in hashtable *create_table(void); function btw).

But I believe that I solved this problem by adjusting the struct by doing this:

typedef struct hash_table{
  node_t *table;
}hashtable;

Is my assumption right?

This is the indicted code:

// HASHTABLE

# include <stdio.h>
# include <stdlib.h>
# include <stdbool.h>
# include <assert.h>

# define SIZE 512

typedef struct nodez{
  struct nodez *next;
  char *element;
  int key;
}node_t;

typedef struct hash_table{
  node_t *table;
}hashtable;


node_t *insert(hashtable *ht, int key, char *element); // head insertion
void *find(hashtable *ht, int key); // finds the node identified by a key, main use is to not have duplicate keys
bool delete(hashtable *ht, int key); // deletes a node identified by a key
hashtable *create_table(void); // creates the table and sets to NULL all the spots
void display_hashtable(hashtable *ht); // displays the hashtable
int hash(int key); // hashes the key


int main(){
  
  hashtable *ht = create_table();
  
  node_t *francesco = insert(ht, 2, "Francesco");
  node_t *daniela = insert(ht, 4, "Daniela");
  node_t *pietro = insert(ht, 1, "Pietro");
  node_t *priscilla = insert(ht, 3, "Priscilla");

  display_hashtable(ht);

  delete(ht, 1);
  delete(ht, 2);
  delete(ht, 3);
  delete(ht, 4);
}

/* apparently is impossible to to assign/compare with NULL*/
hashtable *create_table(void){

  int i = 0;
  hashtable *ht = malloc(sizeof(hashtable) * 1);
  ht->table = malloc(sizeof(node_t) * SIZE);
  
  for(; i < SIZE; ++i){
    ht->table[i] = NULL; // set to zero the hashtable PROBLEM HERE
  }
  
  return ht;
}


node_t *insert(hashtable *ht, int key, char *element){

  assert(element != NULL);

  if(find(ht, key) != NULL) exit(1);

  node_t *new_node = malloc(sizeof(node_t));
  node_t **sentinel = &ht->table[hash(key)].next;

  if(new_node == NULL){
    printf("Failed to allocate %s.\n", element);
    exit(1);
  }

  new_node->key = key;
  new_node->element = element;
  new_node->next = *sentinel;
  *sentinel = new_node;
  
  return new_node;
}


void *find(hashtable *ht, int key){

  node_t **sentinel = &ht->table[hash(key)].next;

  while((*sentinel) && (*sentinel)->key != key){
    sentinel = &(*sentinel)->next;
  }

  if(!(*sentinel)) return NULL;
  
  return (*sentinel)->element;
}


bool delete(hashtable *ht, int key){

  node_t **sentinel = &ht->table[hash(key)].next;
  node_t *will_be_deleted; // so I can properly free the node without having memory leaks

  while((*sentinel) && (*sentinel)->key != key){
    sentinel = &(*sentinel)->next;
  }

  if(!(*sentinel)) return false;

  will_be_deleted = *sentinel;
  *sentinel = (*sentinel)->next;
  free(will_be_deleted); // poor will
  
  return true;
}


/* apparently is impossible to to assign/compare with NULL*/
void display_hashtable(hashtable *ht){

  int i = 0;

  for(i = 0; i < SIZE; i++){
    if(ht->table[i] == NULL) break; // PROBLEM HERE
    printf("Element: %s || Slot: %d || Key: %d.\n", ht->table[i].element, hash(ht->table[i].key), ht->table[i].key);
  }
}


int hash(int key){

  int value;

  value = key; // reminder to properly hash the key
  
  value = value % SIZE; // make the value acceptable
  return value;
}
Pit
  • 11
  • 3
  • `NULL` is a *pointer value*. The type of `ht->table[i]` is `node_t`, i.e. it is not a pointer. These types are not compatible, so you cannot assign them. In general you cannot zero out a structure by assigning `NULL`. Instead, you need to reset all fields manually. – Konrad Rudolph Jan 02 '23 at 16:21
  • @KonradRudolph [You can also use a *compound literal*](https://stackoverflow.com/a/6892019/4756299). FWIW, this question is probably a duplicate of that one. I'll let others decide, though. – Andrew Henle Jan 02 '23 at 16:31
  • @KonradRudolph Ok understood, thanks for that. Does that mean that I can solve the second problem the same way I will solve the first? So if I use a certain syntax to manually zero out a struct, I will reuse it to check if it's "zero" during the for-loop. – Pit Jan 02 '23 at 17:02
  • @AndrewHenle Andrew, I also notice some similarities with the post you refer to. But only regarding my first question, in fact I have not found anything that could answer the questions that follow the one kindly answered by Konrad. Hoping you can understand, I wish you have a good day and thank you for your observation. – Pit Jan 02 '23 at 17:12
  • @Pit You would have to figure out what that even means. A struct can't be null, only a pointer can be null. But maybe you can say that if the node isn't used then the *element* is null. Of course, that only works if you don't *want* the element to be null in the nodes that *are* used. – user253751 Jan 03 '23 at 02:24

1 Answers1

2

Caveat: Your implementation needs almost a complete restructuring.

A few issues ...

  1. The hash table contains: node_t *table; OR node_t table[SIZE]; It should be: node_t *table[SIZE]; (or a malloc variant below: node_t **table).
  2. The print function stops if a hash table entry is NULL.
  3. The print function does not follow the linked list for a given hash entry.
  4. What you're calling sentinel is a pointer to the table entry/head, so not really a sentinel (special value) as generally used. In the code below, I've renamed this to head.
  5. You're using an explicit integer value for the key. But, for hashes of strings, this is generally derived from the string itself.
  6. SIZE is so large that without a lot more test data, the hash table algorithm isn't fully tested. That is, you'll only have one element per hash bucket.
  7. In insert, the original element pointer is stored in the node_t struct. In the general case, we should use strdup (e.g.) The string might be read from a file.
  8. find might be more useful if it returned node_t instead of char * (i.e. the element value).
  9. The use of the double pointers in delete and find can be simplified.

Here is the restructured code. It is annotated:

// HASHTABLE

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>

#ifndef SIZE
#ifdef BIGSIZE
#define SIZE 512
#else
#define SIZE 8
#endif
#endif

typedef struct nodez {
    struct nodez *next;
    const char *element;
    int key;
} node_t;

typedef struct hash_table {
#if DYNAMIC_TABLE
    node_t **table;                         // dynamic table size
#else
    node_t *table[SIZE];                    // fixed table size
#endif
} hashtable;

#ifndef STRDUP
#define STRDUP  1                           // use strdup when storing entry
#endif

#define ARRAY_COUNT(_arr) \
    (sizeof(_arr) / sizeof(_arr[0]))

hashtable *
create_table(void)
{
    hashtable *ht = calloc(1,sizeof(hashtable));

#if DYNAMIC_TABLE
    ht->table = calloc(SIZE,sizeof(node_t *));
#endif

    return ht;
}

// strhash -- get hash key from string
int
strhash(const char *str)
{
    int hash = 0;

    for (;  *str != 0;  ++str)
        hash += (unsigned) *str;

    return hash;
}

// hash -- get hash index from hash key
int
hash(int key)
{
    // make the value acceptable
    key %= SIZE;

    return key;
}

node_t *
find(hashtable *ht,const char *element,int key)
{

    assert(element != NULL);

    if (key <= 0)
        key = strhash(element);

    int hidx = hash(key);
    node_t **head = &ht->table[hidx];

    node_t *cur = *head;
    for (;  cur != NULL;  cur = cur->next) {
        if (key == cur->key) {
            if (strcmp(element,cur->element) == 0)
                break;
        }
    }

    return cur;
}

// insert -- head insertion
node_t *
insert(hashtable *ht, const char *element)
{

    assert(element != NULL);

    int key = strhash(element);

    // point to table bucket
    int hidx = hash(key);
    node_t **head = &ht->table[hidx];

    // look for existing/duplicate element
    node_t *cur = *head;
    for (;  cur != NULL;  cur = cur->next) {
        if (key == cur->key) {
            if (strcmp(element,cur->element) == 0)
                break;
        }
    }

    if (cur != NULL) {
        printf("insert: duplicate node -- '%s'\n",element);
        exit(1);
    }

    node_t *new_node = malloc(sizeof(node_t));
    if (new_node == NULL) {
        printf("Failed to allocate %s.\n", element);
        exit(1);
    }

    new_node->key = key;
#if STRDUP
    element = strdup(element);
#endif
    new_node->element = element;

    // add to head of linked list for this hash bucket
    new_node->next = *head;
    *head = new_node;

    return new_node;
}

bool
delete(hashtable *ht, const char *element)
{

    // compute hash value from string
    int key = strhash(element);

    int hidx = hash(key);
    node_t **head = &ht->table[hidx];

    // look for a match
    node_t *prev = NULL;
    node_t *cur = *head;
    for (;  cur != NULL;  cur = cur->next) {
        if (key == cur->key) {
            if (strcmp(element,cur->element) == 0)
                break;
        }
        prev = cur;
    }

    // node not found
    if (cur == NULL) {
        printf("delete: node not found -- '%s'\n",element);
        return false;
    }

    // remove the node from the linked list
    if (prev != NULL)
        prev->next = cur->next;
    else
        *head = cur->next;

    // release the string
#if STRDUP
    free((void *) cur->element);
#endif

    // release the node
    free(cur);

    return true;
}

// displays the hashtable
void
display_hashtable(hashtable *ht,const char *why)
{

    printf("\n");
    printf("TABLE: %s\n",why);

    for (int i = 0; i < SIZE; i++) {
        node_t *cur = ht->table[i];

        for (;  cur != NULL;  cur = cur->next)
            printf("Element: %s || Slot: %d || Key: %d.\n",
                cur->element, i, cur->key);
    }
}

int
main(void)
{

    hashtable *ht = create_table();

    const char *names[] = {
        "Francesco",
        "Daniela",
        "Pietro",
        "Priscilla",
        "Amber",
        "Roger",
        "Nelly",
        "Marco",
        "Susan",
        "Mary",
        "Ellen",
        "Fred",
        "Frank",
        "Dick",
        "Jane",
        "Nancy",
        "Elvira",
        "Dracula",
        "Frankenstein",
        "Pierre",
        "Alberto"
    };

    // insert all test names into table
    for (int nidx = 0;  nidx < ARRAY_COUNT(names);  ++nidx)
        insert(ht,names[nidx]);
    display_hashtable(ht,"All Names");

    // ensure we can find all the names
    printf("\n");
    printf("Checking table ...\n");
    int err = 0;
    for (int nidx = 0;  nidx < ARRAY_COUNT(names);  ++nidx) {
        const char *name = names[nidx];
        if (find(ht,name,0) == NULL) {
            printf("ERROR: name '%s' not found\n",name);
            err = 1;
        }
    }
    if (err)
        exit(1);
    printf("All names found\n");

    // delete all nodes in a random order
    int delcount = 0;
    while (delcount < ARRAY_COUNT(names)) {
        // get a random name to delete
        int delidx = rand() % ARRAY_COUNT(names);
        const char *name = names[delidx];

        // see if we have already deleted it
        if (name == NULL)
            continue;

        delete(ht,name);

        char why[100];
        sprintf(why,"After deleting %s",name);
        display_hashtable(ht,why);

        // mark it as deleted
        names[delidx] = NULL;
        ++delcount;
    }

    return 0;
}

Here is the program output:


TABLE: All Names
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Ellen || Slot: 0 || Key: 496.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Mary || Slot: 1 || Key: 409.
Element: Frank || Slot: 2 || Key: 498.
Element: Susan || Slot: 2 || Key: 522.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Daniela || Slot: 6 || Key: 686.
Element: Pierre || Slot: 7 || Key: 615.
Element: Roger || Slot: 7 || Key: 511.
Element: Amber || Slot: 7 || Key: 487.

Checking table ...
All names found

TABLE: After deleting Daniela
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Ellen || Slot: 0 || Key: 496.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Mary || Slot: 1 || Key: 409.
Element: Frank || Slot: 2 || Key: 498.
Element: Susan || Slot: 2 || Key: 522.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Pierre || Slot: 7 || Key: 615.
Element: Roger || Slot: 7 || Key: 511.
Element: Amber || Slot: 7 || Key: 487.

TABLE: After deleting Amber
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Ellen || Slot: 0 || Key: 496.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Mary || Slot: 1 || Key: 409.
Element: Frank || Slot: 2 || Key: 498.
Element: Susan || Slot: 2 || Key: 522.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Pierre || Slot: 7 || Key: 615.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Mary
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Ellen || Slot: 0 || Key: 496.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Susan || Slot: 2 || Key: 522.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Pierre || Slot: 7 || Key: 615.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Pierre
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Ellen || Slot: 0 || Key: 496.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Susan || Slot: 2 || Key: 522.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Susan
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Ellen || Slot: 0 || Key: 496.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Ellen
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Alberto || Slot: 1 || Key: 713.
Element: Nancy || Slot: 1 || Key: 505.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Nancy
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Alberto || Slot: 1 || Key: 713.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Pietro || Slot: 3 || Key: 627.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Pietro
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Alberto || Slot: 1 || Key: 713.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Alberto
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Marco || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Marco
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Priscilla || Slot: 3 || Key: 931.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Priscilla
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Elvira || Slot: 3 || Key: 611.
Element: Dick || Slot: 3 || Key: 379.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Elvira
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Dick || Slot: 3 || Key: 379.
Element: Dracula || Slot: 4 || Key: 700.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Dracula
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Dick || Slot: 3 || Key: 379.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Jane || Slot: 6 || Key: 382.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Jane
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Frank || Slot: 2 || Key: 498.
Element: Dick || Slot: 3 || Key: 379.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Frank
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Dick || Slot: 3 || Key: 379.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.
Element: Roger || Slot: 7 || Key: 511.

TABLE: After deleting Roger
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Dick || Slot: 3 || Key: 379.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.

TABLE: After deleting Dick
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Nelly || Slot: 4 || Key: 516.
Element: Francesco || Slot: 4 || Key: 916.

TABLE: After deleting Francesco
Element: Frankenstein || Slot: 0 || Key: 1256.
Element: Fred || Slot: 1 || Key: 385.
Element: Nelly || Slot: 4 || Key: 516.

TABLE: After deleting Frankenstein
Element: Fred || Slot: 1 || Key: 385.
Element: Nelly || Slot: 4 || Key: 516.

TABLE: After deleting Fred
Element: Nelly || Slot: 4 || Key: 516.

TABLE: After deleting Nelly

UPDATE:

hash(key) does not certainly return a non-negative number. insert(h, "\377"); can lead to a return value of -1. Better to use unsigned hash(unsigned key) { key %= SIZE; return key; } and unsigned hidx = hash(key);. –  chux - Reinstate Monica

I thought that this would guarantee non-negative:

hash += (unsigned) *str;

But, it should have been:

hash += (unsigned char) *str;

I kept the int [vs. unsigned int] because I was already changing a lot of code.

But, to be a bit clearer, I've added typedefs:

typedef unsigned int hash_t;                // hash value
typedef unsigned int hidx_t;                // hash index

Disagree with "find might be more useful if it returned node_t instead of char * (i.e. the element value)." as that obliges calling to know about the structure of the hash table. HT details best left out of the public eye. Overall, many good points about this answer. –  chux - Reinstate Monica

Originally, I was going to make find be called by insert and delete to eliminate some replicated code. But, I didn't do that. Below is a refactored version that splits find into findnode [as I suggested] which can be called by findelement [as you suggested].

And, thanks for the compliment ...

// HASHTABLE

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>

#ifndef SIZE
#ifdef BIGSIZE
#define SIZE 512
#else
#define SIZE 8
#endif
#endif

typedef unsigned int hash_t;                // hash value
typedef unsigned int hidx_t;                // hash index

typedef struct nodez {
    struct nodez *next;
    const char *element;
    hash_t key;
} node_t;

typedef struct hash_table {
#if DYNAMIC_TABLE
    node_t **table;                         // dynamic table size
#else
    node_t *table[SIZE];                    // fixed table size
#endif
} hashtable;

#ifndef STRDUP
#define STRDUP  1                           // use strdup when storing entry
#endif

#define ARRAY_COUNT(_arr) \
    (sizeof(_arr) / sizeof(_arr[0]))

hashtable *
create_table(void)
{
    hashtable *ht = calloc(1,sizeof(hashtable));

#if DYNAMIC_TABLE
    ht->table = calloc(SIZE,sizeof(node_t *));
#endif

    return ht;
}

// strhash -- get hash key from string
hash_t
strhash(const char *str)
{
    hash_t hash = 0;

    for (;  *str != 0;  ++str)
        hash += (unsigned char) *str;

    return hash;
}

// hash -- get hash index from hash key
hidx_t
hash(hash_t key)
{

    // make the value acceptable
    key %= SIZE;

    return key;
}

node_t *
findnode(hashtable *ht,const char *element,hash_t key,node_t **aprev)
{

    assert(element != NULL);

    if (key == 0)
        key = strhash(element);

    hidx_t hidx = hash(key);
    node_t *cur = ht->table[hidx];

    node_t *prev = NULL;
    for (;  cur != NULL;  cur = cur->next) {
        if (key == cur->key) {
            if (strcmp(element,cur->element) == 0)
                break;
        }
        prev = cur;
    }

    if (aprev != NULL)
        *aprev = prev;

    return cur;
}

const char *
findelement(hashtable *ht,const char *element,hash_t key)
{

    node_t *cur = findnode(ht,element,key,NULL);

    if (cur != NULL)
        element = cur->element;
    else
        element = NULL;

    return element;
}

// insert -- head insertion
node_t *
insert(hashtable *ht,const char *element)
{

    assert(element != NULL);

    hash_t key = strhash(element);

    // point to table bucket
    hidx_t hidx = hash(key);
    node_t **head = &ht->table[hidx];

    // look for existing/duplicate element
    node_t *cur = findnode(ht,element,key,NULL);

    if (cur != NULL) {
        printf("insert: duplicate node -- '%s'\n",element);
        exit(1);
    }

    node_t *new_node = malloc(sizeof(node_t));
    if (new_node == NULL) {
        printf("Failed to allocate %s.\n", element);
        exit(1);
    }

    new_node->key = key;
#if STRDUP
    element = strdup(element);
#endif
    new_node->element = element;

    // add to head of linked list for this hash bucket
    new_node->next = *head;
    *head = new_node;

    return new_node;
}

bool
delete(hashtable *ht, const char *element)
{

    // compute hash value from string
    hash_t key = strhash(element);

    hidx_t hidx = hash(key);
    node_t **head = &ht->table[hidx];

    // look for a match
    node_t *prev;
    node_t *cur = findnode(ht,element,key,&prev);

    // node not found
    if (cur == NULL) {
        printf("delete: node not found -- '%s'\n",element);
        return false;
    }

    // remove the node from the linked list
    if (prev != NULL)
        prev->next = cur->next;
    else
        *head = cur->next;

    // release the string
#if STRDUP
    free((void *) cur->element);
#endif

    // release the node
    free(cur);

    return true;
}

// displays the hashtable
void
display_hashtable(hashtable *ht,const char *why)
{

    printf("\n");
    printf("TABLE: %s\n",why);

    for (hidx_t i = 0; i < SIZE; i++) {
        node_t *cur = ht->table[i];
        for (;  cur != NULL;  cur = cur->next)
            printf("Element: %s || Slot: %d || Key: %d.\n",
                cur->element, i, cur->key);
    }
}

int
main(void)
{

    hashtable *ht = create_table();

    const char *names[] = {
        "Francesco",
        "Daniela",
        "Pietro",
        "Priscilla",
        "Amber",
        "Roger",
        "Nelly",
        "Marco",
        "Susan",
        "Mary",
        "Ellen",
        "Fred",
        "Frank",
        "Dick",
        "Jane",
        "Nancy",
        "Elvira",
        "Dracula",
        "Frankenstein",
        "Pierre",
        "Alberto"
    };

    // insert all test names into table
    for (hidx_t nidx = 0;  nidx < ARRAY_COUNT(names);  ++nidx)
        insert(ht,names[nidx]);
    display_hashtable(ht,"All Names");

    // ensure we can find all the names
    printf("\n");
    printf("Checking table ...\n");
    int err = 0;
    for (hidx_t nidx = 0;  nidx < ARRAY_COUNT(names);  ++nidx) {
        const char *name = names[nidx];
        if (findnode(ht,name,0,NULL) == NULL) {
            printf("ERROR: name '%s' not found\n",name);
            err = 1;
        }
    }
    if (err)
        exit(1);
    printf("All names found\n");

    // delete all nodes in a random order
    int delcount = 0;
    while (delcount < ARRAY_COUNT(names)) {
        // get a random name to delete
        hidx_t delidx = rand() % ARRAY_COUNT(names);
        const char *name = names[delidx];

        // see if we have already deleted it
        if (name == NULL)
            continue;

        delete(ht,name);

        char why[100];
        sprintf(why,"After deleting %s",name);
        display_hashtable(ht,why);

        // mark it as deleted
        names[delidx] = NULL;
        ++delcount;
    }

    return 0;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • `hash(key)` does not certainly return a non-negative number. `insert(h, "\377");` can lead to a return value of -1. Better to use `unsigned hash(unsigned key) { key %= SIZE; return key; }` and `unsigned hidx = hash(key);`. – chux - Reinstate Monica Jan 02 '23 at 20:45
  • Disagree with "find might be more useful if it returned node_t instead of char * (i.e. the element value)." as that obliges calling to know about the structure of the hash table. HT details best left out of the public eye. Overall, many good points about this answer. – chux - Reinstate Monica Jan 02 '23 at 20:51
  • Note that identifiers ending in `_t` are [reserved by POSIX](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/V2_chap02.html#tag_15_02_02) – Andrew Henle Jan 02 '23 at 23:21
  • 1
    @AndrewHenle It's fairly common on SO (and real code [like the linux kernel]) to use the `_t` suffix for a type, particularly for teaching. And, IMO, it's _hubris_ for POSIX to claim this. It's more of a suggestion. If code conflicts, the programmer resolves it. Either by changing the name, or ensuring (e.g.) `#include ` is _not_ included anywhere there is a conflict. But, in 40+ years, I've never had a collision. I could solve the issue with `fromitz_hash_t` . Also, the `_` func prefix also is hubris [and conflicts with many other langs where `_foo` is a "private" function]. – Craig Estey Jan 03 '23 at 00:37
  • Hey Craig, thankyou very much for your patience, with the work you did above I was able to better understand the nonsense I was doing and, most importantly, learn something new. Apparently I can't cast your answer because of the lack of reputation points that I have, (I will remember to do that tho) plus, I'm not able to tag you for some reason. With the hope you see this comment, I wish you a nice day. – Pit Jan 06 '23 at 09:56