lccrt/lib/common/lccrt_hash.c

558 lines
13 KiB
C
Raw Normal View History

/**
* Part of the Lccrt Project, under the Apache License v2.0
* See http://www.apache.org/licenses/LICENSE-2.0.txt for license information.
* SPDX-License-Identifier: Apache-2.0
*
* lccrt_hash.c - работа с хеш-таблицами.
*/
#include "internal/lccrt_ctx.h"
lccrt_check_type_define( lccrt_hash_entry_t);
lccrt_check_type_define( lccrt_hash_t);
/**
* Запись.
*/
typedef struct lccrt_hash_entry_r
{
uintptr_t key; /* значение ключа */
uintptr_t data; /* пользовательские данные */
int64_t bucket_ident; /* номер корзины */
lccrt_hash_ptr table; /* исходная таблица */
lccrt_hash_entry_ptr prev; /* список записей в корзине */
lccrt_hash_entry_ptr next; /* список записей в корзине */
lccrt_check_type_t type_check;
} lccrt_hash_entry_t;
/**
* Таблица.
*/
typedef struct lccrt_hash_r
{
lccrt_ctx_ptr ctx;
lccrt_hash_key_type_t key_type; /* тип ключа, используемый в таблице */
int64_t num_entries; /* общее количество всех записей */
int64_t num_buckets; /* текущее количество корзин */
lccrt_hash_entry_ptr *buckets; /* массив корзин */
int64_t bucket_path_len; /* суммарный путь поиска по спискам */
lccrt_check_type_t type_check;
} lccrt_hash_t;
#define LCCRT_HASH_IS_REDUCE( h) \
( \
((h)->num_entries < (h)->num_buckets/8) \
&& ((h)->num_buckets > 8) \
)
/**
* Создание новой таблицы.
*/
lccrt_h_ptr
lccrt_hash_new( lccrt_ctx_ptr ctx, lccrt_hkt_t type)
{
lccrt_h_ptr r = lccrt_ctx_malloc( ctx, lccrt_hash_t);
memset( r, 0, sizeof( r[0]));
lccrt_check_type_init( r, lccrt_hash_t);
r->ctx = ctx;
r->key_type = type;
lccrt_assert( (0 <= type) && (type < LCCRT_HASH_KEY_LAST));
return (r);
} /* lccrt_hash_new */
/**
* Удаление данных записи.
*/
static void
lccrt_hash_entry_delete_internal( lccrt_hash_entry_ptr he)
{
lccrt_hash_ptr h = he->table;
lccrt_ctx_ptr ctx = h->ctx;
lccrt_check_type_assert( he, lccrt_hash_entry_t);
if ( (h->key_type == LCCRT_HASH_KEY_STRING) )
{
lccrt_ctx_free( ctx, (char *)he->key);
} else
{
lccrt_assert( h->key_type == LCCRT_HASH_KEY_INTPTR);
}
lccrt_check_type_done( he, lccrt_hash_entry_t);
lccrt_ctx_free( ctx, he);
return;
} /* lccrt_hash_entry_delete_internal */
/**
* Удаление таблицы.
*/
void
lccrt_hash_delete( lccrt_hash_ptr h)
{
int64_t i;
lccrt_ctx_ptr ctx = h->ctx;
lccrt_check_type_assert( h, lccrt_hash_t);
for ( i = 0; i < h->num_buckets; ++i )
{
lccrt_hash_entry_ptr he, he_next;
he = h->buckets[i];
while ( he )
{
he_next = he->next;
lccrt_assert( i == he->bucket_ident);
lccrt_hash_entry_delete_internal( he);
he = he_next;
}
}
lccrt_ctx_free( ctx, h->buckets);
lccrt_check_type_done( h, lccrt_hash_t);
lccrt_ctx_free( ctx, h);
return;
} /* lccrt_hash_delete */
/**
* Количество элементов в хеш-таблице.
*/
int64_t
lccrt_hash_length( lccrt_hash_ptr ht)
{
int64_t r = ht->num_entries;
return (r);
} /* lccrt_hash_length */
/**
* Вычисление хеш-значения для ключа.
*/
static uint64_t
lccrt_hash_key_calc( lccrt_hash_key_type_t type, uintptr_t key)
{
uint64_t r = 0;
if ( (type == LCCRT_HASH_KEY_INTPTR) )
{
r = (uint64_t)key * 1103515245ULL;
} else if ( (type == LCCRT_HASH_KEY_STRING) )
{
const char *p = (char *)key;
while ( p[0] )
{
r = ((r << 7) | (r >> (64 - 7))) ^ (uint64_t)p[0];
++p;
}
} else
{
lccrt_assert( 0);
}
return (r);
} /* lccrt_hash_key_calc */
/**
* Вычисление корзины для ключа.
*/
static int64_t
lccrt_hash_key_calc_pos( lccrt_hash_ptr h, uintptr_t key)
{
int64_t r = -1;
lccrt_check_type_assert( h, lccrt_hash_t);
if ( (h->num_buckets > 0) )
{
r = lccrt_hash_key_calc( h->key_type, key) % h->num_buckets;
}
return (r);
} /* lccrt_hash_key_calc_pos */
/**
* Сравнение двух ключей на равенство.
*/
static uintptr_t
lccrt_hash_key_copy( lccrt_ctx_ptr ctx, lccrt_hkt_t type, uintptr_t key)
{
uintptr_t r = 0;
if ( (type == LCCRT_HASH_KEY_STRING) )
{
r = (uintptr_t)lccrt_ctx_copy_str( ctx, (const char *)key);
} else if ( (type == LCCRT_HASH_KEY_INTPTR) )
{
r = key;
} else
{
lccrt_assert( 0);
}
return (r);
} /* lccrt_hash_key_copy */
/**
* Сравнение двух ключей на равенство.
*/
static int
lccrt_hash_keys_equal( lccrt_hkt_t type, uintptr_t key1, uintptr_t key2)
{
int r = 0;
if ( (type == LCCRT_HASH_KEY_STRING) )
{
r = lccrt_str_eq( (const char *)key1, (const char *)key2);
} else if ( (type == LCCRT_HASH_KEY_INTPTR) )
{
r = (key1 == key2);
} else
{
lccrt_assert( 0);
}
return (r);
} /* lccrt_hash_keys_equal */
/**
* Поиск в корзине записи с ключом заданного значения.
*/
static lccrt_hash_entry_ptr
lccrt_hash_bucket_find_key( lccrt_hash_entry_ptr he, uintptr_t key)
{
lccrt_hash_entry_ptr r = 0;
lccrt_check_type_assert( he, lccrt_hash_entry_t);
if ( he )
{
int64_t i = 0;
lccrt_hash_ptr h = he->table;
lccrt_hash_key_type_t type = h->key_type;
lccrt_check_type_assert( h, lccrt_hash_t);
if ( (type == LCCRT_HASH_KEY_INTPTR) )
{
for ( ; he ; he = he->next, i++ )
{
if ( (key == he->key) )
{
r = he;
break;
}
}
} else if ( (type == LCCRT_HASH_KEY_STRING) )
{
for ( ; he ; he = he->next, i++ )
{
if ( lccrt_hash_keys_equal( type, key, he->key) )
{
r = he;
break;
}
}
} else
{
lccrt_assert( 0);
}
h->bucket_path_len += i;
}
return (r);
} /* lccrt_hash_bucket_find_key */
/**
* Создать новую запись и вставить ее в список записей.
*/
static lccrt_hash_entry_ptr
lccrt_hash_entry_new( lccrt_hash_ptr h, uintptr_t key, uintptr_t data,
int64_t bucket_ident, lccrt_he_ptr prev, lccrt_he_ptr next)
{
lccrt_ctx_ptr ctx = h->ctx;
lccrt_hash_key_type_t type = h->key_type;
lccrt_hash_entry_ptr r = lccrt_ctx_malloc( ctx, lccrt_hash_entry_t);
lccrt_check_type_assert( h, lccrt_hash_t);
memset( r, 0, sizeof( r[0]));
lccrt_check_type_init( r, lccrt_hash_entry_t);
r->table = h;
r->bucket_ident = bucket_ident;
r->key = lccrt_hash_key_copy( ctx, type, key);
r->data = data;
r->prev = prev;
r->next = next;
if ( next )
{
next->prev = r;
}
if ( prev )
{
prev->next = r;
}
return (r);
} /* lccrt_hash_entry_new */
/**
* Увеличение/уменьшение количества корзин.
*/
static void
lccrt_hash_rebuild( lccrt_hash_ptr h, int is_increase)
{
lccrt_ctx_ptr ctx = h->ctx;
lccrt_hash_key_type_t type = h->key_type;
lccrt_check_type_assert( h, lccrt_hash_t);
h->bucket_path_len = 0;
if ( (h->num_entries > 0) )
{
int64_t i;
lccrt_hash_entry_ptr *buckets = h->buckets;
int64_t num_buckets = h->num_buckets;
h->num_buckets = is_increase ? 2*num_buckets : num_buckets/2;
lccrt_assert( num_buckets > 0);
h->buckets = lccrt_ctx_mallocn( ctx, lccrt_hash_entry_ptr, h->num_buckets);
memset( h->buckets, 0, h->num_buckets*sizeof( h->buckets[0]));
for ( i = 0; i < num_buckets; ++i )
{
lccrt_hash_entry_ptr e, e_next;
for ( e = buckets[i]; e; e = e_next )
{
int64_t key_pos = lccrt_hash_key_calc_pos( h, e->key);
e_next = e->next;
h->buckets[key_pos] = lccrt_hash_entry_new( h, e->key, e->data, key_pos, 0, h->buckets[key_pos]);
lccrt_hash_entry_delete_internal( e);
}
}
lccrt_ctx_free( ctx, buckets);
}
return;
} /* lccrt_hash_rebuild */
/**
* Добавить в таблицу запись с заданным ключом.
*/
lccrt_hash_entry_ptr
lccrt_hash_push( lccrt_hash_ptr h, uintptr_t key, int *is_new)
{
lccrt_hash_entry_ptr r = 0;
lccrt_ctx_ptr ctx = h->ctx;
lccrt_hash_key_type_t type = h->key_type;
int64_t key_pos = -1;
lccrt_hash_entry_ptr he = 0;
lccrt_check_type_assert( h, lccrt_hash_t);
if ( (h->num_buckets == 0) )
{
lccrt_assert( h->num_entries == 0);
h->num_buckets = 1;
h->buckets = lccrt_ctx_mallocn( ctx, lccrt_hash_entry_ptr, 1);
key_pos = 0;
he = 0;
} else
{
key_pos = lccrt_hash_key_calc_pos( h, key);
he = h->buckets[key_pos];
r = lccrt_hash_bucket_find_key( he, key);
}
if ( r )
{
lccrt_assign( is_new, 0);
} else
{
lccrt_assign( is_new, 1);
r = lccrt_hash_entry_new( h, key, 0, key_pos, 0, he);
h->buckets[key_pos] = r;
h->num_entries++;
}
#if 0
if ( (h->bucket_path_len >= h->num_entries) )
{
//lccrt_hash_rebuild( h);
}
#endif
if ( (h->num_entries > 0)
&& (h->num_entries >= 4*h->num_buckets) )
{
lccrt_hash_rebuild( h, 1);
r = lccrt_hash_find( h, key);
}
return (r);
} /* lccrt_hash_push */
/**
* Поиск записи.
*/
lccrt_hash_entry_ptr
lccrt_hash_find( lccrt_hash_ptr h, uintptr_t key)
{
lccrt_hash_entry_ptr r = 0;
lccrt_check_type_assert( h, lccrt_hash_t);
if ( (h->num_entries > 0)
&& (h->num_entries >= 4*h->num_buckets) )
{
lccrt_assert( 0);
//lccrt_hash_rebuild( h, 1);
}
if ( (h->num_entries > 0) )
{
int64_t key_pos = lccrt_hash_key_calc_pos( h, key);
lccrt_hash_entry_ptr he = h->buckets[key_pos];
r = lccrt_hash_bucket_find_key( he, key);
}
return (r);
} /* lccrt_hash_find */
/**
* Поиск записи.
*/
uintptr_t
lccrt_hash_remove( lccrt_hash_entry_ptr he)
{
lccrt_hash_entry_ptr r = 0;
lccrt_hash_entry_ptr he_prev = he->prev;
lccrt_hash_entry_ptr he_next = he->next;
lccrt_hash_ptr h = he->table;
lccrt_check_type_assert( he, lccrt_hash_entry_t);
lccrt_check_type_assert( h, lccrt_hash_t);
if ( he->next )
{
he_next->prev = he_prev;
}
if ( he_prev )
{
he_prev->next = he_next;
} else
{
h->buckets[he->bucket_ident] = he_next;
}
lccrt_hash_entry_delete_internal( he);
he = 0;
h->num_entries--;
if ( LCCRT_HASH_IS_REDUCE( h) )
{
lccrt_hash_rebuild( h, 0);
}
return (0);
} /* lccrt_hash_remove */
/**
* Получить пользовательские данные.
*/
uintptr_t
lccrt_hash_get_key( lccrt_hash_entry_ptr he)
{
uintptr_t r = he->key;
lccrt_check_type_assert( he, lccrt_hash_entry_t);
return (r);
} /* lccrt_hash_get_key */
/**
* Получить пользовательские данные.
*/
uintptr_t
lccrt_hash_get( lccrt_hash_entry_ptr he)
{
uintptr_t r = he->data;
lccrt_check_type_assert( he, lccrt_hash_entry_t);
return (r);
} /* lccrt_hash_get */
/**
* Получить пользовательские данные.
*/
uintptr_t
lccrt_hash_set( lccrt_hash_entry_ptr he, uintptr_t data)
{
uintptr_t r = lccrt_hash_get( he);
he->data = data;
return (r);
} /* lccrt_hash_set */
/**
* Получить первую запись.
*/
lccrt_hash_entry_ptr
lccrt_hash_first( lccrt_hash_ptr h)
{
int64_t i;
lccrt_hash_entry_ptr r = 0;
lccrt_check_type_assert( h, lccrt_hash_t);
if ( (h->num_entries > 0) )
{
for ( i = 0; i < h->num_buckets; ++i )
{
if ( h->buckets[i] )
{
r = h->buckets[i];
break;
}
}
}
return (r);
} /* lccrt_hash_first */
/**
* Получить следующую запись.
*/
lccrt_hash_entry_ptr
lccrt_hash_next( lccrt_hash_entry_ptr he)
{
lccrt_hash_entry_ptr r = 0;
lccrt_hash_ptr h = he->table;
lccrt_check_type_assert( he, lccrt_hash_entry_t);
lccrt_check_type_assert( h, lccrt_hash_t);
if ( he->next )
{
r = he->next;
} else
{
int64_t i;
for ( i = he->bucket_ident + 1; i < h->num_buckets; ++i )
{
if ( h->buckets[i] )
{
r = h->buckets[i];
break;
}
}
}
return (r);
} /* lccrt_hash_next */