558 lines
13 KiB
C
558 lines
13 KiB
C
/**
|
||
* 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 */
|