lccrt/lib/common/lccrt_hash.c

558 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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