Update to TommyDS v4.0

This commit is contained in:
Andrea Mazzoleni 2026-10-07 18:44:12 +02:00
parent a39fb132f8
commit 8e7b4c6c8f
14 changed files with 1114 additions and 535 deletions

View file

@ -1604,8 +1604,8 @@ void fs_allocate(struct snapraid_disk* disk, block_off_t parity_pos, struct snap
extent = extent_alloc(parity_pos, file, file_pos, count);
/* insert the extent in the trees */
parity_extent = tommy_tree_insert(&disk->fs_parity, &extent->parity_node, extent);
file_extent = tommy_tree_insert(&disk->fs_file, &extent->file_node, extent);
parity_extent = tommy_tree_insert_unique(&disk->fs_parity, &extent->parity_node, extent);
file_extent = tommy_tree_insert_unique(&disk->fs_file, &extent->file_node, extent);
if (parity_extent != extent || file_extent != extent) {
/* LCOV_EXCL_START */
@ -1689,8 +1689,8 @@ void fs_deallocate(struct snapraid_disk* disk, block_off_t parity_pos)
second_extent = extent_alloc(extent->parity_pos + first_count + 1, extent->file, extent->file_pos + first_count + 1, second_count);
/* insert the extent in the trees */
parity_extent = tommy_tree_insert(&disk->fs_parity, &second_extent->parity_node, second_extent);
file_extent = tommy_tree_insert(&disk->fs_file, &second_extent->file_node, second_extent);
parity_extent = tommy_tree_insert_unique(&disk->fs_parity, &second_extent->parity_node, second_extent);
file_extent = tommy_tree_insert_unique(&disk->fs_file, &second_extent->file_node, second_extent);
if (parity_extent != second_extent || file_extent != second_extent) {
/* LCOV_EXCL_START */

View file

@ -51,25 +51,25 @@ struct hash_test_vector {
* Test vectors for tommy_hash32
*/
static struct hash32_test_vector TEST_HASH32[] = {
{ "", 0, 0x8614384c, 0xa766795d },
{ "a", 1, 0x12c16c36, 0xa766795d },
{ "abc", 3, 0xc58e8af5, 0xa766795d },
{ "message digest", 14, 0x006b32f1, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x7e6fcfe0, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x8604adf8, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 43, 0xdeba3d3a, 0xa766795d },
{ "\x00", 1, 0x4a7d1c33, 0xa766795d },
{ "\x16\x27", 2, 0x8b50899b, 0xa766795d },
{ "\xe2\x56\xb4", 3, 0x60406493, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 4, 0xa049144a, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 5, 0x4da2c2f1, 0xa766795d },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x59de30cf, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x219e149c, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x25067520, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xa1f368d8, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x805fc63d, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x7f75dd0f, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0xb9154382, 0xa766795d },
{ "", 0, 0x3ba63d24, 0xa766795d },
{ "a", 1, 0xc27589e2, 0xa766795d },
{ "abc", 3, 0x4f0589dd, 0xa766795d },
{ "message digest", 14, 0x7bd2e191, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0xa1eebba8, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xc84d4c51, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 43, 0x294e8a7b, 0xa766795d },
{ "\x00", 1, 0x3e1fbed8, 0xa766795d },
{ "\x16\x27", 2, 0x00233fc6, 0xa766795d },
{ "\xe2\x56\xb4", 3, 0xd3f61ec1, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 4, 0xfa10da80, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 5, 0xcd5e7468, 0xa766795d },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x8ba76f9b, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xbf2129d0, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xc3d26370, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xa6840538, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x36115feb, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x48a3b1fb, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x23412f54, 0xa766795d },
{ 0, 0, 0, 0 }
};
@ -77,29 +77,29 @@ static struct hash32_test_vector TEST_HASH32[] = {
* Test vectors for tommy_strhash32
*/
struct strhash32_test_vector TEST_STRHASH32[] = {
{ "", 0x0af1416d, 0xa766795d },
{ "a", 0x68fa0f3f, 0xa766795d },
{ "abc", 0xfc68ffc5, 0xa766795d },
{ "message digest", 0x08477b63, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 0x5b9c25e5, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x1e530ce7, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 0xaf93eefe, 0xa766795d },
{ "\xff", 0xfc88801b, 0xa766795d },
{ "\x16\x27", 0xcd7216db, 0xa766795d },
{ "\xe2\x56\xb4", 0x05f98d02, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 0xf65206f8, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 0x72bd6bda, 0xa766795d },
{ "\xff\x7e\xdf\x1e\x31\x1c", 0x57dfb9b4, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0x499ff634, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0xe896b7ce, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0xfe3939f0, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0x4351d482, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x88e92135, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x01109c16, 0xa766795d },
{ "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xbcb050dc, 0xa766795d },
{ "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xbe5e1fd5, 0xa766795d },
{ "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x70d8c97f, 0xa766795d },
{ "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x957440a9, 0xa766795d },
{ "", 0x3ba63d24, 0xa766795d },
{ "a", 0xc27589e2, 0xa766795d },
{ "abc", 0x4f0589dd, 0xa766795d },
{ "message digest", 0x7bd2e191, 0xa766795d },
{ "abcdefghijklmnopqrstuvwxyz", 0xa1eebba8, 0xa766795d },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0xc84d4c51, 0xa766795d },
{ "The quick brown fox jumps over the lazy dog", 0x294e8a7b, 0xa766795d },
{ "\xff", 0x6ae84401, 0xa766795d },
{ "\x16\x27", 0x00233fc6, 0xa766795d },
{ "\xe2\x56\xb4", 0xd3f61ec1, 0xa766795d },
{ "\xc9\x4d\x9c\xda", 0xfa10da80, 0xa766795d },
{ "\x79\xf1\x29\x69\x5d", 0xcd5e7468, 0xa766795d },
{ "\xff\x7e\xdf\x1e\x31\x1c", 0xe135b2e2, 0xa766795d },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0xbf2129d0, 0xa766795d },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0xc3d26370, 0xa766795d },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0xa6840538, 0xa766795d },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0x36115feb, 0xa766795d },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0xef961a79, 0xa766795d },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x23412f54, 0xa766795d },
{ "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0x04332eac, 0xa766795d },
{ "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xd3025b16, 0xa766795d },
{ "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x4ce44257, 0xa766795d },
{ "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x47d86f33, 0xa766795d },
{ 0, 0, 0 }
};
@ -107,25 +107,25 @@ struct strhash32_test_vector TEST_STRHASH32[] = {
* Test vectors for tommy_hash64
*/
static struct hash64_test_vector TEST_HASH64[] = {
{ "", 0, 0x8614384cb5165fbfULL, 0x2f022773a766795dULL },
{ "a", 1, 0x1a2e0298a8e94a3dULL, 0x2f022773a766795dULL },
{ "abc", 3, 0x7555796b7a7d21ebULL, 0x2f022773a766795dULL },
{ "message digest", 14, 0x9411a57d04b92fb4ULL, 0x2f022773a766795dULL },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x3ca3f8d2b4e69832ULL, 0x2f022773a766795dULL },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x6dae542ba0015a4dULL, 0x2f022773a766795dULL },
{ "The quick brown fox jumps over the lazy dog", 43, 0xe06d8cbb3d2ea1a6ULL, 0x2f022773a766795dULL },
{ "\x00", 1, 0x201e664fb5f2c021ULL, 0x2f022773a766795dULL },
{ "\x16\x27", 2, 0xef42fa8032c4b775ULL, 0x2f022773a766795dULL },
{ "\xe2\x56\xb4", 3, 0x6e6c498a6688466cULL, 0x2f022773a766795dULL },
{ "\xc9\x4d\x9c\xda", 4, 0x5195005419905423ULL, 0x2f022773a766795dULL },
{ "\x79\xf1\x29\x69\x5d", 5, 0x221235b48afee7c1ULL, 0x2f022773a766795dULL },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x1b1f18b9266f095bULL, 0x2f022773a766795dULL },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x2cbafa8e741d49caULL, 0x2f022773a766795dULL },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x4677f04c06e0758dULL, 0x2f022773a766795dULL },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x5afe09e8214e2163ULL, 0x2f022773a766795dULL },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x115b6276d209fab6ULL, 0x2f022773a766795dULL },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xd0636d2f01cf3a3eULL, 0x2f022773a766795dULL },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x6d259f5fef74f93eULL, 0x2f022773a766795dULL },
{ "", 0, 0xf1999eea212f89e6ULL, 0x2f022773a766795dULL },
{ "a", 1, 0x6af55c57ef3cc6e2ULL, 0x2f022773a766795dULL },
{ "abc", 3, 0x8a09859573f3ba72ULL, 0x2f022773a766795dULL },
{ "message digest", 14, 0x08a48193f341bef5ULL, 0x2f022773a766795dULL },
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x8761b9f83f99b36dULL, 0x2f022773a766795dULL },
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x6f9a23bb4217ad12ULL, 0x2f022773a766795dULL },
{ "The quick brown fox jumps over the lazy dog", 43, 0x33e3651d073f9942ULL, 0x2f022773a766795dULL },
{ "\x00", 1, 0x9360785dad01a14eULL, 0x2f022773a766795dULL },
{ "\x16\x27", 2, 0x9d411c7d088f8c1eULL, 0x2f022773a766795dULL },
{ "\xe2\x56\xb4", 3, 0xa20865547db581afULL, 0x2f022773a766795dULL },
{ "\xc9\x4d\x9c\xda", 4, 0xb4eb664b81e7058dULL, 0x2f022773a766795dULL },
{ "\x79\xf1\x29\x69\x5d", 5, 0xf0b386ad052571b4ULL, 0x2f022773a766795dULL },
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0xdd3b8851d859c7d4ULL, 0x2f022773a766795dULL },
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xa8b94da524aabf49ULL, 0x2f022773a766795dULL },
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xb913a803aea6d51dULL, 0x2f022773a766795dULL },
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xb6961500a25f63beULL, 0x2f022773a766795dULL },
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xfa6dccb9650a1d28ULL, 0x2f022773a766795dULL },
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xc95d0a87ec8c2a98ULL, 0x2f022773a766795dULL },
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x15901950afd60cd2ULL, 0x2f022773a766795dULL },
{ 0, 0, 0, 0 }
};
@ -188,6 +188,13 @@ static void test_hash(void)
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
digest = tommy_hash_u32(TEST_STRHASH32[i].seed, buffer_aligned, strlen(TEST_STRHASH32[i].data));
if (digest != TEST_STRHASH32[i].digest) {
/* LCOV_EXCL_START */
log_fatal(EINTERNAL, "Failed strhash32 test\n");
exit(EXIT_FAILURE);
/* LCOV_EXCL_STOP */
}
}
for (i = 0; TEST_HASH64[i].data; ++i) {
@ -584,7 +591,11 @@ static void test_tommy(void)
tommy_tree_insert(&tree, &node[i], (void*)(uintptr_t)(i + 1));
/* try to insert a duplicate, count should not change */
tommy_tree_insert(&tree, &node[TOMMY_SIZE], (void*)(uintptr_t)1);
if (tommy_tree_insert_unique(&tree, &node[TOMMY_SIZE], (void*)(uintptr_t)1) != (void*)(uintptr_t)1) {
/* LCOV_EXCL_START */
goto bail;
/* LCOV_EXCL_STOP */
}
if (tommy_tree_count(&tree) != TOMMY_SIZE) {
/* LCOV_EXCL_START */

View file

@ -119,3 +119,4 @@ tommy_inline tommy_size_t tommy_array_size(tommy_array* array)
TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array);
#endif

View file

@ -87,3 +87,4 @@ tommy_inline tommy_size_t tommy_arrayblkof_size(tommy_arrayblkof* array)
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array);
#endif

View file

@ -65,7 +65,8 @@ tommy_inline void tommy_chain_concat(tommy_node* first_tail, tommy_node* second_
/**
* Merges two chains.
* \param first First chain (will contain the result).
* \param second Second chain (will be empty after the merge).
* \param second Second chain (consumed by the merge; its descriptor is not cleared
* and must not be used as an independent chain afterward).
* \param cmp Comparison function.
*/
tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp)
@ -101,7 +102,8 @@ tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tom
* Merges two chains managing special degenerated cases.
* It's functionally equivalent to tommy_chain_merge() but faster with already ordered chains.
* \param first First chain (will contain the result).
* \param second Second chain (will be empty after the merge).
* \param second Second chain (consumed by the merge; its descriptor is not cleared
* and must not be used as an independent chain afterward).
* \param cmp Comparison function.
*/
tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp)
@ -149,7 +151,7 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func*
*/
tommy_chain bit[TOMMY_SIZE_BIT + 1];
/**
/*
* Value stored inside the bit bucket.
* It's used to know which bucket is empty or full.
*/
@ -207,3 +209,4 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func*
}
#endif

View file

@ -6,7 +6,7 @@
/******************************************************************************/
/* hash */
#include <string.h> /* for memcpy */
#include <string.h> /* for memcpy, strlen */
/**
* Swap endianness.
@ -14,13 +14,26 @@
*/
#if defined(__GNUC__)
#define tommy_swap32(x) __builtin_bswap32(x)
#define tommy_swap64(x) __builtin_bswap64(x)
#else
tommy_inline tommy_uint32_t tommy_swap32(tommy_uint32_t v)
{
return ((v & 0xFF000000) >> 24) |
((v & 0x00FF0000) >> 8) |
((v & 0x0000FF00) << 8) |
((v & 0x000000FF) << 24);
((v & 0x00FF0000) >> 8) |
((v & 0x0000FF00) << 8) |
((v & 0x000000FF) << 24);
}
tommy_inline tommy_uint64_t tommy_swap64(tommy_uint64_t v)
{
return ((v & 0xFF00000000000000ULL) >> 56) |
((v & 0x00FF000000000000ULL) >> 40) |
((v & 0x0000FF0000000000ULL) >> 24) |
((v & 0x000000FF00000000ULL) >> 8) |
((v & 0x00000000FF000000ULL) << 8) |
((v & 0x0000000000FF0000ULL) << 24) |
((v & 0x000000000000FF00ULL) << 40) |
((v & 0x00000000000000FFULL) << 56);
}
#endif
@ -35,200 +48,237 @@ tommy_inline tommy_uint32_t tommy_le_uint32_read(const void* ptr)
return v;
}
tommy_inline tommy_uint64_t tommy_le_uint64_read(const void* ptr)
{
tommy_uint64_t v;
memcpy(&v, ptr, sizeof(v));
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
v = tommy_swap64(v);
#endif
return v;
}
#define tommy_rot(x, k) \
(((x) << (k)) | ((x) >> (32 - (k))))
#define tommy_mix(a, b, c) \
do { \
a -= c; a ^= tommy_rot(c, 4); c += b; \
b -= a; b ^= tommy_rot(a, 6); a += c; \
c -= b; c ^= tommy_rot(b, 8); b += a; \
a -= c; a ^= tommy_rot(c, 16); c += b; \
b -= a; b ^= tommy_rot(a, 19); a += c; \
c -= b; c ^= tommy_rot(b, 4); b += a; \
} while (0)
#define tommy_final(a, b, c) \
do { \
c ^= b; c -= tommy_rot(b, 14); \
a ^= c; a -= tommy_rot(c, 11); \
b ^= a; b -= tommy_rot(a, 25); \
c ^= b; c -= tommy_rot(b, 16); \
a ^= c; a -= tommy_rot(c, 4); \
b ^= a; b -= tommy_rot(a, 14); \
c ^= b; c -= tommy_rot(b, 24); \
} while (0)
#define tommy_rot64(x, k) \
(((x) << (k)) | ((x) >> (64 - (k))))
TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len)
{
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
tommy_uint32_t a, b, c;
const tommy_uint32_t c1 = 0xcc9e2d51;
const tommy_uint32_t c2 = 0x1b873593;
tommy_uint32_t h = init_val;
tommy_size_t nblocks = key_len / 4;
tommy_size_t i;
const unsigned char* tail;
tommy_uint32_t k;
a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val;
while (key_len > 12) {
a += tommy_le_uint32_read(key + 0);
b += tommy_le_uint32_read(key + 4);
c += tommy_le_uint32_read(key + 8);
tommy_mix(a, b, c);
key_len -= 12;
key += 12;
for (i = 0; i < nblocks; ++i) {
k = tommy_le_uint32_read(key + i * 4);
k *= c1;
k = tommy_rot(k, 15);
k *= c2;
h ^= k;
h = tommy_rot(h, 13);
h = h * 5 + 0xe6546b64;
}
switch (key_len) {
case 0 :
return c; /* used only when called with a zero length */
case 12 :
c += tommy_le_uint32_read(key + 8);
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */
case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */
case 9 : c += key[8]; /* fallthrough */
case 8 :
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */
case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */
case 5 : b += key[4]; /* fallthrough */
case 4 :
a += tommy_le_uint32_read(key + 0);
break;
case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */
case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */
case 1 : a += key[0]; /* fallthrough */
/* avoid pointer arithmetic on a null buffer for empty input */
tail = key_len ? key + nblocks * 4 : key;
k = 0;
switch (key_len & 3) {
case 3 : k ^= ((tommy_uint32_t)tail[2]) << 16; /* fallthrough */
case 2 : k ^= ((tommy_uint32_t)tail[1]) << 8; /* fallthrough */
case 1 : k ^= (tommy_uint32_t)tail[0];
k *= c1;
k = tommy_rot(k, 15);
k *= c2;
h ^= k;
}
tommy_final(a, b, c);
h ^= (tommy_uint32_t)key_len;
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return c;
return h;
}
TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len)
{
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
tommy_uint32_t a, b, c;
const tommy_uint64_t c1 = 0x87c37b91114253d5ULL;
const tommy_uint64_t c2 = 0x4cf5ad432745937fULL;
tommy_uint64_t h = init_val;
tommy_size_t nblocks = key_len / 8;
tommy_size_t i;
const unsigned char* tail;
tommy_uint64_t k;
a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff);
c += init_val >> 32;
while (key_len > 12) {
a += tommy_le_uint32_read(key + 0);
b += tommy_le_uint32_read(key + 4);
c += tommy_le_uint32_read(key + 8);
tommy_mix(a, b, c);
key_len -= 12;
key += 12;
for (i = 0; i < nblocks; ++i) {
k = tommy_le_uint64_read(key + i * 8);
k *= c1;
k = tommy_rot64(k, 31);
k *= c2;
h ^= k;
h = tommy_rot64(h, 27);
h = h * 5 + 0x52dce729ULL;
}
switch (key_len) {
case 0 :
return c + ((tommy_uint64_t)b << 32); /* used only when called with a zero length */
case 12 :
c += tommy_le_uint32_read(key + 8);
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */
case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */
case 9 : c += key[8]; /* fallthrough */
case 8 :
b += tommy_le_uint32_read(key + 4);
a += tommy_le_uint32_read(key + 0);
break;
case 7 : b += ((tommy_uint32_t)key[6]) << 16; /* fallthrough */
case 6 : b += ((tommy_uint32_t)key[5]) << 8; /* fallthrough */
case 5 : b += key[4]; /* fallthrough */
case 4 :
a += tommy_le_uint32_read(key + 0);
break;
case 3 : a += ((tommy_uint32_t)key[2]) << 16; /* fallthrough */
case 2 : a += ((tommy_uint32_t)key[1]) << 8; /* fallthrough */
case 1 : a += key[0]; /* fallthrough */
/* avoid pointer arithmetic on a null buffer for empty input */
tail = key_len ? key + nblocks * 8 : key;
k = 0;
switch (key_len & 7) {
case 7 : k ^= ((tommy_uint64_t)tail[6]) << 48; /* fallthrough */
case 6 : k ^= ((tommy_uint64_t)tail[5]) << 40; /* fallthrough */
case 5 : k ^= ((tommy_uint64_t)tail[4]) << 32; /* fallthrough */
case 4 : k ^= ((tommy_uint64_t)tail[3]) << 24; /* fallthrough */
case 3 : k ^= ((tommy_uint64_t)tail[2]) << 16; /* fallthrough */
case 2 : k ^= ((tommy_uint64_t)tail[1]) << 8; /* fallthrough */
case 1 : k ^= (tommy_uint64_t)tail[0];
k *= c1;
k = tommy_rot64(k, 31);
k *= c2;
h ^= k;
}
tommy_final(a, b, c);
h ^= (tommy_uint64_t)key_len;
h ^= h >> 33;
h *= 0xff51afd7ed558ccdULL;
h ^= h >> 33;
h *= 0xc4ceb9fe1a85ec53ULL;
h ^= h >> 33;
return c + ((tommy_uint64_t)b << 32);
return h;
}
TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key)
{
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
tommy_uint32_t a, b, c;
tommy_uint32_t m[3] = { 0xff, 0xff00, 0xff0000 };
const unsigned char* p = tommy_cast(const unsigned char*, void_key);
const tommy_uint32_t c1 = 0xcc9e2d51;
const tommy_uint32_t c2 = 0x1b873593;
tommy_uint32_t h = init_val;
tommy_size_t len = 0;
a = b = c = 0xdeadbeef + init_val;
/* this is different than original lookup3 and the result won't match */
if (((tommy_uintptr_t)p & 3) != 0)
return tommy_hash_u32(init_val, void_key, strlen(tommy_cast(const char*, void_key)));
/*
* Fast path: string pointer is already aligned to 4 bytes.
* This assumes page-granularity protection and page boundaries aligned to
* 4 bytes, so each aligned load stays within a readable page.
* The final load may read up to 3 bytes past the terminator and outside
* the allocated object. Staying within the page avoids a page-boundary
* fault, but an out-of-object read is still undefined behavior in ISO C
* and can be reported by ASan. This path assumes the compiler and runtime
* tolerate these overreads.
*/
while (1) {
tommy_uint32_t v = tommy_le_uint32_read(key);
tommy_uint32_t v = tommy_le_uint32_read(p);
if (tommy_haszero_u32(v)) {
if (v & m[0]) {
a += v & m[0];
if (v & m[1]) {
a += v & m[1];
if (v & m[2])
a += v & m[2];
}
}
tommy_uint32_t z = (v - 0x01010101) & ~v & 0x80808080;
tommy_uint_t zero_idx = tommy_ctz_u32(z) / 8;
if (zero_idx != 0) {
tommy_uint32_t mask = (1U << (zero_idx * 8)) - 1;
tommy_uint32_t k = v & mask;
k *= c1;
k = tommy_rot(k, 15);
k *= c2;
h ^= k;
len += zero_idx;
}
break;
}
a += v;
tommy_uint32_t k = v;
k *= c1;
k = tommy_rot(k, 15);
k *= c2;
h ^= k;
h = tommy_rot(h, 13);
h = h * 5 + 0xe6546b64;
v = tommy_le_uint32_read(key + 4);
if (tommy_haszero_u32(v)) {
if (v & m[0]) {
b += v & m[0];
if (v & m[1]) {
b += v & m[1];
if (v & m[2])
b += v & m[2];
}
}
break;
}
b += v;
v = tommy_le_uint32_read(key + 8);
if (tommy_haszero_u32(v)) {
if (v & m[0]) {
c += v & m[0];
if (v & m[1]) {
c += v & m[1];
if (v & m[2])
c += v & m[2];
}
}
break;
}
c += v;
tommy_mix(a, b, c);
key += 12;
p += 4;
len += 4;
}
/* for lengths that are multipliers of 12 we already have called mix */
/* this is different than the original lookup3 and the result won't match */
h ^= len;
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
tommy_final(a, b, c);
return c;
return h;
}
TOMMY_API tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key)
{
const unsigned char* p = tommy_cast(const unsigned char*, void_key);
const tommy_uint64_t c1 = 0x87c37b91114253d5ULL;
const tommy_uint64_t c2 = 0x4cf5ad432745937fULL;
tommy_uint64_t h = init_val;
tommy_size_t len = 0;
if (((tommy_uintptr_t)p & 7) != 0)
return tommy_hash_u64(init_val, void_key, strlen(tommy_cast(const char*, void_key)));
/*
* Fast path: string pointer is already aligned to 8 bytes.
* This assumes page-granularity protection and page boundaries aligned to
* 8 bytes, so each aligned load stays within a readable page.
* The final load may read up to 7 bytes past the terminator and outside
* the allocated object. Staying within the page avoids a page-boundary
* fault, but an out-of-object read is still undefined behavior in ISO C
* and can be reported by ASan. This path assumes the compiler and runtime
* tolerate these overreads.
*/
while (1) {
tommy_uint64_t v = tommy_le_uint64_read(p);
if (tommy_haszero_u64(v)) {
tommy_uint64_t z = (v - 0x0101010101010101ULL) & ~v & 0x8080808080808080ULL;
tommy_uint_t zero_idx = tommy_ctz_u64(z) / 8;
if (zero_idx != 0) {
tommy_uint64_t mask = (1ULL << (zero_idx * 8)) - 1;
tommy_uint64_t k = v & mask;
k *= c1;
k = tommy_rot64(k, 31);
k *= c2;
h ^= k;
len += zero_idx;
}
break;
}
tommy_uint64_t k = v;
k *= c1;
k = tommy_rot64(k, 31);
k *= c2;
h ^= k;
h = tommy_rot64(h, 27);
h = h * 5 + 0x52dce729ULL;
p += 8;
len += 8;
}
h ^= len;
h ^= h >> 33;
h *= 0xff51afd7ed558ccdULL;
h ^= h >> 33;
h *= 0xc4ceb9fe1a85ec53ULL;
h ^= h >> 33;
return h;
}

View file

@ -15,12 +15,8 @@
/**
* Hash function with a 32 bits result.
* Implementation of the Robert Jenkins "lookup3" hash 32 bits version,
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle().
*
* This hash is designed to provide a good overall performance on all platforms,
* including 32 bits. If you target only 64 bits, you can use faster hashes,
* like SpookyHash or FarmHash.
* Implementation of MurmurHash3 (x86_32) by Austin Appleby,
* from https://github.com/aappleby/smhasher
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
@ -28,19 +24,19 @@
* \param void_key Pointer to the data to hash.
* \param key_len Size of the data to hash.
* \note
* This function is endianness independent.
* This function is endianness independent and alignment independent.
* It produces identical hash results to ::tommy_strhash_u32 for 0-terminated strings.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is not state-of-the-art against malicious collision attacks, but it is simple and fast.
* \return The hash value of 32 bits.
*/
TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len);
/**
* Hash function with a 64 bits result.
* Implementation of the Robert Jenkins "lookup3" hash 64 bits version,
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle2().
*
* This hash is designed to provide a good overall performance on all platforms,
* including 32 bits. If you target only 64 bits, you can use faster hashes,
* like SpookyHash or FarmHash.
* Implementation of MurmurHash3 (x64_64) by Austin Appleby,
* from https://github.com/aappleby/smhasher
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
@ -48,29 +44,57 @@ TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* voi
* \param void_key Pointer to the data to hash.
* \param key_len Size of the data to hash.
* \note
* This function is endianness independent.
* This function is endianness independent and alignment independent.
* It produces identical hash results to ::tommy_strhash_u64 for 0-terminated strings.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is not state-of-the-art against malicious collision attacks, but it is simple and fast.
* \return The hash value of 64 bits.
*/
TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len);
/**
* String hash function with a 32 bits result.
* Implementation is based on Robert Jenkins "lookup3" hash 32 bits version,
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle().
* Implementation is based on MurmurHash3 (x86_32) by Austin Appleby,
* from https://github.com/aappleby/smhasher
*
* This hash is designed to handle strings with an unknown length. If you
* know the string length, the other hash functions are surely faster.
* This hash is designed to handle strings with an unknown length.
* If the string is aligned, it operates in a single pass without calling strlen().
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
* Use 0 if not relevant.
* \param void_key Pointer to the string to hash. It has to be 0 terminated.
* \note
* This function is endianness independent.
* This function is endianness independent and alignment independent.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is not state-of-the-art against malicious collision attacks, but it is simple and fast.
* \return The hash value of 32 bits.
*/
TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key);
/**
* String hash function with a 64 bits result.
* Implementation is based on MurmurHash3 (x64_64) by Austin Appleby,
* from https://github.com/aappleby/smhasher
*
* This hash is designed to handle strings with an unknown length.
* If the string is aligned, it operates in a single pass without calling strlen().
*
* \param init_val Initialization value.
* Using a different initialization value, you can generate a completely different set of hash values.
* Use 0 if not relevant.
* \param void_key Pointer to the string to hash. It has to be 0 terminated.
* \note
* This function is endianness independent and alignment independent.
* Compatibility across different versions of the library is not guaranteed:
* this hash is intended for in-memory runtime use only and not for persistent storage.
* It is not state-of-the-art against malicious collision attacks, but it is simple and fast.
* \return The hash value of 64 bits.
*/
TOMMY_API tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key);
/**
* Integer reversible hash function for 32 bits.
* Implementation of the Robert Jenkins "4-byte Integer Hashing",
@ -108,3 +132,4 @@ tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key)
}
#endif

View file

@ -4,6 +4,8 @@
#include "tommyhashdyn.h"
#include "tommylist.h"
#include <string.h> /* for memset */
/******************************************************************************/
/* hashdyn */
@ -23,6 +25,12 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn)
tommy_free(hashdyn->bucket);
}
TOMMY_API void tommy_hashdyn_clear(tommy_hashdyn* hashdyn)
{
memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*));
hashdyn->count = 0;
}
/**
* Resize the bucket vector.
*/
@ -37,57 +45,70 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket
if (!hashdyn->count) {
/* empty table: allocate zeroed memory with calloc without looping */
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*)));
} else if (new_bucket_bit == bucket_bit + 1) {
tommy_size_t i;
/* grow by 1 bit: zero the two buckets inline in loop to preserve cache locality */
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*)));
for (i = 0; i < bucket_max; ++i) {
tommy_hashdyn_node* j;
new_bucket[i] = 0;
new_bucket[i + bucket_max] = 0;
j = hashdyn->bucket[i];
while (j) {
tommy_hashdyn_node* j_next = j->next;
tommy_size_t pos = j->index & new_bucket_mask;
if (new_bucket[pos])
tommy_list_insert_tail_not_empty(new_bucket[pos], j);
else
tommy_list_insert_first(&new_bucket[pos], j);
j = j_next;
}
}
} else if (new_bucket_bit > bucket_bit) {
tommy_size_t i;
/* grow by multiple bits with elements: allocate zeroed memory and reinsert */
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*)));
if (new_bucket_bit == bucket_bit + 1) {
/* grow by 1 bit: zero the two buckets inline in loop to preserve cache locality */
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*)));
for (i = 0; i < bucket_max; ++i) {
tommy_hashdyn_node* j = hashdyn->bucket[i];
for (i = 0; i < bucket_max; ++i) {
tommy_hashdyn_node* j;
while (j) {
tommy_hashdyn_node* j_next = j->next;
tommy_size_t pos = j->index & new_bucket_mask;
if (new_bucket[pos])
tommy_list_insert_tail_not_empty(new_bucket[pos], j);
else
tommy_list_insert_first(&new_bucket[pos], j);
j = j_next;
new_bucket[i] = 0;
new_bucket[i + bucket_max] = 0;
j = hashdyn->bucket[i];
while (j) {
tommy_hashdyn_node* j_next = j->next;
tommy_size_t pos = j->index & new_bucket_mask;
if (new_bucket[pos])
tommy_list_insert_tail_not_empty(new_bucket[pos], j);
else
tommy_list_insert_first(&new_bucket[pos], j);
j = j_next;
}
}
} else {
/* grow by multiple bits with elements: allocate zeroed memory and reinsert */
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*)));
for (i = 0; i < bucket_max; ++i) {
tommy_hashdyn_node* j = hashdyn->bucket[i];
while (j) {
tommy_hashdyn_node* j_next = j->next;
tommy_size_t pos = j->index & new_bucket_mask;
if (new_bucket[pos])
tommy_list_insert_tail_not_empty(new_bucket[pos], j);
else
tommy_list_insert_first(&new_bucket[pos], j);
j = j_next;
}
}
}
} else {
tommy_size_t i;
/* shrink with elements: all buckets are overwritten, no pre-zeroing needed */
/* all buckets are overwritten, no pre-zeroing needed */
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*)));
for (i = 0; i < new_bucket_max; ++i) {
new_bucket[i] = hashdyn->bucket[i];
tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]);
if (new_bucket_bit + 1 == bucket_bit) {
/* shrink by 1 bit: each new bucket joins exactly two old buckets */
for (i = 0; i < new_bucket_max; ++i) {
new_bucket[i] = hashdyn->bucket[i];
tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]);
}
} else {
/* shrink by multiple bits */
for (i = 0; i < new_bucket_max; ++i) {
tommy_size_t j;
/* all old buckets with the same new modulus must be concatenated. */
new_bucket[i] = hashdyn->bucket[i];
for (j = i + new_bucket_max; j < bucket_max; j += new_bucket_max)
tommy_list_concat(&new_bucket[i], &hashdyn->bucket[j]);
}
}
}
@ -133,6 +154,19 @@ TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count)
tommy_hashdyn_resize(hashdyn, bucket_bit);
}
TOMMY_API void tommy_hashdyn_shrink(tommy_hashdyn* hashdyn)
{
tommy_uint_t bucket_bit = tommy_ilog2(hashdyn->count | 1) + 2;
if (bucket_bit < TOMMY_HASHDYN_BIT)
bucket_bit = TOMMY_HASHDYN_BIT;
if (bucket_bit >= hashdyn->bucket_bit)
return;
tommy_hashdyn_resize(hashdyn, bucket_bit);
}
TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash)
{
tommy_size_t pos = hash & hashdyn->bucket_mask;
@ -146,6 +180,27 @@ TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node*
hashdyn_grow_step(hashdyn);
}
TOMMY_API void* tommy_hashdyn_insert_unique(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
{
void* existing = tommy_hashdyn_search(hashdyn, cmp, cmp_arg, hash);
if (existing)
return existing;
tommy_hashdyn_insert(hashdyn, node, data, hash);
return data;
}
TOMMY_API void tommy_hashdyn_rehash_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_hash_t hash)
{
if (node->index == hash)
return;
/* unlink using the stored hash, without invoking the resize policy. */
tommy_list_remove_existing(&hashdyn->bucket[node->index & hashdyn->bucket_mask], node);
node->index = hash;
tommy_list_insert_tail(&hashdyn->bucket[hash & hashdyn->bucket_mask], node, node->data);
}
TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
{
tommy_size_t pos = node->index & hashdyn->bucket_mask;
@ -221,62 +276,15 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn)
+ tommy_hashdyn_count(hashdyn) * (tommy_size_t)sizeof(tommy_hashdyn_node);
}
/**
* \brief Transfers all elements from the dynamic hashtable into a tommy_list.
*
* Removes every element from the \p hashdyn hashtable and inserts them
* into the provided \p list (at the tail), preserving the per-bucket order
* but not guaranteeing any particular global order.
*
* After the call:
* - the hashtable is left completely empty (\c count = 0, all buckets = NULL)
* - the target list contains all the elements that were previously in the hashtable
*
* This function is useful when you need to:
* - extract all elements to process/sort them outside the hash table
* - convert the hashtable into a list for sequential iteration
* - prepare for a full clear + re-insertion with different hash/ordering
* - move ownership of the nodes to a list-based container
*
* \note The operation is O(n) where n is the number of elements.
* \note No memory allocation is performed.
* \note The relative order of elements that were in the same bucket is preserved,
* but the order among different buckets is bucket-order dependent.
*
* Typical usage pattern:
* \code
* tommy_list all_elements;
* tommy_list_init(&all_elements);
*
* // move everything out of the hashtable into the list
* tommy_hashdyn_to_list(&hashdyn, &all_elements);
*
* // now you can sort, filter, process sequentially, etc.
* tommy_list_sort(&all_elements, compare_by_value);
* \endcode
*
* \param hashdyn The dynamic hashtable to drain
* \param list The list that will receive all the elements
*/
TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list)
{
tommy_size_t bucket_max = hashdyn->bucket_max;
tommy_hashdyn_node** bucket = hashdyn->bucket;
tommy_size_t pos;
/* move everything to the list */
for (pos = 0; pos < bucket_max; ++pos) {
tommy_hashdyn_node* node = bucket[pos];
while (node) {
tommy_node* node_next = node->next;
tommy_list_insert_tail(list, node, node->data);
node = node_next;
}
}
for (pos = 0; pos < hashdyn->bucket_max; ++pos)
tommy_list_concat(list, &hashdyn->bucket[pos]);
/* clear all */
memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*));
hashdyn->count = 0;
tommy_hashdyn_clear(hashdyn);
}

View file

@ -4,9 +4,9 @@
/** \file
* Dynamic chained hashtable.
*
* This hashtable resizes dynamically. It starts with the minimal size of 16 buckets, it doubles
* the size when it reaches a load factor greater than 0.5 and it halves the size when the load
* factor is lower than 0.125.
* This hashtable resizes dynamically. It starts with the minimal size of 16 buckets.
* It doubles the size when it reaches a load factor greater than or equal to 0.5
* and it halves the size when the load factor is less than or equal to 0.125.
*
* All the elements are reallocated in a single resize operation done inside
* tommy_hashdyn_insert() or tommy_hashdyn_remove().
@ -32,7 +32,7 @@
* In the insertion call you have to specify the address of the node, the
* address of the object, and the hash value of the key to use.
* The address of the object is used to initialize the tommy_node::data field
* of the node, and the hash to initialize the tommy_node::key field.
* of the node, and the hash to initialize the tommy_node::index field.
*
* \code
* struct object {
@ -48,7 +48,7 @@
* tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object
* \endcode
*
* To find an element in the hashtable you have to call tommy_hashtable_search()
* To find an element in the hashtable you have to call tommy_hashdyn_search()
* providing a comparison function, its argument, and the hash of the key to search.
*
* \code
@ -114,6 +114,7 @@
#define __TOMMYHASHDYN_H
#include "tommyhash.h"
#include "tommylist.h"
/******************************************************************************/
/* hashdyn */
@ -155,6 +156,17 @@ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn);
*/
TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn);
/**
* Removes all elements, preserving the allocated buckets.
* The hashtable remains initialized and can be reused immediately.
* Objects are not freed and nodes are not accessed or modified.
* Their links must not be used to traverse the previous contents.
* You can call this function after tommy_hashdyn_foreach() has freed the objects.
* Subsequent insertions and removals retain the normal resizing policy.
* \note This operation is O(b), where b is the number of buckets.
*/
TOMMY_API void tommy_hashdyn_clear(tommy_hashdyn* hashdyn);
/**
* Pre-allocates buckets for the specified number of elements.
*
@ -164,11 +176,40 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn);
*/
TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count);
/**
* Shrinks the allocated buckets to fit the current number of elements.
* The target is the smallest power of two, at least 16, for which
* the number of elements is strictly less than half the number of buckets.
* If the current allocation is already no larger than the target, nothing is done.
* An empty hashtable is reduced to 16 buckets.
* Objects are not freed, and the tommy_node::data and tommy_node::index fields
* are left unchanged. The order of elements with the same hash is preserved.
* \note This operation is O(b), where b is the previous number of buckets.
*/
TOMMY_API void tommy_hashdyn_shrink(tommy_hashdyn* hashdyn);
/**
* Inserts an element in the hashtable.
*/
TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash);
/**
* Inserts an element only if no equal element is already contained.
* If found, the first equal element's tommy_node::data field is returned,
* and the hashtable and candidate node are left unchanged.
* Otherwise, the candidate is inserted using the normal insertion policy,
* and its data field is returned.
* Objects are not freed by this call.
* \param node The candidate node. It must not belong to any container.
* \param data The object to insert.
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
* The function should return 0 for equal elements, anything other for different elements.
* \param cmp_arg Compare argument describing the candidate key.
* \param hash Hash of the candidate key, consistent with the comparison function.
* \return The first equal element's data field, or data if the candidate was inserted.
*/
TOMMY_API void* tommy_hashdyn_insert_unique(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash);
/**
* Searches and removes an element from the hashtable.
* You have to provide a compare function and the hash of the element you want to remove.
@ -225,6 +266,22 @@ tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_fun
*/
TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
/**
* Updates the hash of an element already contained in the hashtable.
* The node must belong to this hashtable. The caller updates the object key
* and provides its new hash, without modifying tommy_node::index directly.
* If the hash is unchanged, the node and its position are left unchanged.
* Otherwise, the node is moved to the tail of the destination bucket,
* even if the old and new hashes identify the same bucket.
* The tommy_node::data field and the number of elements are left unchanged.
* No memory allocation, deallocation or resize is performed.
* Equal keys are allowed; no uniqueness check is performed.
* \param node The node whose hash is updated.
* \param hash The new hash of the element.
* \note This operation is O(1).
*/
TOMMY_API void tommy_hashdyn_rehash_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_hash_t hash);
/**
* Calls the specified function for each element in the hashtable.
*
@ -260,6 +317,9 @@ TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func*
/**
* Calls the specified function with an argument for each element in the hashtable.
* The iteration order and callback rules are the same as tommy_hashdyn_foreach().
* The callback may deallocate the current element.
* Adding or removing elements from inside the callback is not allowed.
*/
TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg);
@ -271,6 +331,23 @@ tommy_inline tommy_size_t tommy_hashdyn_count(tommy_hashdyn* hashdyn)
return hashdyn->count;
}
/**
* Checks if empty.
* \return If the hashtable is empty.
*/
tommy_inline tommy_bool_t tommy_hashdyn_empty(tommy_hashdyn* hashdyn)
{
return hashdyn->count == 0;
}
/**
* Gets the number of buckets.
*/
tommy_inline tommy_size_t tommy_hashdyn_bucket_count(tommy_hashdyn* hashdyn)
{
return hashdyn->bucket_max;
}
/**
* Gets the size of allocated memory.
* It includes the size of the ::tommy_hashdyn_node of the stored elements.
@ -285,17 +362,19 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn);
* but not guaranteeing any particular global order.
*
* After the call:
* - the hashtable is left completely empty
* - the hashtable is left empty and initialized, preserving its allocated buckets
* - the target list contains all the elements that were previously in the hashtable
*
* The tommy_node::data and tommy_node::index fields are left unchanged.
*
* This function is useful when you need to:
* - extract all elements to process/sort them outside the hash table
* - convert the hashtable into a list for sequential iteration
* - prepare for a full clear + re-insertion with different hash/ordering
* - move ownership of the nodes to a list-based container
*
* \note The operation is O(n) where n is the number of elements.
* \note No memory allocation is performed.
* \note The operation is O(b) where b is the number of buckets.
* \note No memory allocation or deallocation is performed.
* \note The relative order of elements that were in the same bucket is preserved,
* but the order among different buckets is bucket-order dependent.
*
@ -311,8 +390,9 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn);
* tommy_list_sort(&all_elements, compare_by_value);
* \endcode
*
* \param hashdyn The dynamic hashtable to drain
* \param list The list that will receive all the elements
* \param hashdyn The hashtable to drain
* \param list The destination list. It must be initialized and must not share
* nodes with the hashtable. Existing elements remain at the head of the list.
*/
TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list);

View file

@ -14,6 +14,86 @@ tommy_inline void tommy_list_set(tommy_list* list, tommy_node* head, tommy_node*
*list = head;
}
TOMMY_API void tommy_list_splice_before(tommy_list* list, tommy_node* reference, tommy_list* second)
{
tommy_node* second_head = tommy_list_head(second);
if (!second_head)
return;
if (reference == tommy_list_head(list)) {
/* prepend */
tommy_list_prepend(list, second);
} else {
/* splice in the middle */
tommy_chain_splice(reference->prev, reference, second_head, second_head->prev);
}
}
TOMMY_API void tommy_list_splice_after(tommy_list* list, tommy_node* reference, tommy_list* second)
{
tommy_node* second_head = tommy_list_head(second);
if (!second_head)
return;
if (!reference->next) {
/* append */
tommy_list_concat(list, second);
} else {
/* splice in the middle */
tommy_chain_splice(reference, reference->next, second_head, second_head->prev);
}
}
TOMMY_API void tommy_list_merge(tommy_list* first, tommy_list* second, tommy_compare_func* cmp)
{
tommy_chain first_chain;
tommy_chain second_chain;
tommy_node* first_head = tommy_list_head(first);
tommy_node* second_head = tommy_list_head(second);
if (!second_head)
return;
if (!first_head) {
*first = *second;
return;
}
/* create chains from the non-empty lists */
first_chain.head = first_head;
first_chain.tail = first_head->prev;
second_chain.head = second_head;
second_chain.tail = second_head->prev;
tommy_chain_merge_degenerated(&first_chain, &second_chain, cmp);
/* restore the destination list */
tommy_list_set(first, first_chain.head, first_chain.tail);
}
TOMMY_API void tommy_list_reverse(tommy_list* list)
{
tommy_node* head = tommy_list_head(list);
tommy_node* node = head;
tommy_node* prev = 0;
if (!head)
return;
while (node) {
tommy_node* next = node->next;
node->next = prev;
node->prev = next;
prev = node;
node = next;
}
/* the old head is the new tail */
tommy_list_set(list, prev, head);
}
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp)
{
tommy_chain chain;

View file

@ -60,6 +60,40 @@
* }
* \endcode
*
* To iterate backward, start at tommy_list_tail() and use tommy_list_prev().
* Do not follow tommy_node::prev directly past the head, as it points to the tail.
*
* \code
* tommy_node* i = tommy_list_tail(&list);
* while (i) {
* struct object* obj = i->data;
*
* printf("%d\n", obj->value);
*
* i = tommy_list_prev(&list, i);
* }
* \endcode
*
* To reorder existing elements, use tommy_list_move_head(), tommy_list_move_tail()
* or tommy_list_reverse(). To split a list or transfer elements between lists,
* use tommy_list_split(), tommy_list_prepend(), tommy_list_splice_before()
* and tommy_list_splice_after().
* These operations preserve the object pointers and do not allocate memory.
*
* \code
* tommy_list second;
* tommy_list_init(&second);
*
* // transfers node and all following nodes to second
* tommy_list_split(&list, node, &second);
*
* // appends the transferred nodes back to list
* tommy_list_concat(&list, &second);
* \endcode
*
* Two lists sorted with the same comparison function can be merged with
* tommy_list_merge(). The merge is stable.
*
* To destroy the list you have to remove all the elements,
* as the list is completely inplace and it doesn't allocate memory.
* This can be done with the tommy_list_foreach() function.
@ -67,6 +101,7 @@
* \code
* // deallocates all the objects iterating the list
* tommy_list_foreach(&list, free);
* tommy_list_init(&list); // resets the list before reuse
* \endcode
*/
@ -115,6 +150,22 @@ tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
return head->prev;
}
/**
* Gets the previous node in the list.
* Unlike tommy_node::prev, this function returns 0 at the head of the list,
* allowing backward iteration starting from tommy_list_tail().
* \param list The list.
* \param node The current node. The node must be in the list.
* \return The previous node, or 0 if the current node is the head.
*/
tommy_inline tommy_node* tommy_list_prev(tommy_list* list, tommy_node* node)
{
if (node == tommy_list_head(list))
return 0;
return node->prev;
}
/** \internal
* Creates a new list with a single element.
* \param list The list to initialize.
@ -286,6 +337,130 @@ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node
return node->data;
}
/**
* Removes the element at the head of the list.
* The object is not deallocated.
* \note The removed node content is left unchanged, as in tommy_list_remove_existing().
* \param list The list.
* \return The tommy_node::data field of the node removed, or 0 if the list is empty.
*/
tommy_inline void* tommy_list_remove_head(tommy_list* list)
{
tommy_node* node = tommy_list_head(list);
if (!node)
return 0;
return tommy_list_remove_existing(list, node);
}
/**
* Removes the element at the tail of the list.
* The object is not deallocated.
* \note The removed node content is left unchanged, as in tommy_list_remove_existing().
* \param list The list.
* \return The tommy_node::data field of the node removed, or 0 if the list is empty.
*/
tommy_inline void* tommy_list_remove_tail(tommy_list* list)
{
tommy_node* node = tommy_list_tail(list);
if (!node)
return 0;
return tommy_list_remove_existing(list, node);
}
/**
* Moves an existing node to the head of the list.
* If the node is already at the head, nothing is done.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list.
* \param node The node to move. The node must be in the list.
*/
tommy_inline void tommy_list_move_head(tommy_list* list, tommy_node* node)
{
if (node == tommy_list_head(list))
return;
tommy_list_remove_existing(list, node);
tommy_list_insert_head(list, node, node->data);
}
/**
* Moves an existing node to the tail of the list.
* If the node is already at the tail, nothing is done.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list.
* \param node The node to move. The node must be in the list.
*/
tommy_inline void tommy_list_move_tail(tommy_list* list, tommy_node* node)
{
if (node == tommy_list_tail(list))
return;
tommy_list_remove_existing(list, node);
tommy_list_insert_tail(list, node, node->data);
}
/**
* Splits a list before the specified node.
* The list retains the nodes preceding the specified node. The second list
* receives the specified node and all following nodes. The order of the elements is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list to split.
* \param node The first node to transfer. The node must be in the list,
* or 0 to leave list unchanged and set second to 0.
* Specifying the head transfers the whole list.
* \param second The list receiving node and all following nodes.
* It must be distinct from list and need not be initialized. Its previous value
* is overwritten without removing or deallocating any nodes previously referenced by second.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_split(tommy_list* list, tommy_node* node, tommy_list* second)
{
tommy_node* head = tommy_list_head(list);
tommy_node* tail;
tommy_node* before;
if (!node) {
*second = 0;
return;
}
if (node == head) {
*second = *list;
*list = 0;
return;
}
tail = head->prev;
before = node->prev;
/* terminate the retained list */
head->prev = before;
before->next = 0;
/* create the list of transferred nodes */
node->prev = tail;
*second = node;
}
/**
* Exchanges the contents of two lists.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param first The first list.
* \param second The second list. The lists must not share nodes,
* unless first and second are the same pointer, in which case nothing is done.
* \note This operation is O(1).
*/
tommy_inline void tommy_list_swap(tommy_list* first, tommy_list* second)
{
tommy_list tmp = *first;
*first = *second;
*second = tmp;
}
/**
* Concats two lists.
* The second list is concatenated at the first list.
@ -322,6 +497,72 @@ tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
first_tail->next = second_head;
}
/**
* Prepends two lists.
* The second list is prepended at the first list.
* \param first The first list.
* \param second The second list. After this call the list content is undefined,
* and you should not use it anymore.
*/
tommy_inline void tommy_list_prepend(tommy_list* first, tommy_list* second)
{
tommy_list_concat(second, first);
*first = *second;
}
/**
* Transfers all elements of a list before the specified reference node.
* The order of the transferred elements is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The destination list.
* \param reference The node before which to insert. It must be nonzero and in the destination list.
* \param second The source list. After this call the list content is undefined,
* and you should not use it anymore.
* The lists must be distinct and must not share nodes.
* \note This operation is O(1). An empty source list has no effect.
*/
TOMMY_API void tommy_list_splice_before(tommy_list* list, tommy_node* reference, tommy_list* second);
/**
* Transfers all elements of a list after the specified reference node.
* The order of the transferred elements is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The destination list.
* \param reference The node after which to insert. It must be nonzero and in the destination list.
* \param second The source list. After this call the list content is undefined,
* and you should not use it anymore.
* The lists must be distinct and must not share nodes.
* \note This operation is O(1). An empty source list has no effect.
*/
TOMMY_API void tommy_list_splice_after(tommy_list* list, tommy_node* reference, tommy_list* second);
/**
* Merges two sorted lists.
* Both lists must be sorted using the same comparison function.
* The merge is stable: equal elements from the first list precede those from
* the second list, and the relative order within each list is preserved.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param first The destination list, receiving the sorted result.
* \param second The source list. After this call the list content is undefined,
* and you should not use it anymore.
* The lists must be distinct and must not share nodes.
* \param cmp Compare function called with two elements.
* The function should return <0 if the first element is less than the second,
* ==0 if equal, and >0 if greater. It must not modify the lists.
* \note This operation is O(n+m), or O(1) when one list is empty or the ranges
* can be concatenated directly. It uses O(1) auxiliary space.
*/
TOMMY_API void tommy_list_merge(tommy_list* first, tommy_list* second, tommy_compare_func* cmp);
/**
* Reverses the order of the elements in a list.
* Empty lists and lists containing one element are left unchanged.
* The tommy_node::data and tommy_node::index fields are left unchanged.
* \param list The list.
* \note This operation is O(n) and uses O(1) auxiliary space.
*/
TOMMY_API void tommy_list_reverse(tommy_list* list);
/**
* Sorts a list.
* It's a stable merge sort with O(N*log(N)) worst complexity.
@ -398,6 +639,9 @@ tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
/**
* Calls the specified function with an argument for each element in the list.
* The iteration order and callback rules are the same as tommy_list_foreach().
* The callback may deallocate the current element.
* Adding or removing elements from inside the callback is not allowed.
*/
tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg)
{
@ -411,3 +655,4 @@ tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_fun
}
#endif

View file

@ -155,28 +155,33 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n
}
}
TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data)
tommy_inline void* tommy_tree_insert_impl(tommy_tree* tree, tommy_tree_node* node, void* data, tommy_bool_t unique)
{
tommy_tree_node* parent = 0;
tommy_tree_node* existing = 0;
tommy_tree_node** link = &tree->root;
node->data = data;
node->prev = 0;
node->next = 0;
node->index = 0;
while (*link) {
int cmp = tree->cmp(data, (*link)->data);
parent = *link;
if (cmp < 0)
link = &parent->prev;
else if (cmp > 0)
else if (cmp == 0 && unique) {
existing = parent;
link = &parent->prev;
} else {
/* placing equal keys after existing ones preserves insertion order through rotations */
link = &parent->next;
else
return parent->data;
}
}
if (existing)
return existing->data;
node->data = data;
node->prev = 0;
node->next = 0;
node->index = (tommy_size_t)(tommy_uintptr_t)parent;
*link = node;
++tree->count;
@ -186,6 +191,16 @@ TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void*
return node->data;
}
TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data)
{
tommy_tree_insert_impl(tree, node, data, 0);
}
TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data)
{
return tommy_tree_insert_impl(tree, node, data, 1);
}
tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk)
{
while (node) {
@ -326,35 +341,69 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no
return data;
}
TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func)
tommy_inline void tommy_tree_to_list_node(tommy_tree_node* node, tommy_list* list)
{
while (root) {
tommy_tree_node* next;
if (node) {
tommy_tree_node* right = node->next;
tommy_tree_foreach_node(root->prev, func);
/* make a copy in case func is free() */
next = root->next;
func(root->data);
root = next;
tommy_tree_to_list_node(node->prev, list);
/* list insertion overwrites child links, so keep the right subtree reachable */
tommy_list_insert_tail(list, node, node->data);
tommy_tree_to_list_node(right, list);
}
}
TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg)
TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list)
{
while (root) {
tommy_tree_to_list_node(tree->root, list);
tommy_tree_clear(tree);
}
TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func)
{
/* AVL height is less than twice the bit width of its node count */
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
tommy_size_t depth = 0;
tommy_tree_node* node = tree->root;
while (node || depth) {
tommy_tree_node* next;
tommy_tree_foreach_arg_node(root->prev, func, arg);
while (node) {
stack[depth] = node;
++depth;
node = node->prev;
}
/* make a copy in case func is free() */
next = root->next;
node = stack[--depth];
/* save the next subtree before the callback can free this node */
next = node->next;
func(node->data);
node = next;
}
}
func(arg, root->data);
TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
{
/* AVL height is less than twice the bit width of its node count */
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
tommy_size_t depth = 0;
tommy_tree_node* node = tree->root;
root = next;
while (node || depth) {
tommy_tree_node* next;
while (node) {
stack[depth] = node;
++depth;
node = node->prev;
}
node = stack[--depth];
/* save the next subtree before the callback can free this node */
next = node->next;
func(arg, node->data);
node = next;
}
}

View file

@ -7,7 +7,8 @@
* This tree is a standard AVL tree implementation that stores elements in the
* order defined by the comparison function.
*
* As a difference from other tommy containers, duplicate elements cannot be inserted.
* Duplicate elements are allowed. They are visited in insertion order.
* Use tommy_tree_insert_unique() to reject duplicate keys.
*
* To initialize a tree you have to call tommy_tree_init() specifying a comparison
* function that will define the order in the tree.
@ -39,8 +40,8 @@
* tommy_tree_insert(&tree, &obj->node, obj); // inserts the object
* \endcode
*
* To find an element in the tree you have to call tommy_tree_search() providing
* the key to search.
* To find the first element with a given key in tree order, call
* tommy_tree_search() providing the key to search.
*
* \code
* struct object value_to_find = { 1 };
@ -67,8 +68,8 @@
* }
* \endcode
*
* To remove an element from the tree you have to call tommy_tree_remove()
* providing the key to search and remove.
* To remove the first element with a given key in tree order, call
* tommy_tree_remove() providing the key to search and remove.
*
* \code
* struct object value_to_remove = { 1 };
@ -111,6 +112,7 @@
#define __TOMMYTREE_H
#include "tommytypes.h"
#include "tommylist.h"
/******************************************************************************/
/* tree */
@ -145,16 +147,52 @@ tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
}
/**
* Inserts an element in the tree.
* If the element is already present, it's not inserted again.
* Check the return value to identify if the element was already present or not.
* Removes all elements, preserving the comparison function.
* The tree remains initialized and can be reused immediately.
* Objects are not freed and nodes are not accessed or modified.
* Their links must not be used to traverse the previous contents.
* You can call this function after tommy_tree_foreach() has freed the objects.
* \note This operation is O(1).
*/
tommy_inline void tommy_tree_clear(tommy_tree* tree)
{
tree->root = 0;
tree->count = 0;
}
/**
* Inserts an element in the tree, allowing duplicate keys.
* Equal elements are visited in insertion order.
* You have to provide the pointer of the node embedded into the object and
* the pointer to the object.
* \param node Pointer to the node embedded into the object to insert.
* \param data Pointer to the object to insert.
* \return The element in the tree. Either the already existing one, or the one just inserted.
*/
TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data);
TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data);
/**
* Inserts an element only if no equal element is already contained.
* If found, the first equal element in tree order is returned and the
* candidate node is left unchanged. Otherwise the candidate is inserted
* and its data field is returned.
* \param node Pointer to the node embedded into the object to insert.
* \param data Pointer to the object to insert.
* \return The first equal element, or data if the candidate was inserted.
*/
TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data);
/**
* Transfers all elements from the tree to the tail of a list in tree order.
* Existing list elements remain before the transferred elements. The tree is
* left empty and initialized with its original comparison function.
* The list must be initialized and must not share nodes with the tree.
* Objects are not freed, and the node data and index fields are left unchanged.
* The index field no longer represents a valid tree parent after the transfer.
* \param tree The tree to drain.
* \param list The destination list.
* \note This operation is O(n), using O(log n) stack space.
*/
TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list);
/**
* Removes an element from the tree.
@ -163,22 +201,6 @@ TOMMY_API void* tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void*
*/
TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node);
/** \internal
* Calls the specified function for each element in the tree.
* \param root Root node of the tree.
* \param func Function to call.
*/
TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func);
/** \internal
* Calls the specified function with an argument for each element in the tree.
* \param root Root node of the tree.
* \param func Function to call.
* \param arg Custom argument for the function.
*/
TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg);
/**
* Gets the parent of the specified node.
* \param node Node contained in the tree.
@ -304,106 +326,86 @@ tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree)
}
/** \internal
* Searches a node in the tree.
* If the element is not found, 0 is returned.
* \param cmp Comparison function.
* \param node Node to start the search from.
* \param data Element used for comparison.
* \return The first node found, or 0 if none.
* Searches the first equal node in tree order with a specific comparison function.
* The function must define a suborder of the tree comparison function.
* \param cmp Comparison function called with cmp_arg and the object in the tree.
* \param cmp_arg Argument passed as the first argument of the comparison function.
* \return The first equal node in tree order, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_search_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data)
tommy_inline tommy_tree_node* tommy_tree_search_node_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tree->root;
tommy_tree_node* candidate = 0;
while (node) {
int c = cmp(data, node->data);
int c = cmp(cmp_arg, node->data);
if (c < 0)
node = node->prev;
else if (c > 0)
node = node->next;
else
return node;
}
return 0;
}
/**
* Searches an element in the tree.
* If the element is not found, 0 is returned.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
*/
tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data)
{
tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data);
if (!node)
return 0;
return node->data;
}
/**
* Searches an element in the tree with a specific comparison function.
*
* Like tommy_tree_search() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
*
* The ::data argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
*/
tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_node(cmp, tree->root, cmp_arg);
if (!node)
return 0;
return node->data;
}
/** \internal
* Searches a node in the tree with key greater or equal than the specified one.
* If no such element exists, 0 is returned.
* \param cmp Comparison function.
* \param node Node to start the search from.
* \param data Element used for comparison.
* \return The first node found, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_search_greater_equal_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data)
{
tommy_tree_node* candidate = 0;
while (node) {
int c = cmp(data, node->data);
if (c < 0) {
else {
candidate = node;
node = node->prev;
} else if (c > 0) {
node = node->next;
} else {
return node;
}
}
return candidate;
}
/**
* Searches an element in the tree.
* If the element is not found, 0 is returned.
* \param data Element used for comparison.
* \return The first equal element in tree order, or 0 if none.
*/
tommy_inline void* tommy_tree_search(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_node_compare(tree, tree->cmp, data);
return node ? node->data : 0;
}
/**
* Searches an element in the tree with a specific comparison function.
*
* Like tommy_tree_search() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The first equal element in tree order is returned.
*
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
*/
tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_node_compare(tree, cmp, cmp_arg);
return node ? node->data : 0;
}
/**
* Searches an element in the tree with key greater or equal than the specified one.
* If no such element exists, 0 is returned.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
* \return The first element in tree order with key greater or equal, or 0 if none.
*/
tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data)
tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_greater_equal_node(tree->cmp, tree->root, data);
tommy_tree_node* node = tree->root;
void* candidate = 0;
if (!node)
return 0;
while (node) {
int c = tree->cmp(data, node->data);
return node->data;
if (c <= 0) {
candidate = node->data;
node = node->prev;
} else {
node = node->next;
}
}
return candidate;
}
/**
@ -411,42 +413,24 @@ tommy_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, void* data)
*
* Like tommy_tree_search_greater_equal() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The first qualifying element in tree order is returned.
*
* The ::data argument will be the first argument of the comparison function,
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
*/
tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
tommy_inline void* tommy_tree_search_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_greater_equal_node(cmp, tree->root, cmp_arg);
if (!node)
return 0;
return node->data;
}
/** \internal
* Searches a node in the tree with key less or equal than the specified one.
* If no such element exists, 0 is returned.
* \param cmp Comparison function.
* \param node Node to start the search from.
* \param data Element used for comparison.
* \return The first node found, or 0 if none.
*/
tommy_inline tommy_tree_node* tommy_tree_search_less_equal_node(tommy_compare_func* cmp, tommy_tree_node* node, void* data)
{
tommy_tree_node* candidate = 0;
tommy_tree_node* node = tree->root;
void* candidate = 0;
while (node) {
int c = cmp(data, node->data);
int c = cmp(cmp_arg, node->data);
if (c < 0) {
if (c <= 0) {
candidate = node->data;
node = node->prev;
} else if (c > 0) {
candidate = node;
node = node->next;
} else {
return node;
node = node->next;
}
}
@ -457,16 +441,25 @@ tommy_inline tommy_tree_node* tommy_tree_search_less_equal_node(tommy_compare_fu
* Searches an element in the tree with key less or equal than the specified one.
* If no such element exists, 0 is returned.
* \param data Element used for comparison.
* \return The first element found, or 0 if none.
* \return The last element in tree order with key less or equal, or 0 if none.
*/
tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data)
tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_less_equal_node(tree->cmp, tree->root, data);
tommy_tree_node* node = tree->root;
void* candidate = 0;
if (!node)
return 0;
while (node) {
int c = tree->cmp(data, node->data);
return node->data;
if (c < 0) {
node = node->prev;
} else {
candidate = node->data;
node = node->next;
}
}
return candidate;
}
/**
@ -474,29 +467,58 @@ tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data)
*
* Like tommy_tree_search_less_equal() but you can specify a different comparison function.
* Note that this function must define a suborder of the original one.
* The last qualifying element in tree order is returned.
*
* The ::data argument will be the first argument of the comparison function,
* The cmp_arg argument will be the first argument of the comparison function,
* and it can be of a different type than the objects in the tree.
*/
tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
tommy_inline void* tommy_tree_search_less_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_less_equal_node(cmp, tree->root, cmp_arg);
tommy_tree_node* node = tree->root;
void* candidate = 0;
if (!node)
return 0;
while (node) {
int c = cmp(cmp_arg, node->data);
return node->data;
if (c < 0) {
node = node->prev;
} else {
candidate = node->data;
node = node->next;
}
}
return candidate;
}
/**
* Searches and removes an element.
* If the element is not found, 0 is returned.
* \param data Element used for comparison.
* \return The removed element, or 0 if not found.
* \return The first equal element in tree order, or 0 if not found.
*/
tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data)
tommy_inline void* tommy_tree_remove(tommy_tree* tree, const void* data)
{
tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data);
tommy_tree_node* node = tommy_tree_search_node_compare(tree, tree->cmp, data);
if (!node)
return 0;
return tommy_tree_remove_existing(tree, node);
}
/**
* Searches and removes an element with a specific comparison function.
* The comparison function must define a suborder of the original one.
* The cmp_arg argument is passed as the first argument of the comparison
* function and may have a different type than the objects in the tree.
* \param cmp Comparison function used to find the element.
* \param cmp_arg Argument passed to the comparison function.
* \return The first equal element in tree order, or 0 if not found.
*/
tommy_inline void* tommy_tree_remove_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
{
tommy_tree_node* node = tommy_tree_search_node_compare(tree, cmp, cmp_arg);
if (!node)
return 0;
@ -534,18 +556,15 @@ tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data)
* tommy_tree_foreach(&tree, free);
* \endcode
*/
tommy_inline void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func)
{
tommy_tree_foreach_node(tree->root, func);
}
TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func);
/**
* Calls the specified function with an argument for each element in the tree.
* The iteration order and callback rules are the same as tommy_tree_foreach().
* The callback may deallocate the current element.
* Adding or removing elements from inside the callback is not allowed.
*/
tommy_inline void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
{
tommy_tree_foreach_arg_node(tree->root, func, arg);
}
TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg);
/**
* Checks if empty.

View file

@ -200,6 +200,7 @@ typedef struct tommy_node_struct {
/**
* Pointer to the object containing the node.
* This field is initialized when inserting nodes into a data structure.
* The object pointer passed to insertion functions must not be 0.
*/
void* data;
@ -355,7 +356,7 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value)
*/
return __builtin_clz(value) ^ 31;
#else
/* Find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */
/* find the log base 2 of an N-bit integer in O(lg(N)) operations with multiply and lookup */
/* from http://graphics.stanford.edu/~seander/bithacks.html */
static unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = {
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
@ -372,13 +373,12 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value)
#endif
}
#if TOMMY_SIZE_BIT == 64
/**
* Bit scan reverse or integer log2 for 64 bits.
*/
tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value)
{
#if defined(_MSC_VER)
#if defined(_MSC_VER) && defined(_WIN64)
unsigned long count;
_BitScanReverse64(&count, value);
return count;
@ -393,7 +393,6 @@ tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value)
return tommy_ilog2_u32(l);
#endif
}
#endif
/**
* Bit scan forward or trailing zero count.
@ -412,24 +411,23 @@ tommy_inline tommy_uint_t tommy_ctz_u32(tommy_uint32_t value)
#elif defined(__GNUC__)
return __builtin_ctz(value);
#else
/* Count the consecutive zero bits (trailing) on the right with multiply and lookup */
/* count the consecutive zero bits (trailing) on the right with multiply and lookup */
/* from http://graphics.stanford.edu/~seander/bithacks.html */
static const unsigned char TOMMY_DE_BRUIJN_INDEX_CTZ[32] = {
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & - value) * 0x077CB531U)) >> 27];
return TOMMY_DE_BRUIJN_INDEX_CTZ[(tommy_uint32_t)(((value & -value) * 0x077CB531U)) >> 27];
#endif
}
#if TOMMY_SIZE_BIT == 64
/**
* Bit scan forward or trailing zero count for 64 bits.
*/
tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
{
#if defined(_MSC_VER)
#if defined(_MSC_VER) && defined(_WIN64)
unsigned long count;
_BitScanForward64(&count, value);
return count;
@ -444,7 +442,6 @@ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
return tommy_ctz_u32(h) + 32;
#endif
}
#endif
/**
* Rounds up to the next power of 2.
@ -453,7 +450,7 @@ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
*/
tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value)
{
/* Round up to the next highest power of 2 */
/* round up to the next highest power of 2 */
/* from http://graphics.stanford.edu/~seander/bithacks.html */
--value;
@ -493,7 +490,16 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
return ((value - 0x01010101) & ~value & 0x80808080) != 0;
}
/*
/**
* Check if the specified 64-bit word has a byte at 0.
* \return 0 or 1.
*/
tommy_inline int tommy_haszero_u64(tommy_uint64_t value)
{
return ((value - 0x0101010101010101ULL) & ~value & 0x8080808080808080ULL) != 0;
}
/**
* Bit depth mapping.
*/
#if TOMMY_SIZE_BIT == 64
@ -507,3 +513,4 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
#endif
#endif