/** * 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 */