OP хранит ссылку на одно и то же значение, поэтому, конечно, все поиски выдают одно и то же значение (которое даже не является строкой, но каким бы ни было представлением в хранилище значения переменной i
).
Я предпочитаю создавать цепочки записей хеш-карты и сохранять копию хеш-функции в записи:
struct entry {
struct entry *next;
size_t hash;
void *data;
size_t data_size;
int data_type;
unsigned char name[];
};
typedef struct {
size_t size;
size_t used; /* Number of entries, total */
struct entry **slot; /* Array of entry pointers */
size_t (*hash)(const unsigned char *, size_t);
} hashmap;
int hashmap_new(hashmap *hmap, const size_t size,
size_t (*hash)(const unsigned char *, size_t))
{
if (!hmap)
return -1; /* No hashmap specified */
hmap->size = 0;
hmap->used = 0;
hmap->slot = NULL;
hmap->hash = NULL;
if (size < 1)
return -1; /* Invalid size */
if (!hash)
return -1; /* No hash function specified. */
hmap->slot = calloc(size, sizeof hmap->slot[0]);
if (!hmap->slot)
return -1; /* Not enough memory */
hmap->size = size;
hmap->hash = hash;
return 0;
}
void hashmap_free(hashmap *hmap)
{
if (hmap) {
size_t i = hmap->size;
while (i-->0) {
struct entry *next = hmap->slot[i];
struct entry *curr;
while (next) {
curr = next;
next = next->next;
free(curr->data);
/* Poison the entry, to help detect use-after-free bugs. */
curr->next = NULL;
curr->data = NULL;
curr->hash = 0;
curr->data_size = 0;
curr->data_type = 0;
curr->name[0] = '\0';
free(curr);
}
}
}
free(hmap->slot);
hmap->size = 0;
hmap->used = 0;
hmap->slot = NULL;
hmap->hash = NULL;
}
Чтобы вставить пару ключ-значение, функция либо использует данные, указанные как есть, и в этом случае вызывающая сторона несет ответственность за обеспечение того, чтобы каждый ключ имел свои уникальные данные, не перезаписанные позже; или мы копируем данные пользователя. В приведенной выше функции hashmap_free()
вы увидите free(curr->data);
; он предполагает, что мы распределили память динамически и скопировали туда данные пользователя. Итак:
int hashmap_add(hashmap *hmap, const unsigned char *name,
const void *data, const size_t data_size,
const int data_type)
{
const size_t namelen = (name) ? strlen(name) : 0;
struct entry *curr;
size_t i;
if (!hmap)
return -1; /* No hashmap specified. */
if (name_len < 1)
return -1; /* NULL or empty name. */
/* Allocate memory for the hashmap entry,
including enough room for the name, and end of string '\0'. */
curr = malloc(sizeof (struct entry) + namelen + 1;
if (!curr)
return -1; /* Out of memory. */
/* Copy data, if any. */
if (data_size > 0) {
curr->data = malloc(data_size);
if (!curr->data) {
free(curr);
return -1; /* Out of memory. */
}
memcpy(curr->data, data, data_size);
} else {
curr->data = NULL;
curr->data_size = 0;
}
curr->data_type = data_type;
/* Calculate the hash of the name. */
curr->hash = hmap->hash(name, namelen);
/* Copy name, including the trailing '\0'. */
memcpy(curr->name, name, namelen + 1);
/* Slot to prepend to. */
i = curr->hash % hmap->size;
curr->next = hmap->slot[i];
hmap->slot[i] = curr;
/* An additional node added. */
hmap->used++;
return 0;
}
Значение data_type
полностью зависит от пользователя кода.
Поиск может быть сделан на основе хеша и типа данных:
/* Returns 0 if found. */
int hashmap_find(hashmap *hmap, const unsigned char *name,
const int data_type,
void **dataptr_to, size_t *size_to)
{
struct entry *curr;
size_t hash;
if (size_to)
*size_to = 0;
if (dataptr_to)
*dataptr_to = NULL;
if (!hmap)
return -1; /* No hashmap specified. */
if (!name || !*name)
return -1; /* NULL or empty name. */
hash = hmap->hash(name, strlen(name));
curr = hmap->slot[hash % hmap->size];
for (curr = hmap->slot[hash % hmap->size]; curr != NULL; curr = curr->next) {
if (curr->data_type == data_type && curr->hash == hash &&
!strcmp(curr->name, name)) {
/* Data type an name matches. Save size if requested. */
if (size_to)
*size_to = curr->data_size;
if (dataptr_to)
*dataptr_to = curr->data;
return 0; /* Found. */
}
}
return -1; /* Not found. */
}
Приведенный выше поиск возвращает 0, если найдено, и ненулевое значение, если ошибка или не найдена. (Таким образом, даже нулевые данные NULL могут быть сохранены в хэш-карте.)
Если число поддерживаемых типов данных невелико, скажем, 32, то с помощью unsigned int
с каждым битом (1U<<0 == 1
, 1U<<1 == 2
, 1U<<2 == 4
и т. Д.), Зарезервированным для определенного типа, вы можете выполнить поиск, используя маску, разрешающую только указанные типы. Точно так же data_type
может быть маской, описывающей, к каким типам может быть интерпретировано значение (почти всегда будет установлен только один бит).
Эта схема также позволяет динамически изменять размер хэш-карты, выделяя новый массив slot
указателей и перемещая каждую старую запись в новую. Ключи не нужно перефразировать, потому что оригинальный хеш хранится в каждой записи. Для эффективности поиска цепи (свисающие с каждой щели) должны быть как можно короче. Общее "правило большого пальца" состоит в том, что hashmap->size
должно быть между hashmap->used
и 2 * hashmap->used
.