mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-25 18:40:19 -04:00
Update to TommyDS v4.0
This commit is contained in:
parent
a39fb132f8
commit
88c54f041e
21 changed files with 3032 additions and 1095 deletions
|
|
@ -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);
|
extent = extent_alloc(parity_pos, file, file_pos, count);
|
||||||
|
|
||||||
/* insert the extent in the trees */
|
/* insert the extent in the trees */
|
||||||
parity_extent = tommy_tree_insert(&disk->fs_parity, &extent->parity_node, extent);
|
parity_extent = tommy_tree_insert_unique(&disk->fs_parity, &extent->parity_node, extent);
|
||||||
file_extent = tommy_tree_insert(&disk->fs_file, &extent->file_node, extent);
|
file_extent = tommy_tree_insert_unique(&disk->fs_file, &extent->file_node, extent);
|
||||||
|
|
||||||
if (parity_extent != extent || file_extent != extent) {
|
if (parity_extent != extent || file_extent != extent) {
|
||||||
/* LCOV_EXCL_START */
|
/* 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);
|
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 */
|
/* insert the extent in the trees */
|
||||||
parity_extent = tommy_tree_insert(&disk->fs_parity, &second_extent->parity_node, second_extent);
|
parity_extent = tommy_tree_insert_unique(&disk->fs_parity, &second_extent->parity_node, second_extent);
|
||||||
file_extent = tommy_tree_insert(&disk->fs_file, &second_extent->file_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) {
|
if (parity_extent != second_extent || file_extent != second_extent) {
|
||||||
/* LCOV_EXCL_START */
|
/* LCOV_EXCL_START */
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,23 @@ void* malloc_nofail(size_t size)
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void* realloc_nofail(void* ptr, size_t size)
|
||||||
|
{
|
||||||
|
/* keep zero-size requests allocated, as in malloc_nofail() */
|
||||||
|
void* new_ptr = realloc(ptr, size ? size : 1);
|
||||||
|
|
||||||
|
if (!new_ptr) {
|
||||||
|
/* LCOV_EXCL_START */
|
||||||
|
malloc_fail(size);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
/* LCOV_EXCL_STOP */
|
||||||
|
}
|
||||||
|
|
||||||
|
malloc_counter_inc(size);
|
||||||
|
|
||||||
|
return new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
void* nalloc_nofail(size_t count, size_t size)
|
void* nalloc_nofail(size_t count, size_t size)
|
||||||
{
|
{
|
||||||
if (size != 0 && count > SIZE_MAX / size) {
|
if (size != 0 && count > SIZE_MAX / size) {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ void memory_single_thread(int single_thread);
|
||||||
*/
|
*/
|
||||||
void* malloc_nofail(size_t size);
|
void* malloc_nofail(size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safe realloc.
|
||||||
|
* If no memory is available, it aborts.
|
||||||
|
*/
|
||||||
|
void* realloc_nofail(void* ptr, size_t size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safe malloc with calloc calling arguments.
|
* Safe malloc with calloc calling arguments.
|
||||||
* If no memory is available, it aborts.
|
* If no memory is available, it aborts.
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ static const uint64_t MUSEAIR_CONSTANT[13] = {
|
||||||
do { \
|
do { \
|
||||||
l = a; \
|
l = a; \
|
||||||
h = b; \
|
h = b; \
|
||||||
util_mum(&l, &h); \
|
tommy_mum(&l, &h); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define likely(a) tommy_likely(a)
|
#define likely(a) tommy_likely(a)
|
||||||
|
|
|
||||||
|
|
@ -51,25 +51,25 @@ struct hash_test_vector {
|
||||||
* Test vectors for tommy_hash32
|
* Test vectors for tommy_hash32
|
||||||
*/
|
*/
|
||||||
static struct hash32_test_vector TEST_HASH32[] = {
|
static struct hash32_test_vector TEST_HASH32[] = {
|
||||||
{ "", 0, 0x8614384c, 0xa766795d },
|
{ "", 0, 0xaffe48d7, 0xa766795d },
|
||||||
{ "a", 1, 0x12c16c36, 0xa766795d },
|
{ "a", 1, 0x2ae4a38d, 0xa766795d },
|
||||||
{ "abc", 3, 0xc58e8af5, 0xa766795d },
|
{ "abc", 3, 0x0aefd0ef, 0xa766795d },
|
||||||
{ "message digest", 14, 0x006b32f1, 0xa766795d },
|
{ "message digest", 14, 0x828c4a8a, 0xa766795d },
|
||||||
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x7e6fcfe0, 0xa766795d },
|
{ "abcdefghijklmnopqrstuvwxyz", 26, 0xc8072ea9, 0xa766795d },
|
||||||
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x8604adf8, 0xa766795d },
|
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xe8e24812, 0xa766795d },
|
||||||
{ "The quick brown fox jumps over the lazy dog", 43, 0xdeba3d3a, 0xa766795d },
|
{ "The quick brown fox jumps over the lazy dog", 43, 0xae34960f, 0xa766795d },
|
||||||
{ "\x00", 1, 0x4a7d1c33, 0xa766795d },
|
{ "\x00", 1, 0xac5373be, 0xa766795d },
|
||||||
{ "\x16\x27", 2, 0x8b50899b, 0xa766795d },
|
{ "\x16\x27", 2, 0x321c4629, 0xa766795d },
|
||||||
{ "\xe2\x56\xb4", 3, 0x60406493, 0xa766795d },
|
{ "\xe2\x56\xb4", 3, 0x66b8fbd8, 0xa766795d },
|
||||||
{ "\xc9\x4d\x9c\xda", 4, 0xa049144a, 0xa766795d },
|
{ "\xc9\x4d\x9c\xda", 4, 0xbc37eb40, 0xa766795d },
|
||||||
{ "\x79\xf1\x29\x69\x5d", 5, 0x4da2c2f1, 0xa766795d },
|
{ "\x79\xf1\x29\x69\x5d", 5, 0xb96d1628, 0xa766795d },
|
||||||
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x59de30cf, 0xa766795d },
|
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x760cf131, 0xa766795d },
|
||||||
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x219e149c, 0xa766795d },
|
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0xd1d5a37a, 0xa766795d },
|
||||||
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x25067520, 0xa766795d },
|
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x995d591f, 0xa766795d },
|
||||||
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xa1f368d8, 0xa766795d },
|
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x8c7cfa48, 0xa766795d },
|
||||||
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x805fc63d, 0xa766795d },
|
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xaa8fa306, 0xa766795d },
|
||||||
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x7f75dd0f, 0xa766795d },
|
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0x9c1d4af8, 0xa766795d },
|
||||||
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0xb9154382, 0xa766795d },
|
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x1a2214ec, 0xa766795d },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -77,29 +77,29 @@ static struct hash32_test_vector TEST_HASH32[] = {
|
||||||
* Test vectors for tommy_strhash32
|
* Test vectors for tommy_strhash32
|
||||||
*/
|
*/
|
||||||
struct strhash32_test_vector TEST_STRHASH32[] = {
|
struct strhash32_test_vector TEST_STRHASH32[] = {
|
||||||
{ "", 0x0af1416d, 0xa766795d },
|
{ "", 0xaffe48d7, 0xa766795d },
|
||||||
{ "a", 0x68fa0f3f, 0xa766795d },
|
{ "a", 0x2ae4a38d, 0xa766795d },
|
||||||
{ "abc", 0xfc68ffc5, 0xa766795d },
|
{ "abc", 0x0aefd0ef, 0xa766795d },
|
||||||
{ "message digest", 0x08477b63, 0xa766795d },
|
{ "message digest", 0x828c4a8a, 0xa766795d },
|
||||||
{ "abcdefghijklmnopqrstuvwxyz", 0x5b9c25e5, 0xa766795d },
|
{ "abcdefghijklmnopqrstuvwxyz", 0xc8072ea9, 0xa766795d },
|
||||||
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0x1e530ce7, 0xa766795d },
|
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 0xe8e24812, 0xa766795d },
|
||||||
{ "The quick brown fox jumps over the lazy dog", 0xaf93eefe, 0xa766795d },
|
{ "The quick brown fox jumps over the lazy dog", 0xae34960f, 0xa766795d },
|
||||||
{ "\xff", 0xfc88801b, 0xa766795d },
|
{ "\xff", 0x9ab96198, 0xa766795d },
|
||||||
{ "\x16\x27", 0xcd7216db, 0xa766795d },
|
{ "\x16\x27", 0x321c4629, 0xa766795d },
|
||||||
{ "\xe2\x56\xb4", 0x05f98d02, 0xa766795d },
|
{ "\xe2\x56\xb4", 0x66b8fbd8, 0xa766795d },
|
||||||
{ "\xc9\x4d\x9c\xda", 0xf65206f8, 0xa766795d },
|
{ "\xc9\x4d\x9c\xda", 0xbc37eb40, 0xa766795d },
|
||||||
{ "\x79\xf1\x29\x69\x5d", 0x72bd6bda, 0xa766795d },
|
{ "\x79\xf1\x29\x69\x5d", 0xb96d1628, 0xa766795d },
|
||||||
{ "\xff\x7e\xdf\x1e\x31\x1c", 0x57dfb9b4, 0xa766795d },
|
{ "\xff\x7e\xdf\x1e\x31\x1c", 0xaf63217b, 0xa766795d },
|
||||||
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0x499ff634, 0xa766795d },
|
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 0xd1d5a37a, 0xa766795d },
|
||||||
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0xe896b7ce, 0xa766795d },
|
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 0x995d591f, 0xa766795d },
|
||||||
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0xfe3939f0, 0xa766795d },
|
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 0x8c7cfa48, 0xa766795d },
|
||||||
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0x4351d482, 0xa766795d },
|
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 0xaa8fa306, 0xa766795d },
|
||||||
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x88e92135, 0xa766795d },
|
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\xff\xb7\xae", 0x59437055, 0xa766795d },
|
||||||
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x01109c16, 0xa766795d },
|
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 0x1a2214ec, 0xa766795d },
|
||||||
{ "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xbcb050dc, 0xa766795d },
|
{ "\x87\xd8\x61\x61\x4c\x89\x17\x4e\xa1\xa4\xef\x13\xa9", 0xb5d1be68, 0xa766795d },
|
||||||
{ "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xbe5e1fd5, 0xa766795d },
|
{ "\xfe\xa6\x5b\xc2\xda\xe8\x95\xd4\x64\xab\x4c\x39\x58\x29", 0xb2475cc8, 0xa766795d },
|
||||||
{ "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x70d8c97f, 0xa766795d },
|
{ "\x94\x49\xc0\x78\xa0\x80\xda\xc7\x71\x4e\x17\x37\xa9\x7c\x40", 0x3cf85197, 0xa766795d },
|
||||||
{ "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x957440a9, 0xa766795d },
|
{ "\x53\x7e\x36\xb4\x2e\xc9\xb9\xcc\x18\x3e\x9a\x5f\xfc\xb7\xb0\x61", 0x8ec35c09, 0xa766795d },
|
||||||
{ 0, 0, 0 }
|
{ 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -107,25 +107,25 @@ struct strhash32_test_vector TEST_STRHASH32[] = {
|
||||||
* Test vectors for tommy_hash64
|
* Test vectors for tommy_hash64
|
||||||
*/
|
*/
|
||||||
static struct hash64_test_vector TEST_HASH64[] = {
|
static struct hash64_test_vector TEST_HASH64[] = {
|
||||||
{ "", 0, 0x8614384cb5165fbfULL, 0x2f022773a766795dULL },
|
{ "", 0, 0xf64d2d2164ca4e35ULL, 0x2f022773a766795dULL },
|
||||||
{ "a", 1, 0x1a2e0298a8e94a3dULL, 0x2f022773a766795dULL },
|
{ "a", 1, 0xcb0b7863fd912608ULL, 0x2f022773a766795dULL },
|
||||||
{ "abc", 3, 0x7555796b7a7d21ebULL, 0x2f022773a766795dULL },
|
{ "abc", 3, 0x023adbd4412dfbc2ULL, 0x2f022773a766795dULL },
|
||||||
{ "message digest", 14, 0x9411a57d04b92fb4ULL, 0x2f022773a766795dULL },
|
{ "message digest", 14, 0x8d16f620b58a030eULL, 0x2f022773a766795dULL },
|
||||||
{ "abcdefghijklmnopqrstuvwxyz", 26, 0x3ca3f8d2b4e69832ULL, 0x2f022773a766795dULL },
|
{ "abcdefghijklmnopqrstuvwxyz", 26, 0xa615ea6b87f2b63fULL, 0x2f022773a766795dULL },
|
||||||
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0x6dae542ba0015a4dULL, 0x2f022773a766795dULL },
|
{ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62, 0xb9f145676ebdb4fcULL, 0x2f022773a766795dULL },
|
||||||
{ "The quick brown fox jumps over the lazy dog", 43, 0xe06d8cbb3d2ea1a6ULL, 0x2f022773a766795dULL },
|
{ "The quick brown fox jumps over the lazy dog", 43, 0x99b05c9548b523dcULL, 0x2f022773a766795dULL },
|
||||||
{ "\x00", 1, 0x201e664fb5f2c021ULL, 0x2f022773a766795dULL },
|
{ "\x00", 1, 0x432204e0ea8617fbULL, 0x2f022773a766795dULL },
|
||||||
{ "\x16\x27", 2, 0xef42fa8032c4b775ULL, 0x2f022773a766795dULL },
|
{ "\x16\x27", 2, 0x5118c58166c6ab3eULL, 0x2f022773a766795dULL },
|
||||||
{ "\xe2\x56\xb4", 3, 0x6e6c498a6688466cULL, 0x2f022773a766795dULL },
|
{ "\xe2\x56\xb4", 3, 0x29484604a9976acbULL, 0x2f022773a766795dULL },
|
||||||
{ "\xc9\x4d\x9c\xda", 4, 0x5195005419905423ULL, 0x2f022773a766795dULL },
|
{ "\xc9\x4d\x9c\xda", 4, 0xf000ac44a9f98f4aULL, 0x2f022773a766795dULL },
|
||||||
{ "\x79\xf1\x29\x69\x5d", 5, 0x221235b48afee7c1ULL, 0x2f022773a766795dULL },
|
{ "\x79\xf1\x29\x69\x5d", 5, 0x9603fc74ec52e5aaULL, 0x2f022773a766795dULL },
|
||||||
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0x1b1f18b9266f095bULL, 0x2f022773a766795dULL },
|
{ "\x00\x7e\xdf\x1e\x31\x1c", 6, 0xd39f549b363c8e2bULL, 0x2f022773a766795dULL },
|
||||||
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x2cbafa8e741d49caULL, 0x2f022773a766795dULL },
|
{ "\x2a\x4c\xe1\xff\x9e\x6f\x53", 7, 0x1be47d9f53a2a31dULL, 0x2f022773a766795dULL },
|
||||||
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0x4677f04c06e0758dULL, 0x2f022773a766795dULL },
|
{ "\xba\x02\xab\x18\x30\xc5\x0e\x8a", 8, 0xe3fa24e31af8c4ebULL, 0x2f022773a766795dULL },
|
||||||
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0x5afe09e8214e2163ULL, 0x2f022773a766795dULL },
|
{ "\xec\x4e\x7a\x72\x1e\x71\x2a\xc9\x33", 9, 0xef8c3dcec426a25dULL, 0x2f022773a766795dULL },
|
||||||
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0x115b6276d209fab6ULL, 0x2f022773a766795dULL },
|
{ "\xfd\xe2\x9c\x0f\x72\xb7\x08\xea\xd0\x78", 10, 0xed0be01a88bde7cbULL, 0x2f022773a766795dULL },
|
||||||
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xd0636d2f01cf3a3eULL, 0x2f022773a766795dULL },
|
{ "\x65\xc4\x8a\xb8\x80\x86\x9a\x79\x00\xb7\xae", 11, 0xf9b50d9db5dfa543ULL, 0x2f022773a766795dULL },
|
||||||
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x6d259f5fef74f93eULL, 0x2f022773a766795dULL },
|
{ "\x77\xe9\xd7\x80\x0e\x3f\x5c\x43\xc8\xc2\x46\x39", 12, 0x695329dad2094e8aULL, 0x2f022773a766795dULL },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -188,6 +188,13 @@ static void test_hash(void)
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
/* LCOV_EXCL_STOP */
|
/* 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) {
|
for (i = 0; TEST_HASH64[i].data; ++i) {
|
||||||
|
|
@ -456,7 +463,7 @@ static void test_tommy(void)
|
||||||
tommy_arrayblkof_init(&arrayblkof, sizeof(unsigned));
|
tommy_arrayblkof_init(&arrayblkof, sizeof(unsigned));
|
||||||
|
|
||||||
for (i = 0; i < TOMMY_SIZE; ++i) {
|
for (i = 0; i < TOMMY_SIZE; ++i) {
|
||||||
tommy_array_insert(&array, &node[i]);
|
tommy_array_insert_tail(&array, &node[i]);
|
||||||
tommy_arrayblkof_grow(&arrayblkof, i + 1);
|
tommy_arrayblkof_grow(&arrayblkof, i + 1);
|
||||||
*(unsigned*)tommy_arrayblkof_ref(&arrayblkof, i) = i;
|
*(unsigned*)tommy_arrayblkof_ref(&arrayblkof, i) = i;
|
||||||
}
|
}
|
||||||
|
|
@ -584,7 +591,11 @@ static void test_tommy(void)
|
||||||
tommy_tree_insert(&tree, &node[i], (void*)(uintptr_t)(i + 1));
|
tommy_tree_insert(&tree, &node[i], (void*)(uintptr_t)(i + 1));
|
||||||
|
|
||||||
/* try to insert a duplicate, count should not change */
|
/* 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) {
|
if (tommy_tree_count(&tree) != TOMMY_SIZE) {
|
||||||
/* LCOV_EXCL_START */
|
/* LCOV_EXCL_START */
|
||||||
|
|
|
||||||
119
cmdline/util.c
119
cmdline/util.c
|
|
@ -7,6 +7,7 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "raid/cpu.h"
|
#include "raid/cpu.h"
|
||||||
#include "raid/memory.h"
|
#include "raid/memory.h"
|
||||||
|
#include "tommyds/tommyhash.h"
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
/* memory */
|
/* memory */
|
||||||
|
|
@ -610,112 +611,6 @@ static inline void util_write64(void* ptr, uint64_t v)
|
||||||
memcpy(ptr, &v, sizeof(v));
|
memcpy(ptr, &v, sizeof(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 64*64 -> 128bit multiply function.
|
|
||||||
*
|
|
||||||
* @param A Address of 64-bit number.
|
|
||||||
* @param B Address of 64-bit number.
|
|
||||||
*
|
|
||||||
* Calculates 128-bit C = *A * *B.
|
|
||||||
*
|
|
||||||
* Overwrites A contents with C's low 64 bits.
|
|
||||||
* Overwrites B contents with C's high 64 bits.
|
|
||||||
*/
|
|
||||||
#if defined(__x86_64__) || defined(_M_X64)
|
|
||||||
/**
|
|
||||||
* This is intentionally placed before the __SIZEOF_INT128__ implementation.
|
|
||||||
*
|
|
||||||
* This implementation happens to generate more efficient code.
|
|
||||||
* While this likely depends only on the compiler, as long as it remains faster,
|
|
||||||
* we will keep it in this order.
|
|
||||||
*/
|
|
||||||
static inline void util_mum(uint64_t* A, uint64_t* B)
|
|
||||||
{
|
|
||||||
uint64_t a = *A;
|
|
||||||
uint64_t b = *B;
|
|
||||||
uint64_t low, high;
|
|
||||||
|
|
||||||
__asm__ (
|
|
||||||
"mulq %3"
|
|
||||||
: "=a" (low), "=d" (high)
|
|
||||||
: "a" (a), "rm" (b)
|
|
||||||
: "cc"
|
|
||||||
);
|
|
||||||
|
|
||||||
*A = low;
|
|
||||||
*B = high;
|
|
||||||
}
|
|
||||||
#elif defined(__SIZEOF_INT128__)
|
|
||||||
static inline void util_mum(uint64_t* A, uint64_t* B)
|
|
||||||
{
|
|
||||||
__uint128_t product = ((__uint128_t)*A) * ((__uint128_t)*B);
|
|
||||||
*A = (uint64_t)product;
|
|
||||||
*B = (uint64_t)(product >> 64);
|
|
||||||
}
|
|
||||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
|
||||||
/**
|
|
||||||
* This is intentionally placed after the __SIZEOF_INT128__ implementation.
|
|
||||||
*
|
|
||||||
* Although the code looks correct, there is a report of this
|
|
||||||
* implementation failing the self-test on macOS (Darwin 25.4.0, arm64)
|
|
||||||
* with Apple clang 21.0.0 (clang-2100.0.123.102).
|
|
||||||
*
|
|
||||||
* See: https://github.com/amadvance/snapraid/issues/43
|
|
||||||
*
|
|
||||||
* Note that the implementation is the same of OpenSSL:
|
|
||||||
*
|
|
||||||
* #elif defined(__aarch64__) && defined(SIXTY_FOUR_BIT_LONG)
|
|
||||||
* #if defined(__GNUC__) && __GNUC__ >= 2
|
|
||||||
* #define BN_UMULT_HIGH(a, b) ({ \
|
|
||||||
* register BN_ULONG ret; \
|
|
||||||
* asm ("umulh %0,%1,%2" \
|
|
||||||
* : "=r"(ret) \
|
|
||||||
* : "r"(a), "r"(b)); \
|
|
||||||
* ret; })
|
|
||||||
* #endif
|
|
||||||
* #endif
|
|
||||||
*
|
|
||||||
* See: https://github.com/openssl/openssl/blob/0463cbf185680e66a3a8960f10cd33001377d28e/crypto/bn/bn_local.h#L439
|
|
||||||
*/
|
|
||||||
static inline void util_mum(uint64_t* A, uint64_t* B)
|
|
||||||
{
|
|
||||||
uint64_t a = *A;
|
|
||||||
uint64_t b = *B;
|
|
||||||
uint64_t low = a * b;
|
|
||||||
uint64_t high;
|
|
||||||
__asm__ (
|
|
||||||
"umulh %0, %1, %2"
|
|
||||||
: "=r" (high)
|
|
||||||
: "r" (a), "r" (b)
|
|
||||||
);
|
|
||||||
*A = low;
|
|
||||||
*B = high;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static inline void util_mum(uint64_t* A, uint64_t* B)
|
|
||||||
{
|
|
||||||
uint64_t a = *A;
|
|
||||||
uint64_t b = *B;
|
|
||||||
|
|
||||||
uint64_t a_lo = (uint32_t)a;
|
|
||||||
uint64_t a_hi = a >> 32;
|
|
||||||
uint64_t b_lo = (uint32_t)b;
|
|
||||||
uint64_t b_hi = b >> 32;
|
|
||||||
|
|
||||||
uint64_t p0 = a_lo * b_lo;
|
|
||||||
uint64_t p1 = a_lo * b_hi;
|
|
||||||
uint64_t p2 = a_hi * b_lo;
|
|
||||||
uint64_t p3 = a_hi * b_hi;
|
|
||||||
|
|
||||||
uint64_t middle = p1 + (p0 >> 32) + (uint32_t)p2;
|
|
||||||
uint64_t low = (middle << 32) | (uint32_t)p0;
|
|
||||||
uint64_t high = p3 + (middle >> 32) + (p2 >> 32);
|
|
||||||
|
|
||||||
*A = low;
|
|
||||||
*B = high;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int util_selftest(void)
|
int util_selftest(void)
|
||||||
{
|
{
|
||||||
/* rotate left 32-bit */
|
/* rotate left 32-bit */
|
||||||
|
|
@ -748,33 +643,33 @@ int util_selftest(void)
|
||||||
if (util_rotr64(0x123456789ABCDEF0ULL, 0) != 0x123456789ABCDEF0ULL) return -1;
|
if (util_rotr64(0x123456789ABCDEF0ULL, 0) != 0x123456789ABCDEF0ULL) return -1;
|
||||||
if (util_rotr64(0x123456789ABCDEF0ULL, 64) != 0x123456789ABCDEF0ULL) return -1;
|
if (util_rotr64(0x123456789ABCDEF0ULL, 64) != 0x123456789ABCDEF0ULL) return -1;
|
||||||
|
|
||||||
/* util_mum tests */
|
/* tommy_mum tests */
|
||||||
uint64_t a, b;
|
uint64_t a, b;
|
||||||
|
|
||||||
/* small values */
|
/* small values */
|
||||||
a = 3; b = 4;
|
a = 3; b = 4;
|
||||||
util_mum(&a, &b);
|
tommy_mum(&a, &b);
|
||||||
if (a != 12 || b != 0) return -1;
|
if (a != 12 || b != 0) return -1;
|
||||||
|
|
||||||
/* 1 * anything */
|
/* 1 * anything */
|
||||||
a = 0xFFFFFFFFFFFFFFFFULL; b = 1;
|
a = 0xFFFFFFFFFFFFFFFFULL; b = 1;
|
||||||
util_mum(&a, &b);
|
tommy_mum(&a, &b);
|
||||||
if (a != 0xFFFFFFFFFFFFFFFFULL || b != 0) return -1;
|
if (a != 0xFFFFFFFFFFFFFFFFULL || b != 0) return -1;
|
||||||
|
|
||||||
/* (2^32) * (2^32) = 2^64 */
|
/* (2^32) * (2^32) = 2^64 */
|
||||||
a = 1ULL << 32; b = 1ULL << 32;
|
a = 1ULL << 32; b = 1ULL << 32;
|
||||||
util_mum(&a, &b);
|
tommy_mum(&a, &b);
|
||||||
if (a != 0 || b != 1) return -1;
|
if (a != 0 || b != 1) return -1;
|
||||||
|
|
||||||
/* max uint64 * max uint64 */
|
/* max uint64 * max uint64 */
|
||||||
a = 0xFFFFFFFFFFFFFFFFULL; b = 0xFFFFFFFFFFFFFFFFULL;
|
a = 0xFFFFFFFFFFFFFFFFULL; b = 0xFFFFFFFFFFFFFFFFULL;
|
||||||
util_mum(&a, &b);
|
tommy_mum(&a, &b);
|
||||||
if (a != 1 || b != 0xFFFFFFFFFFFFFFFEULL) return -1;
|
if (a != 1 || b != 0xFFFFFFFFFFFFFFFEULL) return -1;
|
||||||
|
|
||||||
/* larger random-ish test */
|
/* larger random-ish test */
|
||||||
a = 0x0123456789ABCDEFULL;
|
a = 0x0123456789ABCDEFULL;
|
||||||
b = 0xFEDCBA9876543210ULL;
|
b = 0xFEDCBA9876543210ULL;
|
||||||
util_mum(&a, &b);
|
tommy_mum(&a, &b);
|
||||||
if (a != 0x2236D88FE5618CF0ULL || b != 0x121FA00AD77D742ULL) return -1;
|
if (a != 0x2236D88FE5618CF0ULL || b != 0x121FA00AD77D742ULL) return -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@
|
||||||
/* redefine the malloc for tommy use */
|
/* redefine the malloc for tommy use */
|
||||||
#define tommy_malloc malloc_nofail
|
#define tommy_malloc malloc_nofail
|
||||||
#define tommy_calloc calloc_nofail
|
#define tommy_calloc calloc_nofail
|
||||||
|
#define tommy_realloc realloc_nofail
|
||||||
#define tommy_free free
|
#define tommy_free free
|
||||||
|
|
||||||
#include "os/portable.h"
|
#include "os/portable.h"
|
||||||
#include "cmdline/support.h" /* for malloc/calloc_nofail() */
|
#include "cmdline/support.h" /* for malloc/calloc/realloc_nofail() */
|
||||||
|
|
||||||
#include "tommyhash.c"
|
#include "tommyhash.c"
|
||||||
#include "tommyarray.c"
|
#include "tommyarray.c"
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,18 @@
|
||||||
|
|
||||||
#include "tommyarray.h"
|
#include "tommyarray.h"
|
||||||
|
|
||||||
|
#include <string.h> /* for memset */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* array */
|
/* array */
|
||||||
|
|
||||||
TOMMY_API void tommy_array_init(tommy_array* array)
|
TOMMY_API void tommy_array_init(tommy_array* array)
|
||||||
{
|
{
|
||||||
tommy_uint_t i;
|
|
||||||
|
|
||||||
/* fixed initial size */
|
/* fixed initial size */
|
||||||
array->bucket_bit = TOMMY_ARRAY_BIT;
|
array->bucket_bit = TOMMY_ARRAY_BIT;
|
||||||
array->bucket_max = (tommy_size_t)1 << array->bucket_bit;
|
array->bucket_max = (tommy_size_t)1 << array->bucket_bit;
|
||||||
array->bucket[0] = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
|
array->bucket[0] = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
|
||||||
for (i = 1; i < TOMMY_ARRAY_BIT; ++i)
|
for (tommy_uint_t i = 1; i < TOMMY_ARRAY_BIT; ++i)
|
||||||
array->bucket[i] = array->bucket[0];
|
array->bucket[i] = array->bucket[0];
|
||||||
|
|
||||||
array->count = 0;
|
array->count = 0;
|
||||||
|
|
@ -22,26 +22,18 @@ TOMMY_API void tommy_array_init(tommy_array* array)
|
||||||
|
|
||||||
TOMMY_API void tommy_array_done(tommy_array* array)
|
TOMMY_API void tommy_array_done(tommy_array* array)
|
||||||
{
|
{
|
||||||
tommy_uint_t i;
|
|
||||||
|
|
||||||
tommy_free(array->bucket[0]);
|
tommy_free(array->bucket[0]);
|
||||||
for (i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) {
|
for (tommy_uint_t i = TOMMY_ARRAY_BIT; i < array->bucket_bit; ++i) {
|
||||||
void** segment = array->bucket[i];
|
void** segment = array->bucket[i];
|
||||||
tommy_free(&segment[(tommy_ptrdiff_t)1 << i]);
|
tommy_free(&segment[(tommy_ptrdiff_t)1 << i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count)
|
TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size)
|
||||||
{
|
{
|
||||||
if (array->count >= count)
|
while (size > array->bucket_max) {
|
||||||
return;
|
|
||||||
array->count = count;
|
|
||||||
|
|
||||||
while (count > array->bucket_max) {
|
|
||||||
void** segment;
|
|
||||||
|
|
||||||
/* allocate one more segment */
|
/* allocate one more segment */
|
||||||
segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
|
void** segment = tommy_cast(void**, tommy_calloc(array->bucket_max, sizeof(void*)));
|
||||||
|
|
||||||
/* store it adjusting the offset */
|
/* store it adjusting the offset */
|
||||||
/* cast to ptrdiff_t to ensure to get a negative value */
|
/* cast to ptrdiff_t to ensure to get a negative value */
|
||||||
|
|
@ -52,7 +44,78 @@ TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array)
|
TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size)
|
||||||
|
{
|
||||||
|
if (size >= array->count) {
|
||||||
|
tommy_array_grow(array, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear the unused elements to maintain the invariant that unused slots are zero */
|
||||||
|
tommy_size_t pos = size;
|
||||||
|
while (pos < array->count) {
|
||||||
|
tommy_uint_t bsr = tommy_ilog2(pos | 1);
|
||||||
|
tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1));
|
||||||
|
tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end;
|
||||||
|
|
||||||
|
memset(&array->bucket[bsr][pos], 0, (chunk_end - pos) * sizeof(void*));
|
||||||
|
pos = chunk_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
array->count = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void tommy_array_shrink(tommy_array* array)
|
||||||
|
{
|
||||||
|
tommy_uint_t target_bucket_bit = TOMMY_ARRAY_BIT;
|
||||||
|
|
||||||
|
if (array->count > (tommy_size_t)1 << TOMMY_ARRAY_BIT)
|
||||||
|
target_bucket_bit = tommy_ilog2(array->count - 1) + 1;
|
||||||
|
|
||||||
|
while (array->bucket_bit > target_bucket_bit) {
|
||||||
|
--array->bucket_bit;
|
||||||
|
void** segment = array->bucket[array->bucket_bit];
|
||||||
|
tommy_free(&segment[(tommy_ptrdiff_t)1 << array->bucket_bit]);
|
||||||
|
}
|
||||||
|
|
||||||
|
array->bucket_max = (tommy_size_t)1 << array->bucket_bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void tommy_array_foreach(tommy_array* array, tommy_foreach_func* func)
|
||||||
|
{
|
||||||
|
tommy_size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < array->count) {
|
||||||
|
tommy_uint_t bsr = tommy_ilog2(pos | 1);
|
||||||
|
tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1));
|
||||||
|
tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end;
|
||||||
|
void** ptr = array->bucket[bsr];
|
||||||
|
|
||||||
|
while (pos < chunk_end) {
|
||||||
|
func(ptr[pos]);
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void tommy_array_foreach_arg(tommy_array* array, tommy_foreach_arg_func* func, void* arg)
|
||||||
|
{
|
||||||
|
tommy_size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < array->count) {
|
||||||
|
tommy_uint_t bsr = tommy_ilog2(pos | 1);
|
||||||
|
tommy_size_t seg_end = (bsr < TOMMY_ARRAY_BIT) ? ((tommy_size_t)1 << TOMMY_ARRAY_BIT) : ((tommy_size_t)1 << (bsr + 1));
|
||||||
|
tommy_size_t chunk_end = array->count < seg_end ? array->count : seg_end;
|
||||||
|
void** ptr = array->bucket[bsr];
|
||||||
|
|
||||||
|
while (pos < chunk_end) {
|
||||||
|
func(arg, ptr[pos]);
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API tommy_size_t tommy_array_memory_usage(const tommy_array* array)
|
||||||
{
|
{
|
||||||
return array->bucket_max * (tommy_size_t)sizeof(void*);
|
return array->bucket_max * (tommy_size_t)sizeof(void*);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,14 @@
|
||||||
* This array is able to grow dynamically upon request, without any reallocation.
|
* This array is able to grow dynamically upon request, without any reallocation.
|
||||||
*
|
*
|
||||||
* The grow operation involves an allocation of a new array segment, without reallocating
|
* The grow operation involves an allocation of a new array segment, without reallocating
|
||||||
* the already used memory, and thus **not increasing** the heap fragmentation.
|
* the already used memory, and thus not increasing the heap fragmentation.
|
||||||
* This also implies that the address of the stored elements never change.
|
* This also implies that the address of the stored elements never changes.
|
||||||
*
|
*
|
||||||
* Allocated segments grow in size exponentially.
|
* Allocated segments grow in size exponentially.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYARRAY_H
|
#ifndef TOMMY_ARRAY_H
|
||||||
#define __TOMMYARRAY_H
|
#define TOMMY_ARRAY_H
|
||||||
|
|
||||||
#include "tommytypes.h"
|
#include "tommytypes.h"
|
||||||
|
|
||||||
|
|
@ -42,33 +42,83 @@ typedef struct tommy_array_struct {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the array.
|
* Initializes the array.
|
||||||
|
* \param array The array.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_array_init(tommy_array* array);
|
TOMMY_API void tommy_array_init(tommy_array* array);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deinitializes the array.
|
* Deinitializes the array.
|
||||||
|
* \param array The array.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_array_done(tommy_array* array);
|
TOMMY_API void tommy_array_done(tommy_array* array);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grows the size up to the specified value.
|
* Allocates space for at least the specified number of elements.
|
||||||
* All the new elements in the array are initialized with the 0 value.
|
* The initialized size and existing elements are unchanged.
|
||||||
|
* Existing element references remain valid.
|
||||||
|
* \param array The array.
|
||||||
|
* \param size Number of elements to reserve space for.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_array_grow(tommy_array* array, tommy_size_t size);
|
TOMMY_API void tommy_array_reserve(tommy_array* array, tommy_size_t size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a reference of the element at the specified position.
|
* Grows the size up to the specified value.
|
||||||
|
* All the new elements in the array are initialized with the 0 value.
|
||||||
|
* \param array The array.
|
||||||
|
* \param size New size of the array.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_array_grow(tommy_array* array, tommy_size_t size)
|
||||||
|
{
|
||||||
|
if (size > array->count) {
|
||||||
|
array->count = size;
|
||||||
|
|
||||||
|
if (size > array->bucket_max)
|
||||||
|
tommy_array_reserve(array, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the initialized size, preserving the common prefix.
|
||||||
|
* New elements are initialized to zero.
|
||||||
|
* Reducing the size does not release allocated capacity.
|
||||||
|
* \param array The array.
|
||||||
|
* \param size New size of the array.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_array_resize(tommy_array* array, tommy_size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements, preserving the allocated capacity.
|
||||||
|
* Call tommy_array_shrink() after clearing to restore the initial capacity.
|
||||||
|
* Pointed-to elements are not freed.
|
||||||
|
* The array remains initialized and can be reused immediately.
|
||||||
|
* \param array The array.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_array_clear(tommy_array* array)
|
||||||
|
{
|
||||||
|
tommy_array_resize(array, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases unused allocated memory.
|
||||||
|
* Preserves the size, values, and addresses of existing elements.
|
||||||
|
* \param array The array.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_array_shrink(tommy_array* array);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a reference to the element at the specified position.
|
||||||
* You must be sure that space for this position is already
|
* You must be sure that space for this position is already
|
||||||
* allocated calling tommy_array_grow().
|
* allocated by calling tommy_array_grow().
|
||||||
|
* \param array The array.
|
||||||
|
* \param pos Position of the element.
|
||||||
|
* \return Pointer to the slot at the specified position.
|
||||||
*/
|
*/
|
||||||
tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos)
|
tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos)
|
||||||
{
|
{
|
||||||
tommy_uint_t bsr;
|
|
||||||
|
|
||||||
assert(pos < array->count);
|
assert(pos < array->count);
|
||||||
|
|
||||||
/* get the highest bit set, in case of all 0, return 0 */
|
/* get the highest bit set, in case of all 0, return 0 */
|
||||||
bsr = tommy_ilog2(pos | 1);
|
tommy_uint_t bsr = tommy_ilog2(pos | 1);
|
||||||
|
|
||||||
return &array->bucket[bsr][pos];
|
return &array->bucket[bsr][pos];
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +126,10 @@ tommy_inline void** tommy_array_ref(tommy_array* array, tommy_size_t pos)
|
||||||
/**
|
/**
|
||||||
* Sets the element at the specified position.
|
* Sets the element at the specified position.
|
||||||
* You must be sure that space for this position is already
|
* You must be sure that space for this position is already
|
||||||
* allocated calling tommy_array_grow().
|
* allocated by calling tommy_array_grow().
|
||||||
|
* \param array The array.
|
||||||
|
* \param pos Position of the element.
|
||||||
|
* \param element Element to set.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* element)
|
tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* element)
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +139,10 @@ tommy_inline void tommy_array_set(tommy_array* array, tommy_size_t pos, void* el
|
||||||
/**
|
/**
|
||||||
* Gets the element at the specified position.
|
* Gets the element at the specified position.
|
||||||
* You must be sure that space for this position is already
|
* You must be sure that space for this position is already
|
||||||
* allocated calling tommy_array_grow().
|
* allocated by calling tommy_array_grow().
|
||||||
|
* \param array The array.
|
||||||
|
* \param pos Position of the element.
|
||||||
|
* \return The element at the specified position.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos)
|
tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos)
|
||||||
{
|
{
|
||||||
|
|
@ -94,28 +150,129 @@ tommy_inline void* tommy_array_get(tommy_array* array, tommy_size_t pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grows and inserts a new element at the end of the array.
|
* Gets the last element.
|
||||||
|
* The array must not be empty.
|
||||||
|
* \param array The array.
|
||||||
|
* \return The last element.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_array_insert(tommy_array* array, void* element)
|
tommy_inline void* tommy_array_tail(tommy_array* array)
|
||||||
|
{
|
||||||
|
assert(array->count != 0);
|
||||||
|
return tommy_array_get(array, array->count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grows and inserts a new element at the end of the array.
|
||||||
|
* \param array The array.
|
||||||
|
* \param element Element to insert.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_array_insert_tail(tommy_array* array, void* element)
|
||||||
{
|
{
|
||||||
tommy_size_t pos = array->count;
|
tommy_size_t pos = array->count;
|
||||||
|
|
||||||
tommy_array_grow(array, pos + 1);
|
++array->count;
|
||||||
|
if (array->count > array->bucket_max)
|
||||||
|
tommy_array_reserve(array, array->count);
|
||||||
|
|
||||||
tommy_array_set(array, pos, element);
|
tommy_array_set(array, pos, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the initialized size of the array.
|
* Removes and returns the last element.
|
||||||
|
* The array must not be empty.
|
||||||
|
* The removed slot is cleared, and allocated capacity is preserved.
|
||||||
|
* \param array The array.
|
||||||
|
* \return The removed element.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_size_t tommy_array_size(tommy_array* array)
|
tommy_inline void* tommy_array_remove_tail(tommy_array* array)
|
||||||
|
{
|
||||||
|
assert(array->count != 0);
|
||||||
|
|
||||||
|
tommy_size_t pos = array->count - 1;
|
||||||
|
void** ptr = tommy_array_ref(array, pos);
|
||||||
|
void* element = *ptr;
|
||||||
|
|
||||||
|
/* keep unused slots zero so growing within capacity needs no initialization */
|
||||||
|
*ptr = 0;
|
||||||
|
array->count = pos;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the array is empty.
|
||||||
|
* \param array The array.
|
||||||
|
* \return Nonzero if the array is empty, 0 otherwise.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_bool_t tommy_array_empty(const tommy_array* array)
|
||||||
|
{
|
||||||
|
return array->count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the initialized size of the array.
|
||||||
|
* \param array The array.
|
||||||
|
* \return The initialized size of the array.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_size_t tommy_array_size(const tommy_array* array)
|
||||||
{
|
{
|
||||||
return array->count;
|
return array->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the size of allocated memory.
|
* Gets the number of elements that fit without further allocation.
|
||||||
|
* \param array The array.
|
||||||
|
* \return The capacity of the array.
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_size_t tommy_array_memory_usage(tommy_array* array);
|
tommy_inline tommy_size_t tommy_array_capacity(const tommy_array* array)
|
||||||
|
{
|
||||||
|
return array->bucket_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges two initialized arrays without copying their elements.
|
||||||
|
* Element references remain valid and belong to the other array.
|
||||||
|
* Passing the same array twice has no effect.
|
||||||
|
* \param first The first array.
|
||||||
|
* \param second The second array.
|
||||||
|
* \note This operation is O(1).
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_array_swap(tommy_array* first, tommy_array* second)
|
||||||
|
{
|
||||||
|
tommy_array tmp = *first;
|
||||||
|
*first = *second;
|
||||||
|
*second = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the specified function for each element in the array.
|
||||||
|
*
|
||||||
|
* You cannot add or remove elements, nor change the size of the array,
|
||||||
|
* from inside the callback.
|
||||||
|
*
|
||||||
|
* If the callback frees the elements, the array retains dangling
|
||||||
|
* pointers. Call tommy_array_clear() before reusing it, or
|
||||||
|
* tommy_array_done() to release its storage.
|
||||||
|
* \param array The array.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_array_foreach(tommy_array* array, tommy_foreach_func* func);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the specified function with an argument for each element in the array.
|
||||||
|
*
|
||||||
|
* \param array The array.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
* \param arg Argument to pass to the function.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_array_foreach_arg(tommy_array* array, tommy_foreach_arg_func* func, void* arg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of allocated memory.
|
||||||
|
* \param array The array.
|
||||||
|
* \return Total memory allocated by the array, in bytes.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_size_t tommy_array_memory_usage(const tommy_array* array);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@
|
||||||
|
|
||||||
#include "tommyarrayblkof.h"
|
#include "tommyarrayblkof.h"
|
||||||
|
|
||||||
|
#include <string.h> /* for memset */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* array */
|
/* array */
|
||||||
|
|
||||||
TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size)
|
TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size)
|
||||||
{
|
{
|
||||||
tommy_array_init(&array->block);
|
array->block_capacity = (tommy_size_t)1 << TOMMY_ARRAYBLKOF_BIT;
|
||||||
|
array->block = tommy_cast(unsigned char**, tommy_malloc(array->block_capacity * sizeof(array->block[0])));
|
||||||
|
array->block_count = 0;
|
||||||
|
|
||||||
array->element_size = element_size;
|
array->element_size = element_size;
|
||||||
array->count = 0;
|
array->count = 0;
|
||||||
|
|
@ -16,44 +20,127 @@ TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t eleme
|
||||||
|
|
||||||
TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array)
|
TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array)
|
||||||
{
|
{
|
||||||
tommy_size_t i;
|
for (tommy_size_t i = 0; i < array->block_count; ++i)
|
||||||
|
tommy_free(array->block[i]);
|
||||||
|
|
||||||
for (i = 0; i < tommy_array_size(&array->block); ++i)
|
tommy_free(array->block);
|
||||||
tommy_free(tommy_array_get(&array->block, i));
|
|
||||||
|
|
||||||
tommy_array_done(&array->block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t count)
|
TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size)
|
||||||
{
|
{
|
||||||
tommy_size_t block_max;
|
if (size <= array->block_count * TOMMY_ARRAYBLKOF_SIZE)
|
||||||
tommy_size_t block_mac;
|
|
||||||
|
|
||||||
if (array->count >= count)
|
|
||||||
return;
|
return;
|
||||||
array->count = count;
|
|
||||||
|
|
||||||
block_max = (count + TOMMY_ARRAYBLKOF_SIZE - 1) / TOMMY_ARRAYBLKOF_SIZE;
|
tommy_size_t block_max = (size - 1) / TOMMY_ARRAYBLKOF_SIZE + 1;
|
||||||
block_mac = tommy_array_size(&array->block);
|
|
||||||
|
|
||||||
if (block_mac < block_max) {
|
if (array->block_capacity < block_max) {
|
||||||
/* grow the block array */
|
/* only the directory moves; addresses inside existing blocks stay valid */
|
||||||
tommy_array_grow(&array->block, block_max);
|
tommy_size_t capacity = array->block_capacity;
|
||||||
|
while (capacity < block_max)
|
||||||
|
capacity *= 2;
|
||||||
|
array->block = tommy_cast(unsigned char**, tommy_realloc(array->block, capacity * sizeof(array->block[0])));
|
||||||
|
array->block_capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
/* allocate new blocks */
|
/* allocate new blocks */
|
||||||
while (block_mac < block_max) {
|
while (array->block_count < block_max) {
|
||||||
void** ptr = tommy_cast(void**, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size));
|
unsigned char* ptr = tommy_cast(unsigned char*, tommy_calloc(TOMMY_ARRAYBLKOF_SIZE, array->element_size));
|
||||||
|
|
||||||
/* set the new block */
|
array->block[array->block_count] = ptr;
|
||||||
tommy_array_set(&array->block, block_mac, ptr);
|
++array->block_count;
|
||||||
|
|
||||||
++block_mac;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array)
|
TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size)
|
||||||
{
|
{
|
||||||
return tommy_array_memory_usage(&array->block) + tommy_array_size(&array->block) * TOMMY_ARRAYBLKOF_SIZE * array->element_size;
|
if (size >= array->count) {
|
||||||
|
tommy_arrayblkof_grow(array, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear the unused elements to maintain the invariant that unused slots are zero */
|
||||||
|
tommy_size_t pos = size;
|
||||||
|
while (pos < array->count) {
|
||||||
|
tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
tommy_size_t blk_offset = pos % TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
tommy_size_t blk_rem = TOMMY_ARRAYBLKOF_SIZE - blk_offset;
|
||||||
|
tommy_size_t count_rem = array->count - pos;
|
||||||
|
tommy_size_t chunk = blk_rem < count_rem ? blk_rem : count_rem;
|
||||||
|
|
||||||
|
memset(array->block[blk_idx] + blk_offset * array->element_size, 0, chunk * array->element_size);
|
||||||
|
pos += chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
array->count = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
tommy_size_t block_max = (array->count == 0) ? 0 : ((array->count - 1) / TOMMY_ARRAYBLKOF_SIZE + 1);
|
||||||
|
|
||||||
|
while (array->block_count > block_max) {
|
||||||
|
--array->block_count;
|
||||||
|
tommy_free(array->block[array->block_count]);
|
||||||
|
}
|
||||||
|
|
||||||
|
tommy_size_t min_capacity = (tommy_size_t)1 << TOMMY_ARRAYBLKOF_BIT;
|
||||||
|
tommy_size_t capacity = min_capacity;
|
||||||
|
while (capacity < block_max)
|
||||||
|
capacity *= 2;
|
||||||
|
if (capacity < array->block_capacity) {
|
||||||
|
array->block = tommy_cast(unsigned char**, tommy_realloc(array->block, capacity * sizeof(array->block[0])));
|
||||||
|
array->block_capacity = capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void tommy_arrayblkof_foreach(tommy_arrayblkof* array, tommy_foreach_func* func)
|
||||||
|
{
|
||||||
|
/* cache the element size to avoid reloading it after each callback */
|
||||||
|
tommy_size_t element_size = array->element_size;
|
||||||
|
tommy_size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < array->count) {
|
||||||
|
tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
tommy_size_t chunk = array->count - pos;
|
||||||
|
unsigned char* ptr = array->block[blk_idx];
|
||||||
|
|
||||||
|
if (chunk > TOMMY_ARRAYBLKOF_SIZE)
|
||||||
|
chunk = TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
|
||||||
|
for (tommy_size_t i = 0; i < chunk; ++i) {
|
||||||
|
func(ptr);
|
||||||
|
ptr += element_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg)
|
||||||
|
{
|
||||||
|
/* cache the element size to avoid reloading it after each callback */
|
||||||
|
tommy_size_t element_size = array->element_size;
|
||||||
|
tommy_size_t pos = 0;
|
||||||
|
|
||||||
|
while (pos < array->count) {
|
||||||
|
tommy_size_t blk_idx = pos / TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
tommy_size_t chunk = array->count - pos;
|
||||||
|
unsigned char* ptr = array->block[blk_idx];
|
||||||
|
|
||||||
|
if (chunk > TOMMY_ARRAYBLKOF_SIZE)
|
||||||
|
chunk = TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
|
||||||
|
for (tommy_size_t i = 0; i < chunk; ++i) {
|
||||||
|
func(arg, ptr);
|
||||||
|
ptr += element_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(const tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
return array->block_capacity * sizeof(array->block[0]) + array->block_count * TOMMY_ARRAYBLKOF_SIZE * array->element_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,43 @@
|
||||||
// Copyright (C) 2013 Andrea Mazzoleni
|
// Copyright (C) 2013 Andrea Mazzoleni
|
||||||
|
|
||||||
/** \file
|
/** \file
|
||||||
* Dynamic array based on blocks of fixed size.
|
* Dynamic array based on fixed-size blocks.
|
||||||
*
|
*
|
||||||
* This array is able to grow dynamically upon request, without any reallocation.
|
* This array is able to grow dynamically without reallocating stored elements.
|
||||||
|
* The directory of block pointers may be reallocated, but element addresses
|
||||||
|
* never change.
|
||||||
*
|
*
|
||||||
* This is very similar to ::tommy_arrayblk, but it allows to store elements of any
|
* This is very similar to ::tommy_arrayblk, but it allows storing elements of any
|
||||||
* size and not just pointers.
|
* size and not just pointers.
|
||||||
*
|
*
|
||||||
* Note that in this case tommy_arrayblkof_ref() returns a pointer to the element,
|
* The container allocates space for elements but never copies user data into
|
||||||
* that should be used for getting and setting elements in the array,
|
* or out of it. tommy_arrayblkof_ref(), tommy_arrayblkof_tail(), and
|
||||||
* as generic getter and setter are not available.
|
* tommy_arrayblkof_insert_tail() return an element's address so the caller can
|
||||||
|
* read or write it directly.
|
||||||
|
*
|
||||||
|
* \note Only types with fundamental alignment requirements are supported.
|
||||||
|
* Storage is allocated using calloc(), which does not guarantee the alignment
|
||||||
|
* required by over-aligned types (e.g. _Alignas(32) or _Alignas(64)).
|
||||||
|
* When storing typed elements, use sizeof(type) as the element size to
|
||||||
|
* preserve alignment for every element.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYARRAYBLKOF_H
|
#ifndef TOMMY_ARRAYBLKOF_H
|
||||||
#define __TOMMYARRAYBLKOF_H
|
#define TOMMY_ARRAYBLKOF_H
|
||||||
|
|
||||||
#include "tommytypes.h"
|
#include "tommytypes.h"
|
||||||
#include "tommyarray.h"
|
|
||||||
|
|
||||||
#include <assert.h> /* for assert */
|
#include <assert.h> /* for assert */
|
||||||
|
#include <string.h> /* for memset */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* array */
|
/* array */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial and minimal capacity of the block directory expressed as a power of 2.
|
||||||
|
*/
|
||||||
|
#define TOMMY_ARRAYBLKOF_BIT 6
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elements for each block.
|
* Elements for each block.
|
||||||
*/
|
*/
|
||||||
|
|
@ -35,55 +49,214 @@
|
||||||
* \note Don't use internal fields directly, but access the container only using functions.
|
* \note Don't use internal fields directly, but access the container only using functions.
|
||||||
*/
|
*/
|
||||||
typedef struct tommy_arrayblkof_struct {
|
typedef struct tommy_arrayblkof_struct {
|
||||||
tommy_array block; /**< Array of blocks. */
|
unsigned char** block; /**< Directory of blocks. */
|
||||||
|
tommy_size_t block_count; /**< Number of allocated blocks. */
|
||||||
|
tommy_size_t block_capacity; /**< Number of directory entries allocated. */
|
||||||
tommy_size_t element_size; /**< Size of the stored element in bytes. */
|
tommy_size_t element_size; /**< Size of the stored element in bytes. */
|
||||||
tommy_size_t count; /**< Number of initialized elements in the array. */
|
tommy_size_t count; /**< Number of initialized elements in the array. */
|
||||||
} tommy_arrayblkof;
|
} tommy_arrayblkof;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the array.
|
* Initializes the array.
|
||||||
* \param element_size Size in byte of the element to store in the array.
|
* \param array The array.
|
||||||
|
* \param element_size Size in bytes of the element to store in the array.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size);
|
TOMMY_API void tommy_arrayblkof_init(tommy_arrayblkof* array, tommy_size_t element_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deinitializes the array.
|
* Deinitializes the array.
|
||||||
|
* \param array The array.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array);
|
TOMMY_API void tommy_arrayblkof_done(tommy_arrayblkof* array);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grows the size up to the specified value.
|
* Allocates space for at least the specified number of elements.
|
||||||
* All the new elements in the array are initialized with the 0 value.
|
* The initialized size and existing elements are unchanged.
|
||||||
|
* Existing element references remain valid.
|
||||||
|
* \param array The array.
|
||||||
|
* \param size Number of elements to reserve space for.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size);
|
TOMMY_API void tommy_arrayblkof_reserve(tommy_arrayblkof* array, tommy_size_t size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a reference of the element at the specified position.
|
* Grows the size up to the specified value.
|
||||||
|
* All the new elements in the array are initialized with the 0 value.
|
||||||
|
* \param array The array.
|
||||||
|
* \param size New size of the array.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_arrayblkof_grow(tommy_arrayblkof* array, tommy_size_t size)
|
||||||
|
{
|
||||||
|
if (size > array->count) {
|
||||||
|
array->count = size;
|
||||||
|
|
||||||
|
if (size > array->block_count * TOMMY_ARRAYBLKOF_SIZE)
|
||||||
|
tommy_arrayblkof_reserve(array, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the initialized size, preserving the common prefix.
|
||||||
|
* New elements are initialized to zero.
|
||||||
|
* Reducing the size does not release allocated capacity.
|
||||||
|
* \param array The array.
|
||||||
|
* \param size New size of the array.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_arrayblkof_resize(tommy_arrayblkof* array, tommy_size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements, preserving the allocated capacity.
|
||||||
|
* Call tommy_arrayblkof_shrink() after clearing to restore the initial capacity.
|
||||||
|
* The array remains initialized and can be reused immediately.
|
||||||
|
* \param array The array.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_arrayblkof_clear(tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
tommy_arrayblkof_resize(array, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases unused allocated memory.
|
||||||
|
* Preserves the size, values, and addresses of existing elements.
|
||||||
|
* \param array The array.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_arrayblkof_shrink(tommy_arrayblkof* array);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a reference to the element at the specified position.
|
||||||
* You must be sure that space for this position is already
|
* You must be sure that space for this position is already
|
||||||
* allocated calling tommy_arrayblkof_grow().
|
* allocated by calling tommy_arrayblkof_grow().
|
||||||
|
* \param array The array.
|
||||||
|
* \param pos Position of the element.
|
||||||
|
* \return Pointer to the slot at the specified position.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_arrayblkof_ref(tommy_arrayblkof* array, tommy_size_t pos)
|
tommy_inline void* tommy_arrayblkof_ref(tommy_arrayblkof* array, tommy_size_t pos)
|
||||||
{
|
{
|
||||||
unsigned char* base;
|
|
||||||
|
|
||||||
assert(pos < array->count);
|
assert(pos < array->count);
|
||||||
|
|
||||||
base = tommy_cast(unsigned char*, tommy_array_get(&array->block, pos / TOMMY_ARRAYBLKOF_SIZE));
|
return array->block[pos / TOMMY_ARRAYBLKOF_SIZE] + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size;
|
||||||
|
}
|
||||||
|
|
||||||
return base + (pos % TOMMY_ARRAYBLKOF_SIZE) * array->element_size;
|
/**
|
||||||
|
* Gets a reference to the last element.
|
||||||
|
* The array must not be empty.
|
||||||
|
* \param array The array.
|
||||||
|
* \return Pointer to the last element.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_arrayblkof_tail(tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
assert(array->count != 0);
|
||||||
|
return tommy_arrayblkof_ref(array, array->count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a zero-initialized element and returns its reference.
|
||||||
|
* \param array The array.
|
||||||
|
* \return Pointer to the new element.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_arrayblkof_insert_tail(tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
tommy_size_t pos = array->count;
|
||||||
|
|
||||||
|
++array->count;
|
||||||
|
if (array->count > array->block_count * TOMMY_ARRAYBLKOF_SIZE)
|
||||||
|
tommy_arrayblkof_reserve(array, array->count);
|
||||||
|
|
||||||
|
return tommy_arrayblkof_ref(array, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the last element without copying it.
|
||||||
|
* The array must not be empty. The removed slot is cleared.
|
||||||
|
* \param array The array.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_arrayblkof_remove_tail(tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
assert(array->count != 0);
|
||||||
|
|
||||||
|
tommy_size_t pos = array->count - 1;
|
||||||
|
void* ptr = tommy_arrayblkof_ref(array, pos);
|
||||||
|
|
||||||
|
/* keep unused slots zero so growing within capacity needs no initialization */
|
||||||
|
memset(ptr, 0, array->element_size);
|
||||||
|
array->count = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the array is empty.
|
||||||
|
* \param array The array.
|
||||||
|
* \return Nonzero if the array is empty, 0 otherwise.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_bool_t tommy_arrayblkof_empty(const tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
return array->count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the initialized size of the array.
|
* Gets the initialized size of the array.
|
||||||
|
* \param array The array.
|
||||||
|
* \return The initialized size of the array.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_size_t tommy_arrayblkof_size(tommy_arrayblkof* array)
|
tommy_inline tommy_size_t tommy_arrayblkof_size(const tommy_arrayblkof* array)
|
||||||
{
|
{
|
||||||
return array->count;
|
return array->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the size of allocated memory.
|
* Gets the number of elements that fit without further allocation.
|
||||||
|
* \param array The array.
|
||||||
|
* \return The capacity of the array.
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(tommy_arrayblkof* array);
|
tommy_inline tommy_size_t tommy_arrayblkof_capacity(const tommy_arrayblkof* array)
|
||||||
|
{
|
||||||
|
return array->block_count * TOMMY_ARRAYBLKOF_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges two initialized arrays without copying their elements.
|
||||||
|
* Element references remain valid and belong to the other array.
|
||||||
|
* Passing the same array twice has no effect.
|
||||||
|
* \param first The first array.
|
||||||
|
* \param second The second array.
|
||||||
|
* \note This operation is O(1).
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_arrayblkof_swap(tommy_arrayblkof* first, tommy_arrayblkof* second)
|
||||||
|
{
|
||||||
|
/* keep record size with the storage it describes */
|
||||||
|
tommy_arrayblkof tmp = *first;
|
||||||
|
*first = *second;
|
||||||
|
*second = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the specified function for each element in the array.
|
||||||
|
*
|
||||||
|
* You cannot add or remove elements, nor change the size of the array,
|
||||||
|
* from inside the callback.
|
||||||
|
*
|
||||||
|
* Elements are stored inside the array: passing free() as the callback
|
||||||
|
* is invalid. A callback may release resources owned by each element,
|
||||||
|
* but must not free the element itself. Call tommy_arrayblkof_clear() before
|
||||||
|
* reusing it, or tommy_arrayblkof_done() to release its storage.
|
||||||
|
* \param array The array.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_arrayblkof_foreach(tommy_arrayblkof* array, tommy_foreach_func* func);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the specified function with an argument for each element in the array.
|
||||||
|
*
|
||||||
|
* \param array The array.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
* \param arg Argument to pass to the function.
|
||||||
|
*/
|
||||||
|
TOMMY_API void tommy_arrayblkof_foreach_arg(tommy_arrayblkof* array, tommy_foreach_arg_func* func, void* arg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of allocated memory.
|
||||||
|
* \param array The array.
|
||||||
|
* \return Total memory allocated by the array, in bytes.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_size_t tommy_arrayblkof_memory_usage(const tommy_arrayblkof* array);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
* Do not use this directly. Use lists instead.
|
* Do not use this directly. Use lists instead.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYCHAIN_H
|
#ifndef TOMMY_CHAIN_H
|
||||||
#define __TOMMYCHAIN_H
|
#define TOMMY_CHAIN_H
|
||||||
|
|
||||||
#include "tommytypes.h"
|
#include "tommytypes.h"
|
||||||
|
|
||||||
|
|
@ -65,31 +65,39 @@ tommy_inline void tommy_chain_concat(tommy_node* first_tail, tommy_node* second_
|
||||||
/**
|
/**
|
||||||
* Merges two chains.
|
* Merges two chains.
|
||||||
* \param first First chain (will contain the result).
|
* \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.
|
* \param cmp Comparison function.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp)
|
tommy_inline void tommy_chain_merge(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp)
|
||||||
{
|
{
|
||||||
tommy_node* first_i = first->head;
|
/*
|
||||||
|
* comparators must not change the chains, so cache these endpoints across calls.
|
||||||
|
* also caching first_tail caused register spills with GCC and Clang on x86-64.
|
||||||
|
*/
|
||||||
|
tommy_node* first_head = first->head;
|
||||||
|
tommy_node* second_tail = second->tail;
|
||||||
|
tommy_node* first_i = first_head;
|
||||||
tommy_node* second_i = second->head;
|
tommy_node* second_i = second->head;
|
||||||
|
|
||||||
/* merge */
|
/* merge */
|
||||||
while (1) {
|
while (1) {
|
||||||
if (cmp(first_i->data, second_i->data) > 0) {
|
if (cmp(first_i->data, second_i->data) > 0) {
|
||||||
tommy_node* next = second_i->next;
|
tommy_node* next = second_i->next;
|
||||||
if (first_i == first->head) {
|
if (first_i == first_head) {
|
||||||
tommy_chain_concat(second_i, first_i);
|
tommy_chain_concat(second_i, first_i);
|
||||||
first->head = second_i;
|
first_head = second_i;
|
||||||
|
first->head = first_head;
|
||||||
} else {
|
} else {
|
||||||
tommy_chain_splice(first_i->prev, first_i, second_i, second_i);
|
tommy_chain_splice(first_i->prev, first_i, second_i, second_i);
|
||||||
}
|
}
|
||||||
if (second_i == second->tail)
|
if (second_i == second_tail)
|
||||||
break;
|
break;
|
||||||
second_i = next;
|
second_i = next;
|
||||||
} else {
|
} else {
|
||||||
if (first_i == first->tail) {
|
if (first_i == first->tail) {
|
||||||
tommy_chain_concat(first_i, second_i);
|
tommy_chain_concat(first_i, second_i);
|
||||||
first->tail = second->tail;
|
first->tail = second_tail;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
first_i = first_i->next;
|
first_i = first_i->next;
|
||||||
|
|
@ -98,13 +106,36 @@ tommy_inline void tommy_chain_merge(tommy_chain* first, tommy_chain* second, tom
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges two chains managing special degenerated cases.
|
* Merges two chains containing one node each.
|
||||||
* It's functionally equivalent to tommy_chain_merge() but faster with already ordered chains.
|
* \param first First singleton chain (will contain the result).
|
||||||
* \param first First chain (will contain the result).
|
* \param second Second singleton chain (consumed by the merge; its descriptor is not cleared
|
||||||
* \param second Second chain (will be empty after the merge).
|
* and must not be used as an independent chain afterward).
|
||||||
* \param cmp Comparison function.
|
* \param cmp Comparison function.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain* second, tommy_compare_func* cmp)
|
tommy_inline void tommy_chain_merge_singleton(tommy_chain* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp)
|
||||||
|
{
|
||||||
|
tommy_node* first_node = first->head;
|
||||||
|
tommy_node* second_node = second->head;
|
||||||
|
|
||||||
|
/* keep equal elements in their original order for a stable sort */
|
||||||
|
if (cmp(first_node->data, second_node->data) <= 0) {
|
||||||
|
tommy_chain_concat(first_node, second_node);
|
||||||
|
first->tail = second_node;
|
||||||
|
} else {
|
||||||
|
tommy_chain_concat(second_node, first_node);
|
||||||
|
first->head = second_node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges two chains managing special degenerate 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 (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* tommy_restrict first, tommy_chain* tommy_restrict second, tommy_compare_func* cmp)
|
||||||
{
|
{
|
||||||
/* identify the condition first <= second */
|
/* identify the condition first <= second */
|
||||||
if (cmp(first->tail->data, second->head->data) <= 0) {
|
if (cmp(first->tail->data, second->head->data) <= 0) {
|
||||||
|
|
@ -126,9 +157,9 @@ tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts a chain.
|
* Sorts a chain.
|
||||||
* It's a stable merge sort using power of 2 buckets, with O(N*log(N)) complexity,
|
* It's a stable merge sort using power of 2 buckets, with O(n log n) complexity,
|
||||||
* similar to the one used in the SGI STL libraries and in the Linux Kernel,
|
* similar to the one used in the SGI STL libraries and in the Linux Kernel,
|
||||||
* but faster on degenerated cases like already ordered lists.
|
* but faster on degenerate cases like already ordered lists.
|
||||||
*
|
*
|
||||||
* SGI STL stl_list.h
|
* SGI STL stl_list.h
|
||||||
* http://www.sgi.com/tech/stl/stl_list.h
|
* http://www.sgi.com/tech/stl/stl_list.h
|
||||||
|
|
@ -136,43 +167,48 @@ tommy_inline void tommy_chain_merge_degenerated(tommy_chain* first, tommy_chain*
|
||||||
* Linux Kernel lib/list_sort.c
|
* Linux Kernel lib/list_sort.c
|
||||||
* http://lxr.linux.no/#linux+v2.6.36/lib/list_sort.c
|
* http://lxr.linux.no/#linux+v2.6.36/lib/list_sort.c
|
||||||
*
|
*
|
||||||
* \param chain Chain to sort.
|
* \param chain The chain.
|
||||||
* \param cmp Comparison function.
|
* \param cmp Comparison function.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* cmp)
|
tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func* cmp)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Bit buckets of chains.
|
* Bit buckets of chains.
|
||||||
* Each bucket contains 2^i nodes or it's empty.
|
* Each bucket contains 2^(i+1) nodes or it's empty.
|
||||||
|
* The bucket containing an unpaired final node has one node less.
|
||||||
* The chain at address TOMMY_BIT_MAX is an independent variable operating as "carry".
|
* The chain at address TOMMY_BIT_MAX is an independent variable operating as "carry".
|
||||||
* We keep it in the same "bit" vector to avoid reports from the valgrind tool sgcheck.
|
* We keep it in the same "bit" vector to avoid reports from the valgrind tool sgcheck.
|
||||||
*/
|
*/
|
||||||
tommy_chain bit[TOMMY_SIZE_BIT + 1];
|
tommy_chain bit[TOMMY_SIZE_BIT + 1];
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Value stored inside the bit bucket.
|
* Value stored inside the bit bucket.
|
||||||
* It's used to know which bucket is empty or full.
|
* It's used to know which bucket is empty or full.
|
||||||
*/
|
*/
|
||||||
tommy_size_t counter;
|
tommy_size_t counter = 0;
|
||||||
tommy_node* node = chain->head;
|
tommy_node* node = chain->head;
|
||||||
tommy_node* tail = chain->tail;
|
tommy_node* tail = chain->tail;
|
||||||
tommy_size_t mask;
|
|
||||||
tommy_size_t i;
|
|
||||||
|
|
||||||
counter = 0;
|
|
||||||
while (1) {
|
while (1) {
|
||||||
tommy_node* next;
|
|
||||||
tommy_chain* last;
|
|
||||||
|
|
||||||
/* carry bit to add */
|
/* carry bit to add */
|
||||||
last = &bit[TOMMY_SIZE_BIT];
|
tommy_chain* last = &bit[TOMMY_SIZE_BIT];
|
||||||
bit[TOMMY_SIZE_BIT].head = node;
|
bit[TOMMY_SIZE_BIT].head = node;
|
||||||
bit[TOMMY_SIZE_BIT].tail = node;
|
bit[TOMMY_SIZE_BIT].tail = node;
|
||||||
next = node->next;
|
tommy_node* next = node->next;
|
||||||
|
|
||||||
|
/* start with a sorted pair to halve the number of bucket insertions */
|
||||||
|
if (node != tail) {
|
||||||
|
node = next;
|
||||||
|
/* save the next input node before merging can change the links */
|
||||||
|
next = node->next;
|
||||||
|
tommy_chain second = { node, node };
|
||||||
|
tommy_chain_merge_singleton(last, &second, cmp);
|
||||||
|
}
|
||||||
|
|
||||||
/* add the bit, propagating the carry */
|
/* add the bit, propagating the carry */
|
||||||
i = 0;
|
tommy_size_t i = 0;
|
||||||
mask = counter;
|
tommy_size_t mask = counter;
|
||||||
|
|
||||||
while ((mask & 1) != 0) {
|
while ((mask & 1) != 0) {
|
||||||
tommy_chain_merge_degenerated(&bit[i], last, cmp);
|
tommy_chain_merge_degenerated(&bit[i], last, cmp);
|
||||||
mask >>= 1;
|
mask >>= 1;
|
||||||
|
|
@ -192,8 +228,8 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func*
|
||||||
}
|
}
|
||||||
|
|
||||||
/* merge the buckets */
|
/* merge the buckets */
|
||||||
i = tommy_ctz(counter);
|
tommy_size_t i = tommy_ctz(counter);
|
||||||
mask = counter >> i;
|
tommy_size_t mask = counter >> i;
|
||||||
while (mask != 1) {
|
while (mask != 1) {
|
||||||
mask >>= 1;
|
mask >>= 1;
|
||||||
if (mask & 1)
|
if (mask & 1)
|
||||||
|
|
@ -207,3 +243,4 @@ tommy_inline void tommy_chain_mergesort(tommy_chain* chain, tommy_compare_func*
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause AND MIT
|
||||||
// Copyright (C) 2010 Andrea Mazzoleni
|
// Copyright (C) 2010 Andrea Mazzoleni
|
||||||
|
|
||||||
#include "tommyhash.h"
|
#include "tommyhash.h"
|
||||||
|
|
@ -13,222 +13,178 @@
|
||||||
* They are needed only if BigEndian.
|
* They are needed only if BigEndian.
|
||||||
*/
|
*/
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
#define tommy_swap32(x) __builtin_bswap32(x)
|
#define tommy_imp_hash_swap32(x) __builtin_bswap32(x)
|
||||||
#else
|
#else
|
||||||
tommy_inline tommy_uint32_t tommy_swap32(tommy_uint32_t v)
|
tommy_inline tommy_uint32_t tommy_imp_hash_swap32(tommy_uint32_t v)
|
||||||
{
|
{
|
||||||
return ((v & 0xFF000000) >> 24) |
|
return ((v & 0xFF000000) >> 24) |
|
||||||
((v & 0x00FF0000) >> 8) |
|
((v & 0x00FF0000) >> 8) |
|
||||||
((v & 0x0000FF00) << 8) |
|
((v & 0x0000FF00) << 8) |
|
||||||
((v & 0x000000FF) << 24);
|
((v & 0x000000FF) << 24);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
tommy_inline tommy_uint32_t tommy_le_uint32_read(const void* ptr)
|
tommy_inline tommy_uint32_t tommy_imp_hash_le_uint32_read(const void* ptr)
|
||||||
{
|
{
|
||||||
tommy_uint32_t v;
|
tommy_uint32_t v;
|
||||||
memcpy(&v, ptr, sizeof(v));
|
memcpy(&v, ptr, sizeof(v));
|
||||||
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) || \
|
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) || \
|
||||||
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||||
v = tommy_swap32(v);
|
v = tommy_imp_hash_swap32(v);
|
||||||
#endif
|
#endif
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define tommy_rot(x, k) \
|
/******************************************************************************/
|
||||||
(((x) << (k)) | ((x) >> (32 - (k))))
|
/* a5hash */
|
||||||
|
|
||||||
#define tommy_mix(a, b, c) \
|
/**
|
||||||
do { \
|
* a5hash32 and a5hash (64-bit) by Aleksey Vaneev, from https://github.com/avaneev/a5hash.
|
||||||
a -= c; a ^= tommy_rot(c, 4); c += b; \
|
* Adapted from version 5.25 with little-endian word reads.
|
||||||
b -= a; b ^= tommy_rot(a, 6); a += c; \
|
* The 32-bit variant additionally mixes the expanded seed asymmetrically into
|
||||||
c -= b; c ^= tommy_rot(b, 8); b += a; \
|
* h[2] and h[3] before processing the input and produces different hash values.
|
||||||
a -= c; a ^= tommy_rot(c, 16); c += b; \
|
* Copyright (c) 2025 Aleksey Vaneev. Released under the MIT license, see LICENSES/MIT.
|
||||||
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) \
|
tommy_inline void tommy_mul(tommy_uint32_t* lo, tommy_uint32_t* hi)
|
||||||
do { \
|
{
|
||||||
c ^= b; c -= tommy_rot(b, 14); \
|
/* both operands are 32-bit; the full product supplies two state words */
|
||||||
a ^= c; a -= tommy_rot(c, 11); \
|
tommy_uint64_t v = (tommy_uint64_t)*lo * *hi;
|
||||||
b ^= a; b -= tommy_rot(a, 25); \
|
*lo = (tommy_uint32_t)v;
|
||||||
c ^= b; c -= tommy_rot(b, 16); \
|
*hi = v >> 32;
|
||||||
a ^= c; a -= tommy_rot(c, 4); \
|
}
|
||||||
b ^= a; b -= tommy_rot(a, 14); \
|
|
||||||
c ^= b; c -= tommy_rot(b, 24); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len)
|
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);
|
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
|
||||||
tommy_uint32_t a, b, c;
|
tommy_uint32_t val01 = 0x55555555;
|
||||||
|
tommy_uint32_t val10 = 0xaaaaaaaa;
|
||||||
|
tommy_uint32_t h[4] = { 0x243f6a88, 0x85a308d3, 0xfb0bd3ea, 0x0f58fd47 };
|
||||||
|
tommy_uint32_t a, b;
|
||||||
|
tommy_size_t len = key_len;
|
||||||
|
|
||||||
a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + init_val;
|
h[0] ^= key_len;
|
||||||
|
h[1] ^= key_len;
|
||||||
|
#if TOMMY_SIZE_BIT == 64
|
||||||
|
h[2] = (key_len >> 32) ^ 0x452821e6;
|
||||||
|
h[3] = (key_len >> 32) ^ 0x38d01377;
|
||||||
|
tommy_mul(&h[2], &h[3]);
|
||||||
|
#endif
|
||||||
|
h[0] ^= init_val & val01;
|
||||||
|
h[1] ^= init_val & val10;
|
||||||
|
tommy_mul(&h[0], &h[1]);
|
||||||
|
|
||||||
while (key_len > 12) {
|
/*
|
||||||
a += tommy_le_uint32_read(key + 0);
|
* Mix the expanded seed asymmetrically into h[2] and h[3] so that short keys
|
||||||
b += tommy_le_uint32_read(key + 4);
|
* skipping the main loop still depend on the seed, and neither h[0] nor h[1]
|
||||||
c += tommy_le_uint32_read(key + 8);
|
* cancels out when folding h[0] ^= h[2] and h[1] ^= h[3] at the end.
|
||||||
|
*/
|
||||||
|
h[2] ^= h[0] ^ h[1];
|
||||||
|
h[3] ^= h[0];
|
||||||
|
|
||||||
tommy_mix(a, b, c);
|
if (len <= 16) {
|
||||||
|
if (len >= 4) {
|
||||||
|
const unsigned char* tail = key + len - 4;
|
||||||
|
a = tommy_imp_hash_le_uint32_read(key);
|
||||||
|
b = tommy_imp_hash_le_uint32_read(tail);
|
||||||
|
if (len >= 9) {
|
||||||
|
tommy_size_t offset = (len / 8) * 4;
|
||||||
|
h[2] += tommy_imp_hash_le_uint32_read(key + offset);
|
||||||
|
h[3] += tommy_imp_hash_le_uint32_read(tail - offset);
|
||||||
|
tommy_mul(&h[2], &h[3]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a = 0;
|
||||||
|
b = 0;
|
||||||
|
if (len != 0) {
|
||||||
|
a = key[0];
|
||||||
|
if (len >= 2)
|
||||||
|
a |= key[1] << 8;
|
||||||
|
if (len == 3)
|
||||||
|
a |= key[2] << 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val01 ^= h[0];
|
||||||
|
val10 ^= h[1];
|
||||||
|
do {
|
||||||
|
tommy_uint32_t s1 = h[0];
|
||||||
|
tommy_uint32_t s4 = h[3];
|
||||||
|
h[0] += tommy_imp_hash_le_uint32_read(key);
|
||||||
|
h[1] += tommy_imp_hash_le_uint32_read(key + 4);
|
||||||
|
tommy_mul(&h[0], &h[1]);
|
||||||
|
h[2] += tommy_imp_hash_le_uint32_read(key + 8);
|
||||||
|
h[3] += tommy_imp_hash_le_uint32_read(key + 12);
|
||||||
|
tommy_mul(&h[2], &h[3]);
|
||||||
|
h[0] += val01;
|
||||||
|
h[1] += s4;
|
||||||
|
h[2] += s1;
|
||||||
|
h[3] += val10;
|
||||||
|
key += 16;
|
||||||
|
len -= 16;
|
||||||
|
} while (len > 16);
|
||||||
|
|
||||||
key_len -= 12;
|
/* the last words may overlap the previous block, but stay within the input */
|
||||||
key += 12;
|
const unsigned char* tail = key + len;
|
||||||
|
a = tommy_imp_hash_le_uint32_read(tail - 8);
|
||||||
|
b = tommy_imp_hash_le_uint32_read(tail - 4);
|
||||||
|
if (len >= 9) {
|
||||||
|
h[2] += tommy_imp_hash_le_uint32_read(tail - 16);
|
||||||
|
h[3] += tommy_imp_hash_le_uint32_read(tail - 12);
|
||||||
|
tommy_mul(&h[2], &h[3]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (key_len) {
|
h[0] ^= h[2];
|
||||||
case 0 :
|
h[1] ^= h[3];
|
||||||
return c; /* used only when called with a zero length */
|
h[0] += a;
|
||||||
case 12 :
|
h[1] += b;
|
||||||
c += tommy_le_uint32_read(key + 8);
|
tommy_mul(&h[0], &h[1]);
|
||||||
b += tommy_le_uint32_read(key + 4);
|
h[0] ^= val01;
|
||||||
a += tommy_le_uint32_read(key + 0);
|
tommy_mul(&h[0], &h[1]);
|
||||||
break;
|
return h[0] ^ h[1];
|
||||||
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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
tommy_final(a, b, c);
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len)
|
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);
|
const unsigned char* key = tommy_cast(const unsigned char*, void_key);
|
||||||
tommy_uint32_t a, b, c;
|
tommy_uint64_t val01 = 0x5555555555555555ULL;
|
||||||
|
tommy_uint64_t val10 = 0xaaaaaaaaaaaaaaaaULL;
|
||||||
|
tommy_uint64_t h[2] = { (0x452821e638d01377ULL ^ key_len) ^ (init_val & val10), (0x243f6a8885a308d3ULL ^ key_len) ^ (init_val & val01) };
|
||||||
|
tommy_size_t len = key_len;
|
||||||
|
|
||||||
a = b = c = 0xdeadbeef + ((tommy_uint32_t)key_len) + (init_val & 0xffffffff);
|
tommy_mum(&h[0], &h[1]);
|
||||||
c += init_val >> 32;
|
if (len > 16) {
|
||||||
|
val01 ^= h[0];
|
||||||
while (key_len > 12) {
|
val10 ^= h[1];
|
||||||
a += tommy_le_uint32_read(key + 0);
|
do {
|
||||||
b += tommy_le_uint32_read(key + 4);
|
/* each 64-bit operand puts the first little-endian word in its high half */
|
||||||
c += tommy_le_uint32_read(key + 8);
|
h[0] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key) << 32 ^ tommy_imp_hash_le_uint32_read(key + 4);
|
||||||
|
h[1] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key + 8) << 32 ^ tommy_imp_hash_le_uint32_read(key + 12);
|
||||||
tommy_mix(a, b, c);
|
tommy_mum(&h[0], &h[1]);
|
||||||
|
h[0] += val01;
|
||||||
key_len -= 12;
|
h[1] += val10;
|
||||||
key += 12;
|
key += 16;
|
||||||
|
len -= 16;
|
||||||
|
} while (len > 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (key_len) {
|
if (len >= 4) {
|
||||||
case 0 :
|
const unsigned char* tail = key + len - 4;
|
||||||
return c + ((tommy_uint64_t)b << 32); /* used only when called with a zero length */
|
tommy_size_t offset = (len / 8) * 4;
|
||||||
case 12 :
|
h[0] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key) << 32 | tommy_imp_hash_le_uint32_read(tail);
|
||||||
c += tommy_le_uint32_read(key + 8);
|
h[1] ^= (tommy_uint64_t)tommy_imp_hash_le_uint32_read(key + offset) << 32 | tommy_imp_hash_le_uint32_read(tail - offset);
|
||||||
b += tommy_le_uint32_read(key + 4);
|
} else if (len != 0) {
|
||||||
a += tommy_le_uint32_read(key + 0);
|
h[0] ^= key[0];
|
||||||
break;
|
if (len >= 2)
|
||||||
case 11 : c += ((tommy_uint32_t)key[10]) << 16; /* fallthrough */
|
h[0] ^= key[1] << 8;
|
||||||
case 10 : c += ((tommy_uint32_t)key[9]) << 8; /* fallthrough */
|
if (len == 3)
|
||||||
case 9 : c += key[8]; /* fallthrough */
|
h[0] ^= key[2] << 16;
|
||||||
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 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_final(a, b, c);
|
tommy_mum(&h[0], &h[1]);
|
||||||
|
h[0] ^= val01;
|
||||||
return c + ((tommy_uint64_t)b << 32);
|
tommy_mum(&h[0], &h[1]);
|
||||||
}
|
return h[0] ^ h[1];
|
||||||
|
|
||||||
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 };
|
|
||||||
|
|
||||||
a = b = c = 0xdeadbeef + init_val;
|
|
||||||
/* this is different than original lookup3 and the result won't match */
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
tommy_uint32_t v = tommy_le_uint32_read(key);
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
a += v;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 */
|
|
||||||
|
|
||||||
tommy_final(a, b, c);
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,79 +2,145 @@
|
||||||
// Copyright (C) 2010 Andrea Mazzoleni
|
// Copyright (C) 2010 Andrea Mazzoleni
|
||||||
|
|
||||||
/** \file
|
/** \file
|
||||||
* Hash functions for the use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin.
|
* Hash functions for use with ::tommy_hashtable, ::tommy_hashdyn and ::tommy_hashlin.
|
||||||
|
*
|
||||||
|
* This module provides hash functions designed for in-memory data structures.
|
||||||
|
* ::tommy_hash_u32 and ::tommy_strhash_u32 use a modified a5hash32, while
|
||||||
|
* ::tommy_hash_u64 and ::tommy_strhash_u64 use a5hash (64-bit).
|
||||||
|
* Reversible integer hash functions are also provided for 32-bit and 64-bit keys.
|
||||||
|
*
|
||||||
|
* a5hash32 is intended for 32-bit platforms and uses 32-bit operands with
|
||||||
|
* full 64-bit multiplication results. a5hash uses 64-bit operands with
|
||||||
|
* full 128-bit multiplication results. Both algorithms pass SMHasher3 tests.
|
||||||
|
*
|
||||||
|
* Prefer the 64-bit version whenever possible, as it provides better resistance
|
||||||
|
* to collisions, particularly with specially crafted inputs. However, neither
|
||||||
|
* version is designed to resist deliberate collision attacks. If resistance to
|
||||||
|
* such attacks is required, use a dedicated hash function designed for that purpose.
|
||||||
|
*
|
||||||
|
* All hash functions are intended for runtime in-memory use only and not
|
||||||
|
* for persistent storage, as hash compatibility across different versions of the
|
||||||
|
* library is not guaranteed. String and sized buffer hash functions produce
|
||||||
|
* identical results for 0-terminated strings.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYHASH_H
|
#ifndef TOMMY_HASH_H
|
||||||
#define __TOMMYHASH_H
|
#define TOMMY_HASH_H
|
||||||
|
|
||||||
#include "tommytypes.h"
|
#include "tommytypes.h"
|
||||||
|
#include <string.h> /* for strlen */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* hash */
|
/* hash */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash function with a 32 bits result.
|
* Hash function with a 32-bit result.
|
||||||
* Implementation of the Robert Jenkins "lookup3" hash 32 bits version,
|
* Modified implementation of a5hash32 by Aleksey Vaneev,
|
||||||
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle().
|
* from https://github.com/avaneev/a5hash
|
||||||
*
|
*
|
||||||
* This hash is designed to provide a good overall performance on all platforms,
|
* The expanded seed is mixed asymmetrically into the two additional state words
|
||||||
* including 32 bits. If you target only 64 bits, you can use faster hashes,
|
* before processing the input. This preserves seed dependence for short keys
|
||||||
* like SpookyHash or FarmHash.
|
* that skip the main loop and avoids cancellation of either seed state word
|
||||||
|
* in the final XOR fold. The resulting hash values differ from upstream a5hash32.
|
||||||
|
*
|
||||||
|
* This algorithm is compact, fast on short inputs, and passes SMHasher3 tests.
|
||||||
*
|
*
|
||||||
* \param init_val Initialization value.
|
* \param init_val Initialization value.
|
||||||
* Using a different initialization value, you can generate a completely different set of hash values.
|
* Using a different initialization value, you can generate a completely different set of hash values.
|
||||||
* Use 0 if not relevant.
|
* Use 0 if not relevant.
|
||||||
* \param void_key Pointer to the data to hash.
|
* \param void_key Pointer to the data to hash. Must not be 0, even if key_len is 0.
|
||||||
* \param key_len Size of the data to hash.
|
* \param key_len Size of the data to hash.
|
||||||
|
* \return The 32-bit hash value.
|
||||||
* \note
|
* \note
|
||||||
* This function is endianness independent.
|
* This function is endianness independent and alignment independent.
|
||||||
* \return The hash value of 32 bits.
|
* 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 non-cryptographic and is not designed to protect against malicious collision attacks.
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_uint32_t tommy_hash_u32(tommy_uint32_t init_val, const void* void_key, tommy_size_t key_len);
|
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.
|
* Hash function with a 64-bit result.
|
||||||
* Implementation of the Robert Jenkins "lookup3" hash 64 bits version,
|
* Implementation of a5hash (64-bit) by Aleksey Vaneev,
|
||||||
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle2().
|
* from https://github.com/avaneev/a5hash
|
||||||
*
|
*
|
||||||
* This hash is designed to provide a good overall performance on all platforms,
|
* This algorithm is compact, fast on short inputs, and passes SMHasher3 tests.
|
||||||
* including 32 bits. If you target only 64 bits, you can use faster hashes,
|
|
||||||
* like SpookyHash or FarmHash.
|
|
||||||
*
|
*
|
||||||
* \param init_val Initialization value.
|
* \param init_val Initialization value.
|
||||||
* Using a different initialization value, you can generate a completely different set of hash values.
|
* Using a different initialization value, you can generate a completely different set of hash values.
|
||||||
* Use 0 if not relevant.
|
* Use 0 if not relevant.
|
||||||
* \param void_key Pointer to the data to hash.
|
* \param void_key Pointer to the data to hash. Must not be 0, even if key_len is 0.
|
||||||
* \param key_len Size of the data to hash.
|
* \param key_len Size of the data to hash.
|
||||||
|
* \return The 64-bit hash value.
|
||||||
* \note
|
* \note
|
||||||
* This function is endianness independent.
|
* This function is endianness independent and alignment independent.
|
||||||
* \return The hash value of 64 bits.
|
* 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 non-cryptographic and is not designed to protect against malicious collision attacks.
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_uint64_t tommy_hash_u64(tommy_uint64_t init_val, const void* void_key, tommy_size_t key_len);
|
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.
|
* String hash function with a 32-bit result.
|
||||||
* Implementation is based on Robert Jenkins "lookup3" hash 32 bits version,
|
* Modified implementation of a5hash32 by Aleksey Vaneev,
|
||||||
* from http://www.burtleburtle.net/bob/hash/doobs.html, function hashlittle().
|
* from https://github.com/avaneev/a5hash
|
||||||
*
|
*
|
||||||
* This hash is designed to handle strings with an unknown length. If you
|
* This hash is designed to handle strings with an unknown length.
|
||||||
* know the string length, the other hash functions are surely faster.
|
* It is implemented inline using strlen() followed by ::tommy_hash_u32.
|
||||||
|
* This gives better performance than scanning for the terminator while hashing.
|
||||||
|
* It uses the same modified seed mixing and differs from upstream a5hash32.
|
||||||
*
|
*
|
||||||
* \param init_val Initialization value.
|
* \param init_val Initialization value.
|
||||||
* Using a different initialization value, you can generate a completely different set of hash values.
|
* Using a different initialization value, you can generate a completely different set of hash values.
|
||||||
* Use 0 if not relevant.
|
* Use 0 if not relevant.
|
||||||
* \param void_key Pointer to the string to hash. It has to be 0 terminated.
|
* \param void_key Pointer to the string to hash. It has to be 0-terminated.
|
||||||
|
* \return The 32-bit hash value.
|
||||||
* \note
|
* \note
|
||||||
* This function is endianness independent.
|
* This function is endianness independent and alignment independent.
|
||||||
* \return The hash value of 32 bits.
|
* It produces identical hash results to ::tommy_hash_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 non-cryptographic and is not designed to protect against malicious collision attacks.
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key);
|
tommy_inline tommy_uint32_t tommy_strhash_u32(tommy_uint32_t init_val, const void* void_key)
|
||||||
|
{
|
||||||
|
return tommy_hash_u32(init_val, void_key, strlen(tommy_cast(const char*, void_key)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integer reversible hash function for 32 bits.
|
* String hash function with a 64-bit result.
|
||||||
|
* Implementation of a5hash (64-bit) by Aleksey Vaneev,
|
||||||
|
* from https://github.com/avaneev/a5hash
|
||||||
|
*
|
||||||
|
* This hash is designed to handle strings with an unknown length.
|
||||||
|
* It is implemented inline using strlen() followed by ::tommy_hash_u64.
|
||||||
|
* This gives better performance than scanning for the terminator while hashing.
|
||||||
|
*
|
||||||
|
* \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.
|
||||||
|
* \return The 64-bit hash value.
|
||||||
|
* \note
|
||||||
|
* This function is endianness independent and alignment independent.
|
||||||
|
* It produces identical hash results to ::tommy_hash_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 non-cryptographic and is not designed to protect against malicious collision attacks.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_uint64_t tommy_strhash_u64(tommy_uint64_t init_val, const void* void_key)
|
||||||
|
{
|
||||||
|
return tommy_hash_u64(init_val, void_key, strlen(tommy_cast(const char*, void_key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reversible 32-bit integer hash function.
|
||||||
* Implementation of the Robert Jenkins "4-byte Integer Hashing",
|
* Implementation of the Robert Jenkins "4-byte Integer Hashing",
|
||||||
* from http://burtleburtle.net/bob/hash/integer.html
|
* from http://burtleburtle.net/bob/hash/integer.html
|
||||||
|
* \param key The 32-bit integer to hash.
|
||||||
|
* \return The 32-bit hash value.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key)
|
tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key)
|
||||||
{
|
{
|
||||||
|
|
@ -90,9 +156,11 @@ tommy_inline tommy_uint32_t tommy_inthash_u32(tommy_uint32_t key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integer reversible hash function for 64 bits.
|
* Reversible 64-bit integer hash function.
|
||||||
* Implementation of the Thomas Wang "Integer Hash Function",
|
* Implementation of the Thomas Wang "Integer Hash Function",
|
||||||
* from http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm
|
* from http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm
|
||||||
|
* \param key The 64-bit integer to hash.
|
||||||
|
* \return The 64-bit hash value.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key)
|
tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key)
|
||||||
{
|
{
|
||||||
|
|
@ -107,4 +175,146 @@ tommy_inline tommy_uint64_t tommy_inthash_u64(tommy_uint64_t key)
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reversible 32-bit integer hash function using multiplications.
|
||||||
|
* Implementation of the MurmurHash3 fmix32 finalizer by Austin Appleby,
|
||||||
|
* from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
|
||||||
|
* \param key The 32-bit integer to hash.
|
||||||
|
* \return The 32-bit hash value.
|
||||||
|
* \note Requires an architecture with fast 32-bit integer multiplication
|
||||||
|
* for good performance. See tommy_inthash_u32() for an alternative.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_uint32_t tommy_mulhash_u32(tommy_uint32_t key)
|
||||||
|
{
|
||||||
|
key ^= key >> 16;
|
||||||
|
key *= 0x85ebca6bU;
|
||||||
|
key ^= key >> 13;
|
||||||
|
key *= 0xc2b2ae35U;
|
||||||
|
key ^= key >> 16;
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reversible 64-bit integer hash function using multiplications.
|
||||||
|
* Implementation of the SplitMix64 finalizer,
|
||||||
|
* from https://prng.di.unimi.it/splitmix64.c
|
||||||
|
* \param key The 64-bit integer to hash.
|
||||||
|
* \return The 64-bit hash value.
|
||||||
|
* \note Requires an architecture with fast 64-bit integer multiplication
|
||||||
|
* for good performance. It is generally not recommended on 32-bit architectures,
|
||||||
|
* where these multiplications are expensive. Consider tommy_inthash_u64() instead.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_uint64_t tommy_mulhash_u64(tommy_uint64_t key)
|
||||||
|
{
|
||||||
|
key ^= key >> 30;
|
||||||
|
key *= 0xbf58476d1ce4e5b9ULL;
|
||||||
|
key ^= key >> 27;
|
||||||
|
key *= 0x94d049bb133111ebULL;
|
||||||
|
key ^= key >> 31;
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
/* multiplication */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies two 64-bit unsigned numbers to obtain the full 128-bit product.
|
||||||
|
*
|
||||||
|
* \param A Address of the first operand, overwritten with the low 64 bits of the product.
|
||||||
|
* \param B Address of the second operand, overwritten with the high 64 bits of the product.
|
||||||
|
*/
|
||||||
|
#if defined(_MSC_VER) && defined(_M_X64)
|
||||||
|
/*
|
||||||
|
* MSVC x64 does not support inline assembly, so use its full-product intrinsic.
|
||||||
|
*/
|
||||||
|
#pragma intrinsic(_umul128)
|
||||||
|
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
|
||||||
|
{
|
||||||
|
tommy_uint64_t high;
|
||||||
|
tommy_uint64_t low = _umul128(*A, *B, &high);
|
||||||
|
*A = low;
|
||||||
|
*B = high;
|
||||||
|
}
|
||||||
|
#elif defined(_MSC_VER) && defined(_M_ARM64)
|
||||||
|
#pragma intrinsic(__umulh)
|
||||||
|
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
|
||||||
|
{
|
||||||
|
tommy_uint64_t a = *A;
|
||||||
|
tommy_uint64_t b = *B;
|
||||||
|
*A = a * b;
|
||||||
|
*B = __umulh(a, b);
|
||||||
|
}
|
||||||
|
#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
|
||||||
|
/**
|
||||||
|
* Keep this before __SIZEOF_INT128__ because the explicit mulq generates more efficient code.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
|
||||||
|
{
|
||||||
|
tommy_uint64_t a = *A;
|
||||||
|
tommy_uint64_t b = *B;
|
||||||
|
tommy_uint64_t low, high;
|
||||||
|
|
||||||
|
/* require a register to avoid Clang spilling the operand before mulq */
|
||||||
|
__asm__ (
|
||||||
|
"mulq %3"
|
||||||
|
: "=a" (low), "=d" (high)
|
||||||
|
: "a" (a), "r" (b)
|
||||||
|
: "cc"
|
||||||
|
);
|
||||||
|
|
||||||
|
*A = low;
|
||||||
|
*B = high;
|
||||||
|
}
|
||||||
|
#elif defined(__SIZEOF_INT128__)
|
||||||
|
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
|
||||||
|
{
|
||||||
|
__uint128_t product = (__uint128_t)*A * *B;
|
||||||
|
*A = product;
|
||||||
|
*B = product >> 64;
|
||||||
|
}
|
||||||
|
#elif defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__))
|
||||||
|
/*
|
||||||
|
* Keep this after __SIZEOF_INT128__ because the assembly path failed optimized
|
||||||
|
* hash tests on Apple clang 21.0.0. See https://github.com/amadvance/snapraid/issues/43.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
|
||||||
|
{
|
||||||
|
tommy_uint64_t a = *A;
|
||||||
|
tommy_uint64_t b = *B;
|
||||||
|
tommy_uint64_t low = a * b;
|
||||||
|
tommy_uint64_t high;
|
||||||
|
__asm__ (
|
||||||
|
"umulh %0, %1, %2"
|
||||||
|
: "=r" (high)
|
||||||
|
: "r" (a), "r" (b)
|
||||||
|
);
|
||||||
|
*A = low;
|
||||||
|
*B = high;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
tommy_inline void tommy_mum(tommy_uint64_t* A, tommy_uint64_t* B)
|
||||||
|
{
|
||||||
|
tommy_uint64_t a = *A;
|
||||||
|
tommy_uint64_t b = *B;
|
||||||
|
tommy_uint64_t a_lo = (tommy_uint32_t)a;
|
||||||
|
tommy_uint64_t a_hi = a >> 32;
|
||||||
|
tommy_uint64_t b_lo = (tommy_uint32_t)b;
|
||||||
|
tommy_uint64_t b_hi = b >> 32;
|
||||||
|
tommy_uint64_t p0 = a_lo * b_lo;
|
||||||
|
tommy_uint64_t p1 = a_lo * b_hi;
|
||||||
|
tommy_uint64_t p2 = a_hi * b_lo;
|
||||||
|
tommy_uint64_t p3 = a_hi * b_hi;
|
||||||
|
|
||||||
|
/* retain the carry from the middle words in the high half */
|
||||||
|
tommy_uint64_t middle = p1 + (p0 >> 32) + (tommy_uint32_t)p2;
|
||||||
|
tommy_uint64_t low = (middle << 32) | (tommy_uint32_t)p0;
|
||||||
|
tommy_uint64_t high = p3 + (middle >> 32) + (p2 >> 32);
|
||||||
|
*A = low;
|
||||||
|
*B = high;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
#include "tommyhashdyn.h"
|
#include "tommyhashdyn.h"
|
||||||
#include "tommylist.h"
|
#include "tommylist.h"
|
||||||
|
|
||||||
|
#include <string.h> /* for memset */
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* hashdyn */
|
/* hashdyn */
|
||||||
|
|
||||||
|
|
@ -23,10 +25,16 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn)
|
||||||
tommy_free(hashdyn->bucket);
|
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.
|
* Resize the bucket vector.
|
||||||
*/
|
*/
|
||||||
static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit)
|
static void tommy_imp_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit)
|
||||||
{
|
{
|
||||||
tommy_size_t bucket_bit = hashdyn->bucket_bit;
|
tommy_size_t bucket_bit = hashdyn->bucket_bit;
|
||||||
tommy_size_t bucket_max = hashdyn->bucket_max;
|
tommy_size_t bucket_max = hashdyn->bucket_max;
|
||||||
|
|
@ -37,57 +45,72 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket
|
||||||
if (!hashdyn->count) {
|
if (!hashdyn->count) {
|
||||||
/* empty table: allocate zeroed memory with calloc without looping */
|
/* 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*)));
|
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) {
|
} else if (new_bucket_bit > bucket_bit) {
|
||||||
tommy_size_t i;
|
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*)));
|
||||||
|
|
||||||
/* grow by multiple bits with elements: allocate zeroed memory and reinsert */
|
for (tommy_size_t i = 0; i < bucket_max; ++i) {
|
||||||
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*)));
|
new_bucket[i] = 0;
|
||||||
|
new_bucket[i + bucket_max] = 0;
|
||||||
|
|
||||||
for (i = 0; i < bucket_max; ++i) {
|
tommy_hashdyn_node* j = hashdyn->bucket[i];
|
||||||
tommy_hashdyn_node* j = hashdyn->bucket[i];
|
if (j && !j->next) {
|
||||||
|
/* growth keeps old buckets separate, and singleton links are already valid */
|
||||||
|
new_bucket[j->index & new_bucket_mask] = j;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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*)));
|
||||||
|
|
||||||
while (j) {
|
for (tommy_size_t i = 0; i < bucket_max; ++i) {
|
||||||
tommy_hashdyn_node* j_next = j->next;
|
tommy_hashdyn_node* j = hashdyn->bucket[i];
|
||||||
tommy_size_t pos = j->index & new_bucket_mask;
|
|
||||||
if (new_bucket[pos])
|
if (j && !j->next) {
|
||||||
tommy_list_insert_tail_not_empty(new_bucket[pos], j);
|
/* growth keeps old buckets separate, and singleton links are already valid */
|
||||||
else
|
new_bucket[j->index & new_bucket_mask] = j;
|
||||||
tommy_list_insert_first(&new_bucket[pos], j);
|
continue;
|
||||||
j = j_next;
|
}
|
||||||
|
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 {
|
} else {
|
||||||
tommy_size_t i;
|
/* all buckets are overwritten, no pre-zeroing needed */
|
||||||
|
|
||||||
/* shrink with elements: all buckets are overwritten, no pre-zeroing needed */
|
|
||||||
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*)));
|
new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*)));
|
||||||
|
|
||||||
for (i = 0; i < new_bucket_max; ++i) {
|
if (new_bucket_bit + 1 == bucket_bit) {
|
||||||
new_bucket[i] = hashdyn->bucket[i];
|
/* shrink by 1 bit: each new bucket joins exactly two old buckets */
|
||||||
tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]);
|
for (tommy_size_t 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 (tommy_size_t i = 0; i < new_bucket_max; ++i) {
|
||||||
|
/* all old buckets with the same new modulus must be concatenated. */
|
||||||
|
new_bucket[i] = hashdyn->bucket[i];
|
||||||
|
for (tommy_size_t j = i + new_bucket_max; j < bucket_max; j += new_bucket_max)
|
||||||
|
tommy_list_concat(&new_bucket[i], &hashdyn->bucket[j]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,21 +126,21 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket
|
||||||
/**
|
/**
|
||||||
* Grow.
|
* Grow.
|
||||||
*/
|
*/
|
||||||
tommy_inline void hashdyn_grow_step(tommy_hashdyn* hashdyn)
|
tommy_inline void tommy_imp_hashdyn_grow_step(tommy_hashdyn* hashdyn)
|
||||||
{
|
{
|
||||||
/* grow if more than 50% full */
|
/* grow if at least 50% full */
|
||||||
if (hashdyn->count >= hashdyn->bucket_max / 2)
|
if (hashdyn->count >= hashdyn->bucket_max / 2)
|
||||||
tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1);
|
tommy_imp_hashdyn_resize(hashdyn, hashdyn->bucket_bit + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shrink.
|
* Shrink.
|
||||||
*/
|
*/
|
||||||
tommy_inline void hashdyn_shrink_step(tommy_hashdyn* hashdyn)
|
tommy_inline void tommy_imp_hashdyn_shrink_step(tommy_hashdyn* hashdyn)
|
||||||
{
|
{
|
||||||
/* shrink if less than 12.5% full */
|
/* shrink if at most 12.5% full */
|
||||||
if (hashdyn->count <= hashdyn->bucket_max / 8 && hashdyn->bucket_bit > TOMMY_HASHDYN_BIT)
|
if (hashdyn->count <= hashdyn->bucket_max / 8 && hashdyn->bucket_bit > TOMMY_HASHDYN_BIT)
|
||||||
tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1);
|
tommy_imp_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count)
|
TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count)
|
||||||
|
|
@ -130,7 +153,20 @@ TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count)
|
||||||
if (bucket_bit <= hashdyn->bucket_bit)
|
if (bucket_bit <= hashdyn->bucket_bit)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tommy_hashdyn_resize(hashdyn, bucket_bit);
|
tommy_imp_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_imp_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_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash)
|
||||||
|
|
@ -143,7 +179,42 @@ TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node*
|
||||||
|
|
||||||
++hashdyn->count;
|
++hashdyn->count;
|
||||||
|
|
||||||
hashdyn_grow_step(hashdyn);
|
tommy_imp_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)
|
||||||
|
{
|
||||||
|
tommy_hashdyn_node** bucket = &hashdyn->bucket[hash & hashdyn->bucket_mask];
|
||||||
|
tommy_hashdyn_node* i = *bucket;
|
||||||
|
|
||||||
|
while (i) {
|
||||||
|
/* compare keys only for matching full hashes, as the bucket may contain different hashes */
|
||||||
|
if (i->index == hash && cmp(cmp_arg, i->data) == 0)
|
||||||
|
return i->data;
|
||||||
|
i = i->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* insert before resizing, while the bucket selected for the search is still valid */
|
||||||
|
tommy_list_insert_tail(bucket, node, data);
|
||||||
|
|
||||||
|
node->index = hash;
|
||||||
|
|
||||||
|
++hashdyn->count;
|
||||||
|
|
||||||
|
tommy_imp_hashdyn_grow_step(hashdyn);
|
||||||
|
|
||||||
|
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_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
|
||||||
|
|
@ -154,7 +225,7 @@ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hash
|
||||||
|
|
||||||
--hashdyn->count;
|
--hashdyn->count;
|
||||||
|
|
||||||
hashdyn_shrink_step(hashdyn);
|
tommy_imp_hashdyn_shrink_step(hashdyn);
|
||||||
|
|
||||||
return node->data;
|
return node->data;
|
||||||
}
|
}
|
||||||
|
|
@ -171,7 +242,7 @@ TOMMY_API void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func*
|
||||||
|
|
||||||
--hashdyn->count;
|
--hashdyn->count;
|
||||||
|
|
||||||
hashdyn_shrink_step(hashdyn);
|
tommy_imp_hashdyn_shrink_step(hashdyn);
|
||||||
|
|
||||||
return node->data;
|
return node->data;
|
||||||
}
|
}
|
||||||
|
|
@ -185,9 +256,8 @@ TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func*
|
||||||
{
|
{
|
||||||
tommy_size_t bucket_max = hashdyn->bucket_max;
|
tommy_size_t bucket_max = hashdyn->bucket_max;
|
||||||
tommy_hashdyn_node** bucket = hashdyn->bucket;
|
tommy_hashdyn_node** bucket = hashdyn->bucket;
|
||||||
tommy_size_t pos;
|
|
||||||
|
|
||||||
for (pos = 0; pos < bucket_max; ++pos) {
|
for (tommy_size_t pos = 0; pos < bucket_max; ++pos) {
|
||||||
tommy_hashdyn_node* node = bucket[pos];
|
tommy_hashdyn_node* node = bucket[pos];
|
||||||
|
|
||||||
while (node) {
|
while (node) {
|
||||||
|
|
@ -202,9 +272,8 @@ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_a
|
||||||
{
|
{
|
||||||
tommy_size_t bucket_max = hashdyn->bucket_max;
|
tommy_size_t bucket_max = hashdyn->bucket_max;
|
||||||
tommy_hashdyn_node** bucket = hashdyn->bucket;
|
tommy_hashdyn_node** bucket = hashdyn->bucket;
|
||||||
tommy_size_t pos;
|
|
||||||
|
|
||||||
for (pos = 0; pos < bucket_max; ++pos) {
|
for (tommy_size_t pos = 0; pos < bucket_max; ++pos) {
|
||||||
tommy_hashdyn_node* node = bucket[pos];
|
tommy_hashdyn_node* node = bucket[pos];
|
||||||
|
|
||||||
while (node) {
|
while (node) {
|
||||||
|
|
@ -215,68 +284,180 @@ TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn)
|
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(const tommy_hashdyn* hashdyn)
|
||||||
{
|
{
|
||||||
return hashdyn->bucket_max * (tommy_size_t)sizeof(hashdyn->bucket[0])
|
return hashdyn->bucket_max * (tommy_size_t)sizeof(hashdyn->bucket[0])
|
||||||
+ tommy_hashdyn_count(hashdyn) * (tommy_size_t)sizeof(tommy_hashdyn_node);
|
+ 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_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list)
|
||||||
{
|
{
|
||||||
tommy_size_t bucket_max = hashdyn->bucket_max;
|
tommy_builder builder;
|
||||||
tommy_hashdyn_node** bucket = hashdyn->bucket;
|
tommy_node* builder_tail = tommy_builder_init(&builder);
|
||||||
tommy_size_t pos;
|
|
||||||
|
|
||||||
/* move everything to the list */
|
/* clear buckets as they are transferred to avoid a second scan */
|
||||||
for (pos = 0; pos < bucket_max; ++pos) {
|
for (tommy_size_t pos = 0; pos < hashdyn->bucket_max; ++pos) {
|
||||||
tommy_hashdyn_node* node = bucket[pos];
|
tommy_node* head = hashdyn->bucket[pos];
|
||||||
|
if (head) {
|
||||||
while (node) {
|
tommy_node* tail = head->prev;
|
||||||
tommy_node* node_next = node->next;
|
builder_tail = tommy_builder_concat(builder_tail, head, tail);
|
||||||
tommy_list_insert_tail(list, node, node->data);
|
|
||||||
node = node_next;
|
|
||||||
}
|
}
|
||||||
|
hashdyn->bucket[pos] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clear all */
|
tommy_list_concat_builder(list, &builder, builder_tail);
|
||||||
memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*));
|
|
||||||
hashdyn->count = 0;
|
hashdyn->count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares hashes in least-significant-bit-first order, with 0 before 1.
|
||||||
|
*
|
||||||
|
* The first differing bit decides the order: first precedes second when
|
||||||
|
* second has a 1 there. Equal hashes compare false.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_bool_t tommy_imp_hashdyn_before(tommy_hash_t first, tommy_hash_t second)
|
||||||
|
{
|
||||||
|
/* mark the positions where the hashes differ using XOR */
|
||||||
|
tommy_hash_t diff = first ^ second;
|
||||||
|
|
||||||
|
/* isolate the lowest differing bit using two's complement */
|
||||||
|
tommy_hash_t bit = diff & -diff;
|
||||||
|
|
||||||
|
/* a 1 in second means first has 0 there; equal hashes yield bit == 0 */
|
||||||
|
return (second & bit) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the adjacent active bucket in least-significant-bit-first order.
|
||||||
|
* Returns bucket_max when there is no adjacent bucket.
|
||||||
|
*
|
||||||
|
* Traverses the virtual binary tree of buckets in bit-reversed order.
|
||||||
|
*
|
||||||
|
* For example, with 8 buckets the forward traversal order is:
|
||||||
|
* 0, 4, 2, 6, 1, 5, 3, 7
|
||||||
|
*
|
||||||
|
* When resizing to 16 buckets:
|
||||||
|
* 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_size_t tommy_imp_hashdyn_step(tommy_hashdyn* hashdyn, tommy_size_t pos, tommy_bool_t reverse)
|
||||||
|
{
|
||||||
|
tommy_size_t bit = (hashdyn->bucket_mask + 1) / 2;
|
||||||
|
|
||||||
|
/* propagate binary carry in bit-reversed order from MSB downwards */
|
||||||
|
while (bit && ((pos & bit) != 0) != reverse) {
|
||||||
|
pos ^= bit;
|
||||||
|
bit >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* carry wrapped around the root of the tree: end of traversal */
|
||||||
|
if (!bit)
|
||||||
|
return hashdyn->bucket_max;
|
||||||
|
pos ^= bit;
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the first or last node starting at an active bucket.
|
||||||
|
*
|
||||||
|
* Scans buckets in least-significant-bit-first order until a non-empty bucket
|
||||||
|
* is found, then returns its extreme element (minimum for forward, maximum for reverse).
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_hashdyn_node* tommy_imp_hashdyn_limit(tommy_hashdyn* hashdyn, tommy_size_t pos, tommy_bool_t reverse)
|
||||||
|
{
|
||||||
|
while (pos != hashdyn->bucket_max) {
|
||||||
|
tommy_hashdyn_node* best = 0;
|
||||||
|
for (tommy_hashdyn_node* i = hashdyn->bucket[pos]; i; i = i->next) {
|
||||||
|
/* equal hashes retain insertion order, so the reverse limit selects the last */
|
||||||
|
if (reverse) {
|
||||||
|
if (!best || !tommy_imp_hashdyn_before(i->index, best->index))
|
||||||
|
best = i;
|
||||||
|
} else {
|
||||||
|
if (!best || tommy_imp_hashdyn_before(i->index, best->index))
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best)
|
||||||
|
return best;
|
||||||
|
pos = tommy_imp_hashdyn_step(hashdyn, pos, reverse);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a neighbor using the current bucket layout and a resize-independent order.
|
||||||
|
*
|
||||||
|
* First searches for the closest neighbor within the current bucket.
|
||||||
|
* If none exists, advances to the next non-empty active bucket in LSB order.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_hashdyn_node* tommy_imp_hashdyn_neighbor(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, tommy_bool_t reverse)
|
||||||
|
{
|
||||||
|
tommy_size_t pos = node->index & hashdyn->bucket_mask;
|
||||||
|
tommy_hashdyn_node* best = 0;
|
||||||
|
tommy_bool_t passed = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bucket links can change during grow and shrink. Compare full hashes instead
|
||||||
|
* of following those links as iteration order, so no remaining node moves
|
||||||
|
* behind the iterator. Equal hashes always stay in one bucket in insertion order.
|
||||||
|
*/
|
||||||
|
for (tommy_hashdyn_node* i = hashdyn->bucket[pos]; i; i = i->next) {
|
||||||
|
if (i == node) {
|
||||||
|
passed = 1;
|
||||||
|
} else if (i->index == node->index) {
|
||||||
|
/* for identical hashes, follow insertion order relative to the current node */
|
||||||
|
if (reverse && !passed)
|
||||||
|
best = i;
|
||||||
|
else if (!reverse && passed)
|
||||||
|
return i;
|
||||||
|
} else if (reverse) {
|
||||||
|
if (tommy_imp_hashdyn_before(i->index, node->index)) {
|
||||||
|
/* find the closest neighbor within the bucket in LSB order */
|
||||||
|
if (!best || !tommy_imp_hashdyn_before(i->index, best->index))
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tommy_imp_hashdyn_before(node->index, i->index)) {
|
||||||
|
/* find the closest neighbor within the bucket in LSB order */
|
||||||
|
if (!best || tommy_imp_hashdyn_before(i->index, best->index))
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best)
|
||||||
|
return best;
|
||||||
|
|
||||||
|
/* bucket exhausted: advance to the next non-empty bucket in LSB order */
|
||||||
|
return tommy_imp_hashdyn_limit(hashdyn, tommy_imp_hashdyn_step(hashdyn, pos, reverse), reverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_head(tommy_hashdyn* hashdyn)
|
||||||
|
{
|
||||||
|
/* start forward traversal at bucket 0, which has all LSB bits at zero */
|
||||||
|
return tommy_imp_hashdyn_limit(hashdyn, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_tail(tommy_hashdyn* hashdyn)
|
||||||
|
{
|
||||||
|
/* start reverse traversal at the all-ones bucket, which has the highest LSB value */
|
||||||
|
return tommy_imp_hashdyn_limit(hashdyn, hashdyn->bucket_mask, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_next(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
|
||||||
|
{
|
||||||
|
/* adjacent equal hashes are consecutive in insertion order */
|
||||||
|
if (node->next && node->next->index == node->index)
|
||||||
|
return node->next;
|
||||||
|
|
||||||
|
return tommy_imp_hashdyn_neighbor(hashdyn, node, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_prev(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node)
|
||||||
|
{
|
||||||
|
/* equal hashes follow insertion order, but the bucket head's prev points to the tail */
|
||||||
|
if (node != hashdyn->bucket[node->index & hashdyn->bucket_mask] && node->prev->index == node->index)
|
||||||
|
return node->prev;
|
||||||
|
|
||||||
|
return tommy_imp_hashdyn_neighbor(hashdyn, node, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@
|
||||||
/** \file
|
/** \file
|
||||||
* Dynamic chained hashtable.
|
* Dynamic chained hashtable.
|
||||||
*
|
*
|
||||||
* This hashtable resizes dynamically. It starts with the minimal size of 16 buckets, it doubles
|
* This hashtable resizes dynamically. It starts with the minimal size of 16 buckets.
|
||||||
* the size when it reaches a load factor greater than 0.5 and it halves the size when the load
|
* It doubles the size when it reaches a load factor greater than or equal to 0.5
|
||||||
* factor is lower than 0.125.
|
* 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
|
* All the elements are reallocated in a single resize operation done inside
|
||||||
* tommy_hashdyn_insert() or tommy_hashdyn_remove().
|
* tommy_hashdyn_insert() or tommy_hashdyn_remove().
|
||||||
*
|
*
|
||||||
* Note that the resize operation takes approximately 100 [ms] with 1 million of elements,
|
* Note that the resize operation takes approximately 100 [ms] with 1 million elements,
|
||||||
* and 1 [second] with 10 millions. This could be a problem in real-time applications.
|
* and 1 [second] with 10 million elements. This could be a problem in real-time applications.
|
||||||
*
|
*
|
||||||
* The resize also **fragments** the heap, as it involves allocating a double-sized table, **copying** elements,
|
* The resize also fragments the heap, as it involves allocating a double-sized table, copying elements,
|
||||||
* and deallocating the older table, **leaving** a big hole in the heap.
|
* and deallocating the older table, leaving a big hole in the heap.
|
||||||
*
|
*
|
||||||
* The ::tommy_hashlin hashtable fixes both problems.
|
* The ::tommy_hashlin hashtable fixes both problems.
|
||||||
*
|
*
|
||||||
|
|
@ -30,36 +30,36 @@
|
||||||
* To insert elements in the hashtable you have to call tommy_hashdyn_insert() for
|
* To insert elements in the hashtable you have to call tommy_hashdyn_insert() for
|
||||||
* each element.
|
* each element.
|
||||||
* In the insertion call you have to specify the address of the node, the
|
* 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.
|
* address of the element, and the hash value of the key to use.
|
||||||
* The address of the object is used to initialize the tommy_node::data field
|
* The address of the element 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
|
* \code
|
||||||
* struct object {
|
* struct element {
|
||||||
* int value;
|
* int value;
|
||||||
* // other fields
|
* // other fields
|
||||||
* tommy_node node;
|
* tommy_node node;
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* struct object* obj = malloc(sizeof(struct object)); // creates the object
|
* struct element* elem = malloc(sizeof(struct element)); // creates the element
|
||||||
*
|
*
|
||||||
* obj->value = ...; // initializes the object
|
* elem->value = ...; // initializes the element
|
||||||
*
|
*
|
||||||
* tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value)); // inserts the object
|
* tommy_hashdyn_insert(&hashdyn, &elem->node, elem, tommy_inthash_u32(elem->value)); // inserts the element
|
||||||
* \endcode
|
* \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.
|
* providing a comparison function, its argument, and the hash of the key to search.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* int compare(const void* arg, const void* obj)
|
* int compare(const void* arg, const void* elem)
|
||||||
* {
|
* {
|
||||||
* return *(const int*)arg != ((const struct object*)obj)->value;
|
* return *(const int*)arg != ((const struct element*)elem)->value;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* int value_to_find = 1;
|
* int value_to_find = 1;
|
||||||
* struct object* obj = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find));
|
* struct element* elem = tommy_hashdyn_search(&hashdyn, compare, &value_to_find, tommy_inthash_u32(value_to_find));
|
||||||
* if (!obj) {
|
* if (!elem) {
|
||||||
* // not found
|
* // not found
|
||||||
* } else {
|
* } else {
|
||||||
* // found
|
* // found
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* To iterate over all the elements in the hashtable with the same key, you have to
|
* To iterate over all the elements in the hashtable with the same key, you have to
|
||||||
* use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until NULL.
|
* use tommy_hashdyn_bucket() and follow the tommy_node::next pointer until 0.
|
||||||
* You have also to check explicitly for the key, as the bucket may contain
|
* You have also to check explicitly for the key, as the bucket may contain
|
||||||
* different keys.
|
* different keys.
|
||||||
*
|
*
|
||||||
|
|
@ -75,10 +75,10 @@
|
||||||
* int value_to_find = 1;
|
* int value_to_find = 1;
|
||||||
* tommy_node* i = tommy_hashdyn_bucket(&hashdyn, tommy_inthash_u32(value_to_find));
|
* tommy_node* i = tommy_hashdyn_bucket(&hashdyn, tommy_inthash_u32(value_to_find));
|
||||||
* while (i) {
|
* while (i) {
|
||||||
* struct object* obj = i->data; // gets the object pointer
|
* struct element* elem = i->data; // gets the element pointer
|
||||||
*
|
*
|
||||||
* if (obj->value == value_to_find) {
|
* if (elem->value == value_to_find) {
|
||||||
* printf("%d\n", obj->value); // process the object
|
* printf("%d\n", elem->value); // process the element
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* i = i->next; // goes to the next element
|
* i = i->next; // goes to the next element
|
||||||
|
|
@ -90,9 +90,9 @@
|
||||||
* and remove.
|
* and remove.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object* obj = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove));
|
* struct element* elem = tommy_hashdyn_remove(&hashdyn, compare, &value_to_remove, tommy_inthash_u32(value_to_remove));
|
||||||
* if (obj) {
|
* if (elem) {
|
||||||
* free(obj); // frees the object allocated memory
|
* free(elem); // frees the element allocated memory
|
||||||
* }
|
* }
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
|
|
@ -105,15 +105,60 @@
|
||||||
*
|
*
|
||||||
* If you need to iterate over all the elements in the hashtable, you can use
|
* If you need to iterate over all the elements in the hashtable, you can use
|
||||||
* tommy_hashdyn_foreach() or tommy_hashdyn_foreach_arg().
|
* tommy_hashdyn_foreach() or tommy_hashdyn_foreach_arg().
|
||||||
* If you need a more precise control with a real iteration, you have to insert
|
* For traversal that permits insertions and removals, use tommy_hashdyn_head()
|
||||||
* all the elements also in a ::tommy_list, and use the list to iterate.
|
* and tommy_hashdyn_next(), or tommy_hashdyn_tail() and tommy_hashdyn_prev().
|
||||||
* See the \ref multiindex example for more detail.
|
* Hashes are compared from the least significant bit to the most significant
|
||||||
|
* bit, with 0 before 1. Equal hashes are visited in insertion order.
|
||||||
|
* This order is independent of the number of buckets and progressive resizing.
|
||||||
|
* It differs from the bucket order used by foreach and to_list.
|
||||||
|
*
|
||||||
|
* \anchor tommy_hashdyn_iterator_validity
|
||||||
|
* Iterators are pointers to nodes currently contained in the hashtable.
|
||||||
|
* Inserting elements or removing other nodes does not invalidate them or
|
||||||
|
* change the relative iteration order of the remaining elements.
|
||||||
|
* A forward or backward traversal visits every original element that remains
|
||||||
|
* contained, even if removals cause the table to shrink or insertions grow it.
|
||||||
|
* New elements can be visited if inserted after the current position in the
|
||||||
|
* traversal direction; elements inserted before it need not be visited.
|
||||||
|
*
|
||||||
|
* Removing the pointed node invalidates its use as an iterator, even if its
|
||||||
|
* storage has not been freed. Obtain the next or previous node before removal,
|
||||||
|
* and keep that saved node contained until continuing the traversal.
|
||||||
|
*
|
||||||
|
* \code
|
||||||
|
* tommy_hashdyn_node* i = tommy_hashdyn_head(&hashdyn);
|
||||||
|
* while (i) {
|
||||||
|
* tommy_hashdyn_node* next = tommy_hashdyn_next(&hashdyn, i);
|
||||||
|
* if (should_remove(i->data)) {
|
||||||
|
* free(tommy_hashdyn_remove_existing(&hashdyn, i));
|
||||||
|
* }
|
||||||
|
* i = next;
|
||||||
|
* }
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* Changing a hash with tommy_hashdyn_rehash_existing() can change iteration
|
||||||
|
* order, so the traversal guarantee does not cover rehashing during traversal.
|
||||||
|
* After swapping containers, use the container that now contains the node.
|
||||||
|
* Clearing or deinitializing the container invalidates all its iterators.
|
||||||
|
* Concurrent traversal and modification require external synchronization.
|
||||||
|
*
|
||||||
|
* Iterator calls scan bucket contents without allocating or modifying memory.
|
||||||
|
* With a good hash function and normal dynamic resizing, a full traversal
|
||||||
|
* runs in expected O(n) time. Under the same hash distribution assumption,
|
||||||
|
* sparse tables require expected O(n + b) time, where n is the number of
|
||||||
|
* elements and b is the number of buckets. Such tables can arise after
|
||||||
|
* tommy_hashdyn_reserve(), tommy_hashdyn_clear(), or tommy_hashdyn_to_list().
|
||||||
|
* In pathological cases with severe hash collisions, traversal can take
|
||||||
|
* O(n^2 + b) time.
|
||||||
|
* Use foreach for guaranteed O(n + b) traversal when no insertions or removals
|
||||||
|
* are needed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYHASHDYN_H
|
#ifndef TOMMY_HASHDYN_H
|
||||||
#define __TOMMYHASHDYN_H
|
#define TOMMY_HASHDYN_H
|
||||||
|
|
||||||
#include "tommyhash.h"
|
#include "tommyhash.h"
|
||||||
|
#include "tommylist.h"
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* hashdyn */
|
/* hashdyn */
|
||||||
|
|
@ -126,7 +171,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hashtable node.
|
* Hashtable node.
|
||||||
* This is the node that you have to include inside your objects.
|
* This is the node that you have to include inside your elements.
|
||||||
*/
|
*/
|
||||||
typedef tommy_node tommy_hashdyn_node;
|
typedef tommy_node tommy_hashdyn_node;
|
||||||
|
|
||||||
|
|
@ -144,6 +189,7 @@ typedef struct tommy_hashdyn_struct {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the hashtable.
|
* Initializes the hashtable.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn);
|
TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn);
|
||||||
|
|
||||||
|
|
@ -152,30 +198,99 @@ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn);
|
||||||
*
|
*
|
||||||
* You can call this function with elements still contained,
|
* You can call this function with elements still contained,
|
||||||
* but such elements are not going to be freed by this call.
|
* but such elements are not going to be freed by this call.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn);
|
TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges the complete state of two initialized hashtables, including bucket allocations.
|
||||||
|
* The hashtables must not share nodes. Nodes and elements are not accessed or modified.
|
||||||
|
* Existing node pointers remain valid and belong to the other hashtable.
|
||||||
|
* Passing the same hashtable twice has no effect. Both hashtables remain usable and deinitializable.
|
||||||
|
* \param first The first hashtable.
|
||||||
|
* \param second The second hashtable.
|
||||||
|
* \note This operation is O(1) with respect to the number of elements.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_hashdyn_swap(tommy_hashdyn* first, tommy_hashdyn* second)
|
||||||
|
{
|
||||||
|
tommy_hashdyn tmp = *first;
|
||||||
|
*first = *second;
|
||||||
|
*second = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements, preserving the allocated buckets.
|
||||||
|
* Call tommy_hashdyn_shrink() after clearing to restore the initial capacity.
|
||||||
|
* The hashtable remains initialized and can be reused immediately.
|
||||||
|
* Elements 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 elements.
|
||||||
|
* Subsequent insertions and removals retain the normal resizing policy.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \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.
|
* Pre-allocates buckets for the specified number of elements.
|
||||||
*
|
*
|
||||||
* It ensures that the hashtable has enough buckets allocated to contain
|
* It ensures that the hashtable has enough buckets allocated to contain
|
||||||
* the specified number of elements without triggering a dynamic resize.
|
* the specified number of elements without triggering a dynamic resize on insertion.
|
||||||
|
* Subsequent removals retain the normal shrinking policy and may reduce
|
||||||
|
* the allocated capacity.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
* \param count Number of elements to reserve space for.
|
* \param count Number of elements to reserve space for.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count);
|
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 2, 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.
|
||||||
|
* Elements 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.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \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.
|
* Inserts an element in the hashtable.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \param node Pointer to the node embedded into the element to insert.
|
||||||
|
* \param data Pointer to the element to insert.
|
||||||
|
* \param hash Hash of the element to insert.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash);
|
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 is returned,
|
||||||
|
* and the hashtable and candidate node are left unchanged.
|
||||||
|
* Otherwise, the candidate is inserted using the normal insertion policy,
|
||||||
|
* and data is returned.
|
||||||
|
* Elements are not freed by this call.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \param node The candidate node. It must not belong to any container.
|
||||||
|
* \param data Pointer to the element 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, non-zero 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, 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.
|
* 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.
|
* You have to provide a compare function and the hash of the element you want to remove.
|
||||||
* If the element is not found, 0 is returned.
|
* If the element is not found, 0 is returned.
|
||||||
* If more equal elements are present, the first one is removed.
|
* If more equal elements are present, the first one is removed.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
|
* \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.
|
* The function should return 0 for equal elements, non-zero for different elements.
|
||||||
* \param cmp_arg Compare argument passed as first argument of the compare function.
|
* \param cmp_arg Compare argument passed as first argument of the compare function.
|
||||||
* \param hash Hash of the element to find and remove.
|
* \param hash Hash of the element to find and remove.
|
||||||
* \return The removed element, or 0 if not found.
|
* \return The removed element, or 0 if not found.
|
||||||
|
|
@ -185,8 +300,9 @@ TOMMY_API void* tommy_hashdyn_remove(tommy_hashdyn* hashdyn, tommy_search_func*
|
||||||
/**
|
/**
|
||||||
* Gets the bucket of the specified hash.
|
* Gets the bucket of the specified hash.
|
||||||
* The bucket is guaranteed to contain ALL the elements with the specified hash,
|
* The bucket is guaranteed to contain ALL the elements with the specified hash,
|
||||||
* but it can contain also others.
|
* but it can also contain others.
|
||||||
* You can access elements in the bucket following the ::next pointer until 0.
|
* You can access elements in the bucket following the tommy_node::next pointer until 0.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
* \param hash Hash of the element to find.
|
* \param hash Hash of the element to find.
|
||||||
* \return The head of the bucket, or 0 if empty.
|
* \return The head of the bucket, or 0 if empty.
|
||||||
*/
|
*/
|
||||||
|
|
@ -196,35 +312,123 @@ tommy_inline tommy_hashdyn_node* tommy_hashdyn_bucket(tommy_hashdyn* hashdyn, to
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches an element in the hashtable.
|
* Finds a node in the hashtable.
|
||||||
* You have to provide a compare function and the hash of the element you want to find.
|
* You have to provide a compare function and the hash of the element you want to find.
|
||||||
* If more equal elements are present, the first one is returned.
|
* If more equal elements are present, the first one is returned.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
* \param cmp Compare function called with cmp_arg as first argument and with the element to compare as a second one.
|
* \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.
|
* The function should return 0 for equal elements, non-zero for different elements.
|
||||||
* \param cmp_arg Compare argument passed as first argument of the compare function.
|
* \param cmp_arg Compare argument passed as first argument of the compare function.
|
||||||
* \param hash Hash of the element to find.
|
* \param hash Hash of the element to find.
|
||||||
* \return The first element found, or 0 if none.
|
* \return The first node found, or 0 if none.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
|
tommy_inline tommy_hashdyn_node* tommy_hashdyn_find(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
|
||||||
{
|
{
|
||||||
tommy_hashdyn_node* i = tommy_hashdyn_bucket(hashdyn, hash);
|
tommy_hashdyn_node* i = tommy_hashdyn_bucket(hashdyn, hash);
|
||||||
|
|
||||||
while (i) {
|
while (i) {
|
||||||
/* we first check if the hash matches, as in the same bucket we may have multiple hash values */
|
/* we first check if the hash matches, as in the same bucket we may have multiple hash values */
|
||||||
if (i->index == hash && cmp(cmp_arg, i->data) == 0)
|
if (i->index == hash && cmp(cmp_arg, i->data) == 0)
|
||||||
return i->data;
|
return i;
|
||||||
i = i->next;
|
i = i->next;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches an element in the hashtable.
|
||||||
|
* You have to provide a compare function and the hash of the element you want to find.
|
||||||
|
* If more equal elements are present, the first one is returned.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \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, non-zero for different elements.
|
||||||
|
* \param cmp_arg Compare argument passed as first argument of the compare function.
|
||||||
|
* \param hash Hash of the element to find.
|
||||||
|
* \return The first element found, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_hashdyn_search(tommy_hashdyn* hashdyn, tommy_search_func* cmp, const void* cmp_arg, tommy_hash_t hash)
|
||||||
|
{
|
||||||
|
tommy_hashdyn_node* node = tommy_hashdyn_find(hashdyn, cmp, cmp_arg, hash);
|
||||||
|
|
||||||
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an element from the hashtable.
|
* Removes an element from the hashtable.
|
||||||
* You must already have the address of the element to remove.
|
* You must already have the address of the node to remove.
|
||||||
* \return The tommy_node::data field of the node removed.
|
* \param hashdyn The hashtable.
|
||||||
|
* \param node The node to remove.
|
||||||
|
* \return The removed element.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
|
TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first node in iteration order, or 0 if empty.
|
||||||
|
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \return The first node in iteration order, or 0 if empty.
|
||||||
|
* \note This operation is expected O(1) under suitable hash distribution and
|
||||||
|
* normal dynamic resizing. In the worst case, scanning bucket contents and
|
||||||
|
* empty buckets takes O(n + b), where n is the number of elements and b is
|
||||||
|
* the number of buckets.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_head(tommy_hashdyn* hashdyn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last node in iteration order, or 0 if empty.
|
||||||
|
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \return The last node in iteration order, or 0 if empty.
|
||||||
|
* \note This operation is expected O(1) under suitable hash distribution and
|
||||||
|
* normal dynamic resizing. In the worst case, scanning bucket contents and
|
||||||
|
* empty buckets takes O(n + b), where n is the number of elements and b is
|
||||||
|
* the number of buckets.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_tail(tommy_hashdyn* hashdyn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the following node in iteration order, or 0 for the tail.
|
||||||
|
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \param node Node currently contained in the hashtable. It must not be 0.
|
||||||
|
* \return The following node in iteration order, or 0 for the tail.
|
||||||
|
* \note This operation is expected O(1) under suitable hash distribution and
|
||||||
|
* normal dynamic resizing. In the worst case, scanning bucket contents and
|
||||||
|
* empty buckets takes O(n + b), where n is the number of elements and b is
|
||||||
|
* the number of buckets.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_next(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the preceding node in iteration order, or 0 for the head.
|
||||||
|
* See \ref tommy_hashdyn_iterator_validity for ordering and iterator validity.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \param node Node currently contained in the hashtable. It must not be 0.
|
||||||
|
* \return The preceding node in iteration order, or 0 for the head.
|
||||||
|
* \note This operation is expected O(1) under suitable hash distribution and
|
||||||
|
* normal dynamic resizing. In the worst case, scanning bucket contents and
|
||||||
|
* empty buckets takes O(n + b), where n is the number of elements and b is
|
||||||
|
* the number of buckets.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_hashdyn_node* tommy_hashdyn_prev(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 element 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 hashdyn The hashtable.
|
||||||
|
* \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.
|
* Calls the specified function for each element in the hashtable.
|
||||||
*
|
*
|
||||||
|
|
@ -239,66 +443,102 @@ TOMMY_API void* tommy_hashdyn_remove_existing(tommy_hashdyn* hashdyn, tommy_hash
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // creates an object
|
* // creates an element
|
||||||
* struct object* obj = malloc(sizeof(struct object));
|
* struct element* elem = malloc(sizeof(struct element));
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // insert it in the hashtable
|
* // insert it in the hashtable
|
||||||
* tommy_hashdyn_insert(&hashdyn, &obj->node, obj, tommy_inthash_u32(obj->value));
|
* tommy_hashdyn_insert(&hashdyn, &elem->node, elem, tommy_inthash_u32(elem->value));
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // deallocates all the objects iterating the hashtable
|
* // deallocates all the elements iterating the hashtable
|
||||||
* tommy_hashdyn_foreach(&hashdyn, free);
|
* tommy_hashdyn_foreach(&hashdyn, free);
|
||||||
*
|
*
|
||||||
* // deallocates the hashtable
|
* // deallocates the hashtable
|
||||||
* tommy_hashdyn_done(&hashdyn);
|
* tommy_hashdyn_done(&hashdyn);
|
||||||
* \endcode
|
* \endcode
|
||||||
|
*
|
||||||
|
* If the callback frees the elements, the hashtable retains dangling
|
||||||
|
* node pointers. Call tommy_hashdyn_clear() before reusing it, or
|
||||||
|
* tommy_hashdyn_done() to release its storage.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \param func Function to call with each element.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* func);
|
TOMMY_API void tommy_hashdyn_foreach(tommy_hashdyn* hashdyn, tommy_foreach_func* func);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the specified function with an argument for each element in the hashtable.
|
* 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.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
* \param arg Argument to pass to the function.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg);
|
TOMMY_API void tommy_hashdyn_foreach_arg(tommy_hashdyn* hashdyn, tommy_foreach_arg_func* func, void* arg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the number of elements.
|
* Gets the number of elements.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \return The number of elements in the hashtable.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_size_t tommy_hashdyn_count(tommy_hashdyn* hashdyn)
|
tommy_inline tommy_size_t tommy_hashdyn_count(const tommy_hashdyn* hashdyn)
|
||||||
{
|
{
|
||||||
return hashdyn->count;
|
return hashdyn->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the size of allocated memory.
|
* Checks whether the hashtable is empty.
|
||||||
* It includes the size of the ::tommy_hashdyn_node of the stored elements.
|
* \param hashdyn The hashtable.
|
||||||
|
* \return Nonzero if the hashtable is empty, 0 otherwise.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn);
|
tommy_inline tommy_bool_t tommy_hashdyn_empty(const tommy_hashdyn* hashdyn)
|
||||||
|
{
|
||||||
|
return hashdyn->count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Transfers all elements from the hashtable into a tommy_list.
|
* Gets the number of buckets.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \return The number of buckets.
|
||||||
|
* \note This operation is O(1).
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_size_t tommy_hashdyn_bucket_count(const 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.
|
||||||
|
* \param hashdyn The hashtable.
|
||||||
|
* \return Total memory allocated by the hashtable, in bytes.
|
||||||
|
*/
|
||||||
|
TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(const tommy_hashdyn* hashdyn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfers all elements from the hashtable into a tommy_list.
|
||||||
*
|
*
|
||||||
* Removes every element from the \p hashdyn hashtable and inserts them
|
* Removes every element from the \p hashdyn hashtable and inserts them
|
||||||
* into the provided \p list (at the tail), preserving the per-bucket order
|
* into the provided \p list (at the tail), preserving the per-bucket order
|
||||||
* but not guaranteeing any particular global order.
|
* but not guaranteeing any particular global order.
|
||||||
*
|
*
|
||||||
* After the call:
|
* 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 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:
|
* This function is useful when you need to:
|
||||||
* - extract all elements to process/sort them outside the hash table
|
* - extract all elements to process/sort them outside the hashtable
|
||||||
* - convert the hashtable into a list for sequential iteration
|
* - convert the hashtable into a list for sequential iteration
|
||||||
* - prepare for a full clear + re-insertion with different hash/ordering
|
* - prepare for a full clear + re-insertion with different hash/ordering
|
||||||
* - move ownership of the nodes to a list-based container
|
* - 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:
|
* Typical usage pattern:
|
||||||
* \code
|
* \code
|
||||||
* tommy_list all_elements;
|
* tommy_list all_elements;
|
||||||
|
|
@ -311,8 +551,13 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn);
|
||||||
* tommy_list_sort(&all_elements, compare_by_value);
|
* tommy_list_sort(&all_elements, compare_by_value);
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* \param hashdyn The dynamic hashtable to drain
|
* \param hashdyn The hashtable to drain.
|
||||||
* \param list The list that will receive all the elements
|
* \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.
|
||||||
|
* \note This 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.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list);
|
TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,30 +7,109 @@
|
||||||
/** \internal
|
/** \internal
|
||||||
* Setup a list.
|
* Setup a list.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_set(tommy_list* list, tommy_node* head, tommy_node* tail)
|
tommy_inline void tommy_imp_list_set(tommy_list* list, tommy_node* head, tommy_node* tail)
|
||||||
{
|
{
|
||||||
head->prev = tail;
|
head->prev = tail;
|
||||||
tail->next = 0;
|
tail->next = 0;
|
||||||
*list = head;
|
*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_node* second_head = tommy_list_head(second);
|
||||||
|
|
||||||
|
if (!second_head)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tommy_node* first_head = tommy_list_head(first);
|
||||||
|
|
||||||
|
if (!first_head) {
|
||||||
|
*first = *second;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create chains from the non-empty lists */
|
||||||
|
tommy_chain first_chain;
|
||||||
|
tommy_chain second_chain;
|
||||||
|
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_imp_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_imp_list_set(list, prev, head);
|
||||||
|
}
|
||||||
|
|
||||||
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp)
|
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp)
|
||||||
{
|
{
|
||||||
tommy_chain chain;
|
|
||||||
tommy_node* head;
|
|
||||||
|
|
||||||
if (tommy_list_empty(list))
|
if (tommy_list_empty(list))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
head = tommy_list_head(list);
|
tommy_node* head = tommy_list_head(list);
|
||||||
|
|
||||||
/* create a chain from the list */
|
/* create a chain from the list */
|
||||||
|
tommy_chain chain;
|
||||||
chain.head = head;
|
chain.head = head;
|
||||||
chain.tail = head->prev;
|
chain.tail = head->prev;
|
||||||
|
|
||||||
tommy_chain_mergesort(&chain, cmp);
|
tommy_chain_mergesort(&chain, cmp);
|
||||||
|
|
||||||
/* restore the list */
|
/* restore the list */
|
||||||
tommy_list_set(list, chain.head, chain.tail);
|
tommy_imp_list_set(list, chain.head, chain.tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,21 @@
|
||||||
// Copyright (C) 2010 Andrea Mazzoleni
|
// Copyright (C) 2010 Andrea Mazzoleni
|
||||||
|
|
||||||
/** \file
|
/** \file
|
||||||
* Double linked list for collisions into hashtables.
|
* Doubly linked list for collisions into hashtables.
|
||||||
*
|
*
|
||||||
* This list is a **doubly** linked list mainly targeted for handling collisions
|
* This list is a doubly linked list mainly targeted for handling collisions
|
||||||
* into an hashtable, but **usable** also as a generic list.
|
* into a hashtable, but usable also as a generic list.
|
||||||
*
|
*
|
||||||
* The main feature of this list is to require only one pointer to represent the
|
* The main feature of this list is to require only one pointer to represent the
|
||||||
* list, compared to a classic implementation requiring a head **and** a tail pointers.
|
* list, compared to a classic implementation requiring both head and tail pointers.
|
||||||
* This reduces the memory usage in hashtables.
|
* This reduces the memory usage in hashtables.
|
||||||
*
|
*
|
||||||
* Another feature is to support the insertion at the end of the list. This **allows** to store
|
* Another feature is to support the insertion at the end of the list. This allows
|
||||||
* collisions in a stable order. Where for stable order we mean that equal elements keep
|
* storing collisions in a stable order, meaning that equal elements preserve
|
||||||
* their insertion order.
|
* their insertion order.
|
||||||
*
|
*
|
||||||
* To initialize the list, you have to call tommy_list_init(), or to simply assign
|
* To initialize the list, you have to call tommy_list_init(), or to simply assign
|
||||||
* to it NULL, as an empty list is represented by the NULL value.
|
* to it 0, as an empty list is represented by the 0 value.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* tommy_list list;
|
* tommy_list list;
|
||||||
|
|
@ -27,51 +27,113 @@
|
||||||
* To insert elements in the list you have to call tommy_list_insert_tail()
|
* To insert elements in the list you have to call tommy_list_insert_tail()
|
||||||
* or tommy_list_insert_head() for each element.
|
* or tommy_list_insert_head() for each element.
|
||||||
* In the insertion call you have to specify the address of the node and the
|
* In the insertion call you have to specify the address of the node and the
|
||||||
* address of the object.
|
* address of the element.
|
||||||
* The address of the object is used to initialize the tommy_node::data field
|
* The address of the element is used to initialize the tommy_node::data field
|
||||||
* of the node.
|
* of the node.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object {
|
* struct element {
|
||||||
* int value;
|
* int value;
|
||||||
* // other fields
|
* // other fields
|
||||||
* tommy_node node;
|
* tommy_node node;
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* struct object* obj = malloc(sizeof(struct object)); // creates the object
|
* struct element* elem = malloc(sizeof(struct element)); // creates the element
|
||||||
*
|
*
|
||||||
* obj->value = ...; // initializes the object
|
* elem->value = ...; // initializes the element
|
||||||
*
|
*
|
||||||
* tommy_list_insert_tail(&list, &obj->node, obj); // inserts the object
|
* tommy_list_insert_tail(&list, &elem->node, elem); // inserts the element
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* To iterate over all the elements in the list you have to call
|
* To iterate over all the elements in the list you have to call
|
||||||
* tommy_list_head() to get the head of the list and follow the
|
* tommy_list_head() to get the head of the list and follow the
|
||||||
* tommy_node::next pointer until NULL.
|
* tommy_node::next pointer until 0.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* tommy_node* i = tommy_list_head(&list);
|
* tommy_node* i = tommy_list_head(&list);
|
||||||
* while (i) {
|
* while (i) {
|
||||||
* struct object* obj = i->data; // gets the object pointer
|
* struct element* elem = i->data; // gets the element pointer
|
||||||
*
|
*
|
||||||
* printf("%d\n", obj->value); // process the object
|
* printf("%d\n", elem->value); // process the element
|
||||||
*
|
*
|
||||||
* i = i->next; // go to the next element
|
* i = i->next; // go to the next element
|
||||||
* }
|
* }
|
||||||
* \endcode
|
* \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 element* elem = i->data;
|
||||||
|
*
|
||||||
|
* printf("%d\n", elem->value);
|
||||||
|
*
|
||||||
|
* i = tommy_list_prev(&list, i);
|
||||||
|
* }
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* \anchor tommy_list_iterator_validity
|
||||||
|
* Iterators are pointers to nodes contained in the list.
|
||||||
|
* Inserting elements or removing other nodes between iterator calls does not
|
||||||
|
* invalidate pointers to nodes that remain in the list.
|
||||||
|
* Subsequent next/prev calls use the current contents and ordering;
|
||||||
|
* inserted elements may become visible and removed elements are skipped.
|
||||||
|
*
|
||||||
|
* Removing the pointed node invalidates its use as an iterator, even if
|
||||||
|
* its storage has not been freed. To continue traversal after removing
|
||||||
|
* the current node, obtain the next or previous node before removal.
|
||||||
|
*
|
||||||
|
* \code
|
||||||
|
* tommy_node* i = tommy_list_head(&list);
|
||||||
|
* while (i) {
|
||||||
|
* tommy_node* next = i->next; // obtain the next node before removal
|
||||||
|
*
|
||||||
|
* if (condition) {
|
||||||
|
* tommy_list_remove_existing(&list, i);
|
||||||
|
* free(i->data); // safe to deallocate
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* i = next;
|
||||||
|
* }
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* Concurrent traversal and modification require external synchronization.
|
||||||
|
*
|
||||||
|
* 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 element 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,
|
* To destroy the list you have to remove all the elements,
|
||||||
* as the list is completely inplace and it doesn't allocate memory.
|
* as the list is completely in-place and it doesn't allocate memory.
|
||||||
* This can be done with the tommy_list_foreach() function.
|
* This can be done with the tommy_list_foreach() function.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* // deallocates all the objects iterating the list
|
* // deallocates all the elements iterating the list
|
||||||
* tommy_list_foreach(&list, free);
|
* tommy_list_foreach(&list, free);
|
||||||
|
* tommy_list_clear(&list); // resets the list before reuse
|
||||||
* \endcode
|
* \endcode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYLIST_H
|
#ifndef TOMMY_LIST_H
|
||||||
#define __TOMMYLIST_H
|
#define TOMMY_LIST_H
|
||||||
|
|
||||||
#include "tommytypes.h"
|
#include "tommytypes.h"
|
||||||
|
|
||||||
|
|
@ -83,18 +145,72 @@
|
||||||
*/
|
*/
|
||||||
typedef tommy_node* tommy_list;
|
typedef tommy_node* tommy_list;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary list builder with an embedded sentinel.
|
||||||
|
* Initialize with tommy_builder_init() and finish with tommy_list_concat_builder().
|
||||||
|
* Keep the builder at the same address until finalization, as node links
|
||||||
|
* refer to its sentinel.
|
||||||
|
*/
|
||||||
|
typedef struct tommy_builder_struct {
|
||||||
|
tommy_node dummy; /**< Sentinel excluded from the resulting list. */
|
||||||
|
} tommy_builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an empty builder in O(1).
|
||||||
|
* \param builder Builder to initialize in its final location.
|
||||||
|
* \return Initial tail, pointing to the builder's sentinel.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_node* tommy_builder_init(tommy_builder* builder)
|
||||||
|
{
|
||||||
|
builder->dummy.next = 0;
|
||||||
|
return &builder->dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a non-empty chain to a builder in O(1), preserving element order.
|
||||||
|
* \param tail Current tail, initially returned by tommy_builder_init().
|
||||||
|
* \param head Head of the non-empty source chain.
|
||||||
|
* \param last Tail of the source chain. For a single node, pass head again.
|
||||||
|
* The chains must not share nodes. Inner links must be valid;
|
||||||
|
* head's prev and last's next may be undefined.
|
||||||
|
* \return Updated tail to pass to the next append or tommy_list_concat_builder().
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_node* tommy_builder_concat(tommy_node* tail, tommy_node* head, tommy_node* last)
|
||||||
|
{
|
||||||
|
head->prev = tail;
|
||||||
|
tail->next = head;
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the list.
|
* Initializes the list.
|
||||||
* The list is completely inplace, so it doesn't need to be deinitialized.
|
* The list is completely in-place, so it doesn't need to be deinitialized.
|
||||||
|
* \param list The list.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_init(tommy_list* list)
|
tommy_inline void tommy_list_init(tommy_list* list)
|
||||||
{
|
{
|
||||||
*list = 0;
|
*list = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements.
|
||||||
|
* The list remains initialized and can be reused immediately.
|
||||||
|
* Elements 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_list_foreach() has freed the elements.
|
||||||
|
* \param list The list.
|
||||||
|
* \note This operation is O(1).
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_list_clear(tommy_list* list)
|
||||||
|
{
|
||||||
|
*list = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the head of the list.
|
* Gets the head of the list.
|
||||||
* \return The head node. For empty lists 0 is returned.
|
* \param list The list.
|
||||||
|
* \return The head node, or 0 if the list is empty.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_node* tommy_list_head(tommy_list* list)
|
tommy_inline tommy_node* tommy_list_head(tommy_list* list)
|
||||||
{
|
{
|
||||||
|
|
@ -103,7 +219,9 @@ tommy_inline tommy_node* tommy_list_head(tommy_list* list)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the tail of the list.
|
* Gets the tail of the list.
|
||||||
* \return The tail node. For empty lists 0 is returned.
|
* \param list The list.
|
||||||
|
* \return The tail node, or 0 if the list is empty.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
|
tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
|
||||||
{
|
{
|
||||||
|
|
@ -115,9 +233,27 @@ tommy_inline tommy_node* tommy_list_tail(tommy_list* list)
|
||||||
return head->prev;
|
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().
|
||||||
|
* See \ref tommy_list_iterator_validity for iterator validity across insertions and removals.
|
||||||
|
* \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.
|
||||||
|
* \note This operation is O(1).
|
||||||
|
*/
|
||||||
|
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
|
/** \internal
|
||||||
* Creates a new list with a single element.
|
* Creates a new list with a single element.
|
||||||
* \param list The list to initialize.
|
* \param list The list.
|
||||||
* \param node The node to insert.
|
* \param node The node to insert.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
|
tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
|
||||||
|
|
@ -134,7 +270,7 @@ tommy_inline void tommy_list_insert_first(tommy_list* list, tommy_node* node)
|
||||||
/** \internal
|
/** \internal
|
||||||
* Inserts an element at the head of a non-empty list.
|
* Inserts an element at the head of a non-empty list.
|
||||||
* The element is inserted at the head of the list. The list cannot be empty.
|
* The element is inserted at the head of the list. The list cannot be empty.
|
||||||
* \param list The list. The list cannot be empty.
|
* \param list The list.
|
||||||
* \param node The node to insert.
|
* \param node The node to insert.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node)
|
tommy_inline void tommy_list_insert_head_not_empty(tommy_list* list, tommy_node* node)
|
||||||
|
|
@ -171,8 +307,8 @@ tommy_inline void tommy_list_insert_tail_not_empty(tommy_node* head, tommy_node*
|
||||||
/**
|
/**
|
||||||
* Inserts an element at the head of a list.
|
* Inserts an element at the head of a list.
|
||||||
* \param list The list.
|
* \param list The list.
|
||||||
* \param node The node to insert.
|
* \param node Pointer to the node embedded into the element to insert.
|
||||||
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
|
* \param data Pointer to the element to insert.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data)
|
tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, void* data)
|
||||||
{
|
{
|
||||||
|
|
@ -189,8 +325,8 @@ tommy_inline void tommy_list_insert_head(tommy_list* list, tommy_node* node, voi
|
||||||
/**
|
/**
|
||||||
* Inserts an element at the tail of a list.
|
* Inserts an element at the tail of a list.
|
||||||
* \param list The list.
|
* \param list The list.
|
||||||
* \param node The node to insert.
|
* \param node Pointer to the node embedded into the element to insert.
|
||||||
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
|
* \param data Pointer to the element to insert.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data)
|
tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, void* data)
|
||||||
{
|
{
|
||||||
|
|
@ -208,8 +344,8 @@ tommy_inline void tommy_list_insert_tail(tommy_list* list, tommy_node* node, voi
|
||||||
* Inserts an element before the specified reference node.
|
* Inserts an element before the specified reference node.
|
||||||
* \param list The list.
|
* \param list The list.
|
||||||
* \param reference The reference node. The new node will be inserted before this node. The reference node must be in the list.
|
* \param reference The reference node. The new node will be inserted before this node. The reference node must be in the list.
|
||||||
* \param node The node to insert.
|
* \param node Pointer to the node embedded into the element to insert.
|
||||||
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
|
* \param data Pointer to the element to insert.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
|
tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
|
||||||
{
|
{
|
||||||
|
|
@ -235,8 +371,8 @@ tommy_inline void tommy_list_insert_before(tommy_list* list, tommy_node* referen
|
||||||
* Inserts an element after the specified reference node.
|
* Inserts an element after the specified reference node.
|
||||||
* \param list The list.
|
* \param list The list.
|
||||||
* \param reference The reference node. The new node will be inserted after this node. The reference node must be in the list.
|
* \param reference The reference node. The new node will be inserted after this node. The reference node must be in the list.
|
||||||
* \param node The node to insert.
|
* \param node Pointer to the node embedded into the element to insert.
|
||||||
* \param data The object containing the node. It's used to set the tommy_node::data field of the node.
|
* \param data Pointer to the element to insert.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
|
tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* reference, tommy_node* node, void* data)
|
||||||
{
|
{
|
||||||
|
|
@ -260,12 +396,12 @@ tommy_inline void tommy_list_insert_after(tommy_list* list, tommy_node* referenc
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an element from the list.
|
* Removes an element from the list.
|
||||||
* You must already have the address of the element to remove.
|
* You must already have the address of the node to remove.
|
||||||
* \note The node content is left unchanged, including the tommy_node::next
|
|
||||||
* and tommy_node::prev fields that still contain pointers **in** the list.
|
|
||||||
* \param list The list.
|
* \param list The list.
|
||||||
* \param node The node to remove. The node must be in the list.
|
* \param node The node to remove.
|
||||||
* \return The tommy_node::data field of the node removed.
|
* \return The removed element.
|
||||||
|
* \note The node content is left unchanged, including the tommy_node::next
|
||||||
|
* and tommy_node::prev fields that still contain pointers in the list.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node)
|
tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node)
|
||||||
{
|
{
|
||||||
|
|
@ -287,32 +423,167 @@ tommy_inline void* tommy_list_remove_existing(tommy_list* list, tommy_node* node
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concats two lists.
|
* Removes the element at the head of the list.
|
||||||
* The second list is concatenated at the first list.
|
* \param list The list.
|
||||||
|
* \return The removed element, 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;
|
||||||
|
|
||||||
|
/* the new head must retain the tail pointer in the "circular" prev list */
|
||||||
|
if (node->next)
|
||||||
|
node->next->prev = node->prev;
|
||||||
|
|
||||||
|
*list = node->next;
|
||||||
|
|
||||||
|
return node->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the element at the tail of the list.
|
||||||
|
* \param list The list.
|
||||||
|
* \return The removed element, or 0 if the list is empty.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_list_remove_tail(tommy_list* list)
|
||||||
|
{
|
||||||
|
tommy_node* head = tommy_list_head(list);
|
||||||
|
|
||||||
|
if (!head)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
tommy_node* node = head->prev;
|
||||||
|
|
||||||
|
/* a single node is also the head, and its links must remain unchanged */
|
||||||
|
if (node == head) {
|
||||||
|
*list = 0;
|
||||||
|
} else {
|
||||||
|
head->prev = node->prev;
|
||||||
|
node->prev->next = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_not_empty(list, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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->next == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tommy_list_remove_existing(list, node);
|
||||||
|
tommy_list_insert_tail_not_empty(tommy_list_head(list), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Output list receiving node and all following nodes.
|
||||||
|
* It must be distinct from list and need not be initialized.
|
||||||
|
* Its previous value is overwritten. Any previously referenced nodes
|
||||||
|
* are left unchanged and are not deallocated.
|
||||||
|
* \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 lists must not share nodes. Nodes and elements are not accessed or modified.
|
||||||
|
* The tommy_node::data and tommy_node::index fields are left unchanged.
|
||||||
|
* Passing the same list twice has no effect.
|
||||||
|
* \param first The first list.
|
||||||
|
* \param second The second list.
|
||||||
|
* \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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates two lists.
|
||||||
|
* The second list is concatenated to the first list.
|
||||||
* \param first The first list.
|
* \param first The first list.
|
||||||
* \param second The second list. After this call the list content is undefined,
|
* \param second The second list. After this call the list content is undefined,
|
||||||
* and you should not use it anymore.
|
* and you should not use it anymore.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
|
tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
|
||||||
{
|
{
|
||||||
tommy_node* first_head;
|
|
||||||
tommy_node* first_tail;
|
|
||||||
tommy_node* second_head;
|
|
||||||
|
|
||||||
/* if the second is empty, nothing to do */
|
/* if the second is empty, nothing to do */
|
||||||
second_head = tommy_list_head(second);
|
tommy_node* second_head = tommy_list_head(second);
|
||||||
if (second_head == 0)
|
if (second_head == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* if the first is empty, copy the second */
|
/* if the first is empty, copy the second */
|
||||||
first_head = tommy_list_head(first);
|
tommy_node* first_head = tommy_list_head(first);
|
||||||
if (first_head == 0) {
|
if (first_head == 0) {
|
||||||
*first = *second;
|
*first = *second;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tail of the first list */
|
/* tail of the first list */
|
||||||
first_tail = first_head->prev;
|
tommy_node* first_tail = first_head->prev;
|
||||||
|
|
||||||
/* set the "circular" prev list */
|
/* set the "circular" prev list */
|
||||||
first_head->prev = second_head->prev;
|
first_head->prev = second_head->prev;
|
||||||
|
|
@ -322,32 +593,136 @@ tommy_inline void tommy_list_concat(tommy_list* first, tommy_list* second)
|
||||||
first_tail->next = second_head;
|
first_tail->next = second_head;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a builder's chain to a list, preserving element order and restoring list links.
|
||||||
|
* \param list Destination list, possibly empty. It must not share nodes with the builder.
|
||||||
|
* \param builder Initialized builder, possibly empty. Its contents are consumed;
|
||||||
|
* reinitialize it before reuse.
|
||||||
|
* \param tail Current tail returned by the builder helpers, ignored when empty.
|
||||||
|
* \note This operation is O(1). An empty builder has no effect.
|
||||||
|
*/
|
||||||
|
tommy_inline void tommy_list_concat_builder(tommy_list* list, const tommy_builder* builder, tommy_node* tail)
|
||||||
|
{
|
||||||
|
tommy_node* head = builder->dummy.next;
|
||||||
|
|
||||||
|
if (!head)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tommy_node* first = *list;
|
||||||
|
|
||||||
|
/* replace the sentinel link so no list node refers to the temporary builder */
|
||||||
|
if (first) {
|
||||||
|
tommy_node* first_tail = first->prev;
|
||||||
|
first_tail->next = head;
|
||||||
|
head->prev = first_tail;
|
||||||
|
first->prev = tail;
|
||||||
|
} else {
|
||||||
|
*list = head;
|
||||||
|
head->prev = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail->next = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepends a list to another list.
|
||||||
|
* The second list is prepended to 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.
|
||||||
|
* \note This operation is O(1).
|
||||||
|
*/
|
||||||
|
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 not be 0 and must be 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 not be 0 and must be 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.
|
* Sorts a list.
|
||||||
* It's a stable merge sort with O(N*log(N)) worst complexity.
|
* It's a stable merge sort with O(n log n) worst complexity.
|
||||||
* It's faster on degenerated cases like partially ordered lists.
|
* It's faster on degenerate cases like partially ordered lists.
|
||||||
|
* \param list The list.
|
||||||
* \param cmp Compare function called with two elements.
|
* \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.
|
* The function should return <0 if the first element is less than the second, ==0 if equal, and >0 if greater.
|
||||||
|
* \note This operation is O(n log n) in the worst case.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp);
|
TOMMY_API void tommy_list_sort(tommy_list* list, tommy_compare_func* cmp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if empty.
|
* Checks whether the list is empty.
|
||||||
* \return If the list is empty.
|
* \param list The list.
|
||||||
|
* \return Nonzero if the list is empty, 0 otherwise.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_bool_t tommy_list_empty(tommy_list* list)
|
tommy_inline tommy_bool_t tommy_list_empty(const tommy_list* list)
|
||||||
{
|
{
|
||||||
return tommy_list_head(list) == 0;
|
return *list == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the number of elements.
|
* Gets the number of elements.
|
||||||
|
* \param list The list.
|
||||||
|
* \return The number of elements in the list.
|
||||||
* \note This operation is O(n).
|
* \note This operation is O(n).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_size_t tommy_list_count(tommy_list* list)
|
tommy_inline tommy_size_t tommy_list_count(const tommy_list* list)
|
||||||
{
|
{
|
||||||
tommy_size_t count = 0;
|
tommy_size_t count = 0;
|
||||||
tommy_node* i = tommy_list_head(list);
|
const tommy_node* i = *list;
|
||||||
|
|
||||||
while (i) {
|
while (i) {
|
||||||
++count;
|
++count;
|
||||||
|
|
@ -371,19 +746,26 @@ tommy_inline tommy_size_t tommy_list_count(tommy_list* list)
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // creates an object
|
* // creates an element
|
||||||
* struct object* obj = malloc(sizeof(struct object));
|
* struct element* elem = malloc(sizeof(struct element));
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // insert it in the list
|
* // insert it in the list
|
||||||
* tommy_list_insert_tail(&list, &obj->node, obj);
|
* tommy_list_insert_tail(&list, &elem->node, elem);
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // deallocates all the objects iterating the list
|
* // deallocates all the elements iterating the list
|
||||||
* tommy_list_foreach(&list, free);
|
* tommy_list_foreach(&list, free);
|
||||||
|
* tommy_list_clear(&list); // resets the list before reuse
|
||||||
* \endcode
|
* \endcode
|
||||||
|
*
|
||||||
|
* If the callback frees the elements, the list retains dangling node
|
||||||
|
* pointers. Call tommy_list_clear() or tommy_list_init() before reusing it.
|
||||||
|
* The list has no storage to release.
|
||||||
|
* \param list The list.
|
||||||
|
* \param func Function to call with each element.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
|
tommy_inline void tommy_list_foreach(tommy_list* list, tommy_foreach_func* func)
|
||||||
{
|
{
|
||||||
|
|
@ -398,6 +780,12 @@ 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.
|
* 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.
|
||||||
|
* \param list The list.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
* \param arg Argument to pass to the function.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg)
|
tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_func* func, void* arg)
|
||||||
{
|
{
|
||||||
|
|
@ -411,3 +799,4 @@ tommy_inline void tommy_list_foreach_arg(tommy_list* list, tommy_foreach_arg_fun
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
#define TOMMY_TREE_BALANCE_LEFT ((tommy_size_t)1)
|
#define TOMMY_TREE_BALANCE_LEFT ((tommy_size_t)1)
|
||||||
#define TOMMY_TREE_BALANCE_RIGHT ((tommy_size_t)2)
|
#define TOMMY_TREE_BALANCE_RIGHT ((tommy_size_t)2)
|
||||||
|
|
||||||
tommy_inline int tommy_tree_balance_get(tommy_tree_node* node)
|
tommy_inline int tommy_imp_tree_balance_get(tommy_tree_node* node)
|
||||||
{
|
{
|
||||||
tommy_size_t balance = node->index & TOMMY_TREE_BALANCE_MASK;
|
tommy_size_t balance = node->index & TOMMY_TREE_BALANCE_MASK;
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ tommy_inline int tommy_tree_balance_get(tommy_tree_node* node)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline void tommy_tree_balance_set(tommy_tree_node* node, int balance)
|
tommy_inline void tommy_imp_tree_balance_set(tommy_tree_node* node, int balance)
|
||||||
{
|
{
|
||||||
tommy_size_t value = 0;
|
tommy_size_t value = 0;
|
||||||
|
|
||||||
|
|
@ -37,12 +37,12 @@ tommy_inline void tommy_tree_balance_set(tommy_tree_node* node, int balance)
|
||||||
node->index = (node->index & ~TOMMY_TREE_BALANCE_MASK) | value;
|
node->index = (node->index & ~TOMMY_TREE_BALANCE_MASK) | value;
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline void tommy_tree_parent_set(tommy_tree_node* node, tommy_tree_node* parent)
|
tommy_inline void tommy_imp_tree_parent_set(tommy_tree_node* node, tommy_tree_node* parent)
|
||||||
{
|
{
|
||||||
node->index = (tommy_size_t)(tommy_uintptr_t)parent | (node->index & TOMMY_TREE_BALANCE_MASK);
|
node->index = (tommy_size_t)(tommy_uintptr_t)parent | (node->index & TOMMY_TREE_BALANCE_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline void tommy_tree_replace(tommy_tree* tree, tommy_tree_node* root, tommy_tree_node* node)
|
tommy_inline void tommy_imp_tree_replace(tommy_tree* tree, tommy_tree_node* root, tommy_tree_node* node)
|
||||||
{
|
{
|
||||||
tommy_tree_node* parent = tommy_tree_parent(root);
|
tommy_tree_node* parent = tommy_tree_parent(root);
|
||||||
|
|
||||||
|
|
@ -55,45 +55,45 @@ tommy_inline void tommy_tree_replace(tommy_tree* tree, tommy_tree_node* root, to
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node)
|
if (node)
|
||||||
tommy_tree_parent_set(node, parent);
|
tommy_imp_tree_parent_set(node, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline tommy_tree_node* tommy_tree_rotate_left(tommy_tree* tree, tommy_tree_node* root)
|
tommy_inline tommy_tree_node* tommy_imp_tree_rotate_left(tommy_tree* tree, tommy_tree_node* root)
|
||||||
{
|
{
|
||||||
tommy_tree_node* next = root->next;
|
tommy_tree_node* next = root->next;
|
||||||
|
|
||||||
tommy_tree_replace(tree, root, next);
|
tommy_imp_tree_replace(tree, root, next);
|
||||||
root->next = next->prev;
|
root->next = next->prev;
|
||||||
if (root->next)
|
if (root->next)
|
||||||
tommy_tree_parent_set(root->next, root);
|
tommy_imp_tree_parent_set(root->next, root);
|
||||||
|
|
||||||
next->prev = root;
|
next->prev = root;
|
||||||
tommy_tree_parent_set(root, next);
|
tommy_imp_tree_parent_set(root, next);
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline tommy_tree_node* tommy_tree_rotate_right(tommy_tree* tree, tommy_tree_node* root)
|
tommy_inline tommy_tree_node* tommy_imp_tree_rotate_right(tommy_tree* tree, tommy_tree_node* root)
|
||||||
{
|
{
|
||||||
tommy_tree_node* prev = root->prev;
|
tommy_tree_node* prev = root->prev;
|
||||||
|
|
||||||
tommy_tree_replace(tree, root, prev);
|
tommy_imp_tree_replace(tree, root, prev);
|
||||||
root->prev = prev->next;
|
root->prev = prev->next;
|
||||||
if (root->prev)
|
if (root->prev)
|
||||||
tommy_tree_parent_set(root->prev, root);
|
tommy_imp_tree_parent_set(root->prev, root);
|
||||||
|
|
||||||
prev->next = root;
|
prev->next = root;
|
||||||
tommy_tree_parent_set(root, prev);
|
tommy_imp_tree_parent_set(root, prev);
|
||||||
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* node)
|
tommy_inline void tommy_imp_tree_insert_balance(tommy_tree* tree, tommy_tree_node* node)
|
||||||
{
|
{
|
||||||
tommy_tree_node* parent = tommy_tree_parent(node);
|
tommy_tree_node* parent = tommy_tree_parent(node);
|
||||||
|
|
||||||
while (parent) {
|
while (parent) {
|
||||||
int balance = tommy_tree_balance_get(parent);
|
int balance = tommy_imp_tree_balance_get(parent);
|
||||||
|
|
||||||
if (node == parent->prev)
|
if (node == parent->prev)
|
||||||
--balance;
|
--balance;
|
||||||
|
|
@ -102,12 +102,12 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n
|
||||||
}
|
}
|
||||||
|
|
||||||
if (balance == 0) {
|
if (balance == 0) {
|
||||||
tommy_tree_balance_set(parent, balance);
|
tommy_imp_tree_balance_set(parent, balance);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (balance >= -1 && balance <= 1) {
|
if (balance >= -1 && balance <= 1) {
|
||||||
tommy_tree_balance_set(parent, balance);
|
tommy_imp_tree_balance_set(parent, balance);
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = tommy_tree_parent(parent);
|
parent = tommy_tree_parent(parent);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -115,39 +115,39 @@ tommy_inline void tommy_tree_insert_balance(tommy_tree* tree, tommy_tree_node* n
|
||||||
|
|
||||||
if (balance == -2) {
|
if (balance == -2) {
|
||||||
tommy_tree_node* left = parent->prev;
|
tommy_tree_node* left = parent->prev;
|
||||||
int left_balance = tommy_tree_balance_get(left);
|
int left_balance = tommy_imp_tree_balance_get(left);
|
||||||
|
|
||||||
if (left_balance < 0) {
|
if (left_balance < 0) {
|
||||||
tommy_tree_rotate_right(tree, parent);
|
tommy_imp_tree_rotate_right(tree, parent);
|
||||||
tommy_tree_balance_set(parent, 0);
|
tommy_imp_tree_balance_set(parent, 0);
|
||||||
tommy_tree_balance_set(left, 0);
|
tommy_imp_tree_balance_set(left, 0);
|
||||||
} else {
|
} else {
|
||||||
tommy_tree_node* middle = left->next;
|
tommy_tree_node* middle = left->next;
|
||||||
int middle_balance = tommy_tree_balance_get(middle);
|
int middle_balance = tommy_imp_tree_balance_get(middle);
|
||||||
|
|
||||||
tommy_tree_rotate_left(tree, left);
|
tommy_imp_tree_rotate_left(tree, left);
|
||||||
tommy_tree_rotate_right(tree, parent);
|
tommy_imp_tree_rotate_right(tree, parent);
|
||||||
tommy_tree_balance_set(parent, middle_balance < 0 ? 1 : 0);
|
tommy_imp_tree_balance_set(parent, middle_balance < 0 ? 1 : 0);
|
||||||
tommy_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
|
tommy_imp_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
|
||||||
tommy_tree_balance_set(middle, 0);
|
tommy_imp_tree_balance_set(middle, 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tommy_tree_node* right = parent->next;
|
tommy_tree_node* right = parent->next;
|
||||||
int right_balance = tommy_tree_balance_get(right);
|
int right_balance = tommy_imp_tree_balance_get(right);
|
||||||
|
|
||||||
if (right_balance > 0) {
|
if (right_balance > 0) {
|
||||||
tommy_tree_rotate_left(tree, parent);
|
tommy_imp_tree_rotate_left(tree, parent);
|
||||||
tommy_tree_balance_set(parent, 0);
|
tommy_imp_tree_balance_set(parent, 0);
|
||||||
tommy_tree_balance_set(right, 0);
|
tommy_imp_tree_balance_set(right, 0);
|
||||||
} else {
|
} else {
|
||||||
tommy_tree_node* middle = right->prev;
|
tommy_tree_node* middle = right->prev;
|
||||||
int middle_balance = tommy_tree_balance_get(middle);
|
int middle_balance = tommy_imp_tree_balance_get(middle);
|
||||||
|
|
||||||
tommy_tree_rotate_right(tree, right);
|
tommy_imp_tree_rotate_right(tree, right);
|
||||||
tommy_tree_rotate_left(tree, parent);
|
tommy_imp_tree_rotate_left(tree, parent);
|
||||||
tommy_tree_balance_set(parent, middle_balance > 0 ? -1 : 0);
|
tommy_imp_tree_balance_set(parent, middle_balance > 0 ? -1 : 0);
|
||||||
tommy_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
|
tommy_imp_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
|
||||||
tommy_tree_balance_set(middle, 0);
|
tommy_imp_tree_balance_set(middle, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,41 +155,59 @@ 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_imp_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data, tommy_bool_t unique)
|
||||||
{
|
{
|
||||||
tommy_tree_node* parent = 0;
|
tommy_tree_node* parent = 0;
|
||||||
|
tommy_tree_node* existing = 0;
|
||||||
tommy_tree_node** link = &tree->root;
|
tommy_tree_node** link = &tree->root;
|
||||||
|
/* cache the comparator to avoid reloading it after each comparison */
|
||||||
|
tommy_compare_func* cmp_func = tree->cmp;
|
||||||
|
|
||||||
|
while (*link) {
|
||||||
|
/* comparators must not change the tree, so keep the node across the call */
|
||||||
|
parent = *link;
|
||||||
|
int cmp = cmp_func(data, parent->data);
|
||||||
|
|
||||||
|
if (cmp < 0)
|
||||||
|
link = &parent->prev;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
return existing->data;
|
||||||
|
|
||||||
node->data = data;
|
node->data = data;
|
||||||
node->prev = 0;
|
node->prev = 0;
|
||||||
node->next = 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)
|
|
||||||
link = &parent->next;
|
|
||||||
else
|
|
||||||
return parent->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
node->index = (tommy_size_t)(tommy_uintptr_t)parent;
|
node->index = (tommy_size_t)(tommy_uintptr_t)parent;
|
||||||
*link = node;
|
*link = node;
|
||||||
++tree->count;
|
++tree->count;
|
||||||
|
|
||||||
tommy_tree_insert_balance(tree, node);
|
tommy_imp_tree_insert_balance(tree, node);
|
||||||
|
|
||||||
return node->data;
|
return node->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk)
|
TOMMY_API void tommy_tree_insert(tommy_tree* tree, tommy_tree_node* node, void* data)
|
||||||
|
{
|
||||||
|
tommy_imp_tree_insert(tree, node, data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOMMY_API void* tommy_tree_insert_unique(tommy_tree* tree, tommy_tree_node* node, void* data)
|
||||||
|
{
|
||||||
|
return tommy_imp_tree_insert(tree, node, data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
tommy_inline void tommy_imp_tree_remove_balance(tommy_tree* tree, tommy_tree_node* node, tommy_bool_t left_shrunk)
|
||||||
{
|
{
|
||||||
while (node) {
|
while (node) {
|
||||||
int balance = tommy_tree_balance_get(node);
|
int balance = tommy_imp_tree_balance_get(node);
|
||||||
|
|
||||||
if (left_shrunk)
|
if (left_shrunk)
|
||||||
++balance;
|
++balance;
|
||||||
|
|
@ -197,15 +215,13 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n
|
||||||
--balance;
|
--balance;
|
||||||
|
|
||||||
if (balance == -1 || balance == 1) {
|
if (balance == -1 || balance == 1) {
|
||||||
tommy_tree_balance_set(node, balance);
|
tommy_imp_tree_balance_set(node, balance);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (balance == 0) {
|
if (balance == 0) {
|
||||||
tommy_tree_node* parent;
|
tommy_imp_tree_balance_set(node, balance);
|
||||||
|
tommy_tree_node* parent = tommy_tree_parent(node);
|
||||||
tommy_tree_balance_set(node, balance);
|
|
||||||
parent = tommy_tree_parent(node);
|
|
||||||
if (!parent)
|
if (!parent)
|
||||||
return;
|
return;
|
||||||
left_shrunk = node == parent->prev;
|
left_shrunk = node == parent->prev;
|
||||||
|
|
@ -215,27 +231,27 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n
|
||||||
|
|
||||||
if (balance == -2) {
|
if (balance == -2) {
|
||||||
tommy_tree_node* left = node->prev;
|
tommy_tree_node* left = node->prev;
|
||||||
int left_balance = tommy_tree_balance_get(left);
|
int left_balance = tommy_imp_tree_balance_get(left);
|
||||||
tommy_tree_node* root;
|
tommy_tree_node* root;
|
||||||
|
|
||||||
if (left_balance <= 0) {
|
if (left_balance <= 0) {
|
||||||
root = tommy_tree_rotate_right(tree, node);
|
root = tommy_imp_tree_rotate_right(tree, node);
|
||||||
if (left_balance == 0) {
|
if (left_balance == 0) {
|
||||||
tommy_tree_balance_set(node, -1);
|
tommy_imp_tree_balance_set(node, -1);
|
||||||
tommy_tree_balance_set(left, 1);
|
tommy_imp_tree_balance_set(left, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tommy_tree_balance_set(node, 0);
|
tommy_imp_tree_balance_set(node, 0);
|
||||||
tommy_tree_balance_set(left, 0);
|
tommy_imp_tree_balance_set(left, 0);
|
||||||
} else {
|
} else {
|
||||||
tommy_tree_node* middle = left->next;
|
tommy_tree_node* middle = left->next;
|
||||||
int middle_balance = tommy_tree_balance_get(middle);
|
int middle_balance = tommy_imp_tree_balance_get(middle);
|
||||||
|
|
||||||
tommy_tree_rotate_left(tree, left);
|
tommy_imp_tree_rotate_left(tree, left);
|
||||||
root = tommy_tree_rotate_right(tree, node);
|
root = tommy_imp_tree_rotate_right(tree, node);
|
||||||
tommy_tree_balance_set(node, middle_balance < 0 ? 1 : 0);
|
tommy_imp_tree_balance_set(node, middle_balance < 0 ? 1 : 0);
|
||||||
tommy_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
|
tommy_imp_tree_balance_set(left, middle_balance > 0 ? -1 : 0);
|
||||||
tommy_tree_balance_set(middle, 0);
|
tommy_imp_tree_balance_set(middle, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
node = tommy_tree_parent(root);
|
node = tommy_tree_parent(root);
|
||||||
|
|
@ -247,27 +263,27 @@ tommy_inline void tommy_tree_remove_balance(tommy_tree* tree, tommy_tree_node* n
|
||||||
|
|
||||||
{
|
{
|
||||||
tommy_tree_node* right = node->next;
|
tommy_tree_node* right = node->next;
|
||||||
int right_balance = tommy_tree_balance_get(right);
|
int right_balance = tommy_imp_tree_balance_get(right);
|
||||||
tommy_tree_node* root;
|
tommy_tree_node* root;
|
||||||
|
|
||||||
if (right_balance >= 0) {
|
if (right_balance >= 0) {
|
||||||
root = tommy_tree_rotate_left(tree, node);
|
root = tommy_imp_tree_rotate_left(tree, node);
|
||||||
if (right_balance == 0) {
|
if (right_balance == 0) {
|
||||||
tommy_tree_balance_set(node, 1);
|
tommy_imp_tree_balance_set(node, 1);
|
||||||
tommy_tree_balance_set(right, -1);
|
tommy_imp_tree_balance_set(right, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tommy_tree_balance_set(node, 0);
|
tommy_imp_tree_balance_set(node, 0);
|
||||||
tommy_tree_balance_set(right, 0);
|
tommy_imp_tree_balance_set(right, 0);
|
||||||
} else {
|
} else {
|
||||||
tommy_tree_node* middle = right->prev;
|
tommy_tree_node* middle = right->prev;
|
||||||
int middle_balance = tommy_tree_balance_get(middle);
|
int middle_balance = tommy_imp_tree_balance_get(middle);
|
||||||
|
|
||||||
tommy_tree_rotate_right(tree, right);
|
tommy_imp_tree_rotate_right(tree, right);
|
||||||
root = tommy_tree_rotate_left(tree, node);
|
root = tommy_imp_tree_rotate_left(tree, node);
|
||||||
tommy_tree_balance_set(node, middle_balance > 0 ? -1 : 0);
|
tommy_imp_tree_balance_set(node, middle_balance > 0 ? -1 : 0);
|
||||||
tommy_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
|
tommy_imp_tree_balance_set(right, middle_balance < 0 ? 1 : 0);
|
||||||
tommy_tree_balance_set(middle, 0);
|
tommy_imp_tree_balance_set(middle, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
node = tommy_tree_parent(root);
|
node = tommy_tree_parent(root);
|
||||||
|
|
@ -289,7 +305,7 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no
|
||||||
|
|
||||||
parent = tommy_tree_parent(node);
|
parent = tommy_tree_parent(node);
|
||||||
left_shrunk = parent && node == parent->prev;
|
left_shrunk = parent && node == parent->prev;
|
||||||
tommy_tree_replace(tree, node, child);
|
tommy_imp_tree_replace(tree, node, child);
|
||||||
} else {
|
} else {
|
||||||
tommy_tree_node* next = node->next;
|
tommy_tree_node* next = node->next;
|
||||||
|
|
||||||
|
|
@ -298,67 +314,106 @@ TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* no
|
||||||
|
|
||||||
parent = tommy_tree_parent(next);
|
parent = tommy_tree_parent(next);
|
||||||
if (parent == node) {
|
if (parent == node) {
|
||||||
tommy_tree_replace(tree, node, next);
|
tommy_imp_tree_replace(tree, node, next);
|
||||||
next->prev = node->prev;
|
next->prev = node->prev;
|
||||||
tommy_tree_parent_set(next->prev, next);
|
tommy_imp_tree_parent_set(next->prev, next);
|
||||||
tommy_tree_balance_set(next, tommy_tree_balance_get(node));
|
tommy_imp_tree_balance_set(next, tommy_imp_tree_balance_get(node));
|
||||||
parent = next;
|
parent = next;
|
||||||
left_shrunk = 0;
|
left_shrunk = 0;
|
||||||
} else {
|
} else {
|
||||||
parent->prev = next->next;
|
parent->prev = next->next;
|
||||||
if (parent->prev)
|
if (parent->prev)
|
||||||
tommy_tree_parent_set(parent->prev, parent);
|
tommy_imp_tree_parent_set(parent->prev, parent);
|
||||||
|
|
||||||
tommy_tree_replace(tree, node, next);
|
tommy_imp_tree_replace(tree, node, next);
|
||||||
next->prev = node->prev;
|
next->prev = node->prev;
|
||||||
next->next = node->next;
|
next->next = node->next;
|
||||||
tommy_tree_parent_set(next->prev, next);
|
tommy_imp_tree_parent_set(next->prev, next);
|
||||||
tommy_tree_parent_set(next->next, next);
|
tommy_imp_tree_parent_set(next->next, next);
|
||||||
tommy_tree_balance_set(next, tommy_tree_balance_get(node));
|
tommy_imp_tree_balance_set(next, tommy_imp_tree_balance_get(node));
|
||||||
left_shrunk = 1;
|
left_shrunk = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
--tree->count;
|
--tree->count;
|
||||||
if (parent)
|
if (parent)
|
||||||
tommy_tree_remove_balance(tree, parent, left_shrunk);
|
tommy_imp_tree_remove_balance(tree, parent, left_shrunk);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API void tommy_tree_foreach_node(tommy_tree_node* root, tommy_foreach_func* func)
|
TOMMY_API void tommy_tree_to_list(tommy_tree* tree, tommy_list* list)
|
||||||
{
|
{
|
||||||
while (root) {
|
/* AVL height is less than twice the bit width of its node count */
|
||||||
tommy_tree_node* next;
|
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
|
||||||
|
tommy_size_t depth = 0;
|
||||||
|
tommy_tree_node* node = tree->root;
|
||||||
|
tommy_builder builder;
|
||||||
|
tommy_node* builder_tail = tommy_builder_init(&builder);
|
||||||
|
|
||||||
tommy_tree_foreach_node(root->prev, func);
|
while (node || depth) {
|
||||||
|
while (node) {
|
||||||
|
stack[depth] = node;
|
||||||
|
++depth;
|
||||||
|
node = node->prev;
|
||||||
|
}
|
||||||
|
|
||||||
/* make a copy in case func is free() */
|
node = stack[--depth];
|
||||||
next = root->next;
|
/* chain appending overwrites child links, so keep the right subtree reachable */
|
||||||
|
tommy_tree_node* next = node->next;
|
||||||
|
builder_tail = tommy_builder_concat(builder_tail, node, node);
|
||||||
|
node = next;
|
||||||
|
}
|
||||||
|
|
||||||
func(root->data);
|
tommy_list_concat_builder(list, &builder, builder_tail);
|
||||||
|
tommy_tree_clear(tree);
|
||||||
|
}
|
||||||
|
|
||||||
root = next;
|
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) {
|
||||||
|
while (node) {
|
||||||
|
stack[depth] = node;
|
||||||
|
++depth;
|
||||||
|
node = node->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = stack[--depth];
|
||||||
|
/* save the next subtree before the callback can free this node */
|
||||||
|
tommy_tree_node* next = node->next;
|
||||||
|
func(node->data);
|
||||||
|
node = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API void tommy_tree_foreach_arg_node(tommy_tree_node* root, tommy_foreach_arg_func* func, void* arg)
|
TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
|
||||||
{
|
{
|
||||||
while (root) {
|
/* AVL height is less than twice the bit width of its node count */
|
||||||
tommy_tree_node* next;
|
tommy_tree_node* stack[2 * TOMMY_SIZE_BIT];
|
||||||
|
tommy_size_t depth = 0;
|
||||||
|
tommy_tree_node* node = tree->root;
|
||||||
|
|
||||||
tommy_tree_foreach_arg_node(root->prev, func, arg);
|
while (node || depth) {
|
||||||
|
while (node) {
|
||||||
|
stack[depth] = node;
|
||||||
|
++depth;
|
||||||
|
node = node->prev;
|
||||||
|
}
|
||||||
|
|
||||||
/* make a copy in case func is free() */
|
node = stack[--depth];
|
||||||
next = root->next;
|
/* save the next subtree before the callback can free this node */
|
||||||
|
tommy_tree_node* next = node->next;
|
||||||
func(arg, root->data);
|
func(arg, node->data);
|
||||||
|
node = next;
|
||||||
root = next;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TOMMY_API tommy_size_t tommy_tree_memory_usage(tommy_tree* tree)
|
TOMMY_API tommy_size_t tommy_tree_memory_usage(const tommy_tree* tree)
|
||||||
{
|
{
|
||||||
return tommy_tree_count(tree) * sizeof(tommy_tree_node);
|
return tommy_tree_count(tree) * sizeof(tommy_tree_node);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
* This tree is a standard AVL tree implementation that stores elements in the
|
* This tree is a standard AVL tree implementation that stores elements in the
|
||||||
* order defined by the comparison function.
|
* 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
|
* To initialize a tree you have to call tommy_tree_init() specifying a comparison
|
||||||
* function that will define the order in the tree.
|
* function that will define the order in the tree.
|
||||||
|
|
@ -21,31 +22,31 @@
|
||||||
* To insert elements in the tree you have to call tommy_tree_insert() for
|
* To insert elements in the tree you have to call tommy_tree_insert() for
|
||||||
* each element.
|
* each element.
|
||||||
* In the insertion call you have to specify the address of the node and the
|
* In the insertion call you have to specify the address of the node and the
|
||||||
* address of the object.
|
* address of the element.
|
||||||
* The address of the object is used to initialize the tommy_node::data field
|
* The address of the element is used to initialize the tommy_node::data field
|
||||||
* of the node.
|
* of the node.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object {
|
* struct element {
|
||||||
* int value;
|
* int value;
|
||||||
* // other fields
|
* // other fields
|
||||||
* tommy_tree_node node;
|
* tommy_tree_node node;
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* struct object* obj = malloc(sizeof(struct object)); // creates the object
|
* struct element* elem = malloc(sizeof(struct element)); // creates the element
|
||||||
*
|
*
|
||||||
* obj->value = ...; // initializes the object
|
* elem->value = ...; // initializes the element
|
||||||
*
|
*
|
||||||
* tommy_tree_insert(&tree, &obj->node, obj); // inserts the object
|
* tommy_tree_insert(&tree, &elem->node, elem); // inserts the element
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* To find an element in the tree you have to call tommy_tree_search() providing
|
* To find the first element with a given key in tree order, call
|
||||||
* the key to search.
|
* tommy_tree_search() providing the key to search.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object value_to_find = { 1 };
|
* struct element value_to_find = { 1 };
|
||||||
* struct object* obj = tommy_tree_search(&tree, &value_to_find);
|
* struct element* elem = tommy_tree_search(&tree, &value_to_find);
|
||||||
* if (!obj) {
|
* if (!elem) {
|
||||||
* // not found
|
* // not found
|
||||||
* } else {
|
* } else {
|
||||||
* // found
|
* // found
|
||||||
|
|
@ -59,22 +60,36 @@
|
||||||
* \code
|
* \code
|
||||||
* tommy_tree_node* i = tommy_tree_head(&tree);
|
* tommy_tree_node* i = tommy_tree_head(&tree);
|
||||||
* while (i) {
|
* while (i) {
|
||||||
* struct object* obj = i->data; // gets the object pointer
|
* struct element* elem = i->data; // gets the element pointer
|
||||||
*
|
*
|
||||||
* printf("%d\n", obj->value); // process the object
|
* printf("%d\n", elem->value); // process the element
|
||||||
*
|
*
|
||||||
* i = tommy_tree_next(i); // go to the next element
|
* i = tommy_tree_next(i); // go to the next element
|
||||||
* }
|
* }
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* To remove an element from the tree you have to call tommy_tree_remove()
|
* \anchor tommy_tree_iterator_validity
|
||||||
* providing the key to search and remove.
|
* Iterators are pointers to nodes contained in the tree.
|
||||||
|
* Inserting elements or removing other nodes between iterator calls does not
|
||||||
|
* invalidate pointers to nodes that remain in the tree.
|
||||||
|
* Subsequent next/prev calls use the current contents and ordering;
|
||||||
|
* inserted elements may become visible and removed elements are skipped.
|
||||||
|
*
|
||||||
|
* Removing the pointed node invalidates its use as an iterator, even if
|
||||||
|
* its storage has not been freed. To continue traversal after removing
|
||||||
|
* the current node, obtain the next or previous node before removal.
|
||||||
|
*
|
||||||
|
* Keys and fields used for ordering must not be modified while contained.
|
||||||
|
* Concurrent traversal and modification require external synchronization.
|
||||||
|
*
|
||||||
|
* To remove the first element with a given key in tree order, call
|
||||||
|
* tommy_tree_remove() providing the key to search and remove.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object value_to_remove = { 1 };
|
* struct element value_to_remove = { 1 };
|
||||||
* struct object* obj = tommy_tree_remove(&tree, &value_to_remove);
|
* struct element* elem = tommy_tree_remove(&tree, &value_to_remove);
|
||||||
* if (obj) {
|
* if (elem) {
|
||||||
* free(obj); // frees the object allocated memory
|
* free(elem); // frees the element allocated memory
|
||||||
* }
|
* }
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
|
|
@ -82,18 +97,18 @@
|
||||||
* address using tommy_tree_remove_existing().
|
* address using tommy_tree_remove_existing().
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object* obj = ...;
|
* struct element* elem = ...;
|
||||||
* tommy_tree_remove_existing(&tree, &obj->node);
|
* tommy_tree_remove_existing(&tree, &elem->node);
|
||||||
* free(obj);
|
* free(elem);
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
* To remove the smallest or greatest element you can use tommy_tree_remove_head()
|
* To remove the smallest or greatest element you can use tommy_tree_remove_head()
|
||||||
* or tommy_tree_remove_tail().
|
* or tommy_tree_remove_tail().
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object* obj = tommy_tree_remove_head(&tree);
|
* struct element* elem = tommy_tree_remove_head(&tree);
|
||||||
* if (obj) {
|
* if (elem) {
|
||||||
* free(obj);
|
* free(elem);
|
||||||
* }
|
* }
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
|
|
@ -102,22 +117,23 @@
|
||||||
* This can be done with the tommy_tree_foreach() function.
|
* This can be done with the tommy_tree_foreach() function.
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* // deallocates all the objects iterating the tree
|
* // deallocates all the elements iterating the tree
|
||||||
* tommy_tree_foreach(&tree, free);
|
* tommy_tree_foreach(&tree, free);
|
||||||
* \endcode
|
* \endcode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYTREE_H
|
#ifndef TOMMY_TREE_H
|
||||||
#define __TOMMYTREE_H
|
#define TOMMY_TREE_H
|
||||||
|
|
||||||
#include "tommytypes.h"
|
#include "tommytypes.h"
|
||||||
|
#include "tommylist.h"
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* tree */
|
/* tree */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tree node.
|
* Tree node.
|
||||||
* This is the node that you have to include inside your objects.
|
* This is the node that you have to include inside your elements.
|
||||||
* The node must preserve its natural alignment because the tree stores the
|
* The node must preserve its natural alignment because the tree stores the
|
||||||
* balance factor in the two least significant bits of the parent pointer.
|
* balance factor in the two least significant bits of the parent pointer.
|
||||||
*/
|
*/
|
||||||
|
|
@ -135,6 +151,7 @@ typedef struct tommy_tree_struct {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the tree.
|
* Initializes the tree.
|
||||||
|
* \param tree The tree.
|
||||||
* \param cmp The comparison function that defines the order in the tree.
|
* \param cmp The comparison function that defines the order in the tree.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
|
tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
|
||||||
|
|
@ -145,40 +162,81 @@ tommy_inline void tommy_tree_init(tommy_tree* tree, tommy_compare_func* cmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts an element in the tree.
|
* Exchanges the contents of two initialized trees, including their comparison functions.
|
||||||
* If the element is already present, it's not inserted again.
|
* Each comparison function moves with its root to preserve the tree order.
|
||||||
* Check the return value to identify if the element was already present or not.
|
* The trees must not share nodes. Nodes are not accessed or modified.
|
||||||
* You have to provide the pointer of the node embedded into the object and
|
* Passing the same tree twice has no effect.
|
||||||
* the pointer to the object.
|
* \param first The first tree.
|
||||||
* \param node Pointer to the node embedded into the object to insert.
|
* \param second The second tree.
|
||||||
* \param data Pointer to the object to insert.
|
* \note This operation is O(1).
|
||||||
* \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_inline void tommy_tree_swap(tommy_tree* first, tommy_tree* second)
|
||||||
|
{
|
||||||
|
tommy_tree tmp = *first;
|
||||||
|
*first = *second;
|
||||||
|
*second = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all elements, preserving the comparison function.
|
||||||
|
* The tree remains initialized and can be reused immediately.
|
||||||
|
* Elements 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 elements.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \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 element and
|
||||||
|
* the pointer to the element.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param node Pointer to the node embedded into the element to insert.
|
||||||
|
* \param data Pointer to the element to insert.
|
||||||
|
*/
|
||||||
|
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 data is returned.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param node The candidate node. It must not belong to any container.
|
||||||
|
* \param data Pointer to the element 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.
|
||||||
|
* Elements 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.
|
* Removes an element from the tree.
|
||||||
* You must already have the address of the element to remove.
|
* You must already have the address of the node to remove.
|
||||||
* \return The tommy_node::data field of the node removed.
|
* \param tree The tree.
|
||||||
|
* \param node The node to remove.
|
||||||
|
* \return The removed element.
|
||||||
*/
|
*/
|
||||||
TOMMY_API void* tommy_tree_remove_existing(tommy_tree* tree, tommy_tree_node* node);
|
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.
|
* Gets the parent of the specified node.
|
||||||
* \param node Node contained in the tree.
|
* \param node Node contained in the tree.
|
||||||
|
|
@ -191,13 +249,13 @@ tommy_inline tommy_tree_node* tommy_tree_parent(tommy_tree_node* node)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the node following the specified one in tree order.
|
* Gets the node following the specified one in tree order.
|
||||||
|
* See \ref tommy_tree_iterator_validity for iterator validity across insertions and removals.
|
||||||
* \param node Node contained in the tree.
|
* \param node Node contained in the tree.
|
||||||
* \return The following node. For the tail node 0 is returned.
|
* \return The following node, or 0 for the tail node.
|
||||||
|
* \note This operation is O(log n), and amortized O(1) during full traversal.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
|
tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
|
||||||
{
|
{
|
||||||
tommy_tree_node* parent;
|
|
||||||
|
|
||||||
if (node->next) {
|
if (node->next) {
|
||||||
node = node->next;
|
node = node->next;
|
||||||
while (node->prev)
|
while (node->prev)
|
||||||
|
|
@ -205,7 +263,7 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent = tommy_tree_parent(node);
|
tommy_tree_node* parent = tommy_tree_parent(node);
|
||||||
while (parent && node == parent->next) {
|
while (parent && node == parent->next) {
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = tommy_tree_parent(parent);
|
parent = tommy_tree_parent(parent);
|
||||||
|
|
@ -216,13 +274,13 @@ tommy_inline tommy_tree_node* tommy_tree_next(tommy_tree_node* node)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the node preceding the specified one in tree order.
|
* Gets the node preceding the specified one in tree order.
|
||||||
|
* See \ref tommy_tree_iterator_validity for iterator validity across insertions and removals.
|
||||||
* \param node Node contained in the tree.
|
* \param node Node contained in the tree.
|
||||||
* \return The preceding node. For the head node 0 is returned.
|
* \return The preceding node, or 0 for the head node.
|
||||||
|
* \note This operation is O(log n), and amortized O(1) during full traversal.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
|
tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
|
||||||
{
|
{
|
||||||
tommy_tree_node* parent;
|
|
||||||
|
|
||||||
if (node->prev) {
|
if (node->prev) {
|
||||||
node = node->prev;
|
node = node->prev;
|
||||||
while (node->next)
|
while (node->next)
|
||||||
|
|
@ -230,7 +288,7 @@ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent = tommy_tree_parent(node);
|
tommy_tree_node* parent = tommy_tree_parent(node);
|
||||||
while (parent && node == parent->prev) {
|
while (parent && node == parent->prev) {
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = tommy_tree_parent(parent);
|
parent = tommy_tree_parent(parent);
|
||||||
|
|
@ -240,8 +298,10 @@ tommy_inline tommy_tree_node* tommy_tree_prev(tommy_tree_node* node)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the head (smallest) element in the tree.
|
* Gets the head (smallest) node in the tree.
|
||||||
* \return The head node. For empty trees 0 is returned.
|
* \param tree The tree.
|
||||||
|
* \return The head node, or 0 if the tree is empty.
|
||||||
|
* \note This operation is O(log n).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree)
|
tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree)
|
||||||
{
|
{
|
||||||
|
|
@ -257,8 +317,10 @@ tommy_inline tommy_tree_node* tommy_tree_head(tommy_tree* tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the tail (greatest) element in the tree.
|
* Gets the tail (greatest) node in the tree.
|
||||||
* \return The tail node. For empty trees 0 is returned.
|
* \param tree The tree.
|
||||||
|
* \return The tail node, or 0 if the tree is empty.
|
||||||
|
* \note This operation is O(log n).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree)
|
tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree)
|
||||||
{
|
{
|
||||||
|
|
@ -276,7 +338,8 @@ tommy_inline tommy_tree_node* tommy_tree_tail(tommy_tree* tree)
|
||||||
/**
|
/**
|
||||||
* Removes and returns the head (smallest) element.
|
* Removes and returns the head (smallest) element.
|
||||||
* If the tree is empty, 0 is returned.
|
* If the tree is empty, 0 is returned.
|
||||||
* \return The removed element, or 0 if empty.
|
* \param tree The tree.
|
||||||
|
* \return The removed element, or 0 if the tree is empty.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_tree_remove_head(tommy_tree* tree)
|
tommy_inline void* tommy_tree_remove_head(tommy_tree* tree)
|
||||||
{
|
{
|
||||||
|
|
@ -291,7 +354,8 @@ tommy_inline void* tommy_tree_remove_head(tommy_tree* tree)
|
||||||
/**
|
/**
|
||||||
* Removes and returns the tail (greatest) element.
|
* Removes and returns the tail (greatest) element.
|
||||||
* If the tree is empty, 0 is returned.
|
* If the tree is empty, 0 is returned.
|
||||||
* \return The removed element, or 0 if empty.
|
* \param tree The tree.
|
||||||
|
* \return The removed element, or 0 if the tree is empty.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree)
|
tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree)
|
||||||
{
|
{
|
||||||
|
|
@ -303,44 +367,53 @@ tommy_inline void* tommy_tree_remove_tail(tommy_tree* tree)
|
||||||
return tommy_tree_remove_existing(tree, node);
|
return tommy_tree_remove_existing(tree, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \internal
|
/**
|
||||||
* Searches a node in the tree.
|
* Finds the first equal node in the tree with a specific comparison function.
|
||||||
* If the element is not found, 0 is returned.
|
*
|
||||||
* \param cmp Comparison function.
|
* Like tommy_tree_find() but you can specify a different comparison function.
|
||||||
* \param node Node to start the search from.
|
* Note that this function must define a suborder of the original one.
|
||||||
* \param data Element used for comparison.
|
* The first equal node in tree order is returned.
|
||||||
* \return The first node found, or 0 if none.
|
*
|
||||||
|
* The cmp_arg argument will be the first argument of the comparison function,
|
||||||
|
* and it can be of a different type than the elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \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_find_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) {
|
while (node) {
|
||||||
int c = cmp(data, node->data);
|
/* comparators must not change the tree, so retain the data across the call */
|
||||||
|
void* data = node->data;
|
||||||
|
int c = cmp(cmp_arg, data);
|
||||||
|
|
||||||
if (c < 0)
|
if (c < 0)
|
||||||
node = node->prev;
|
node = node->prev;
|
||||||
else if (c > 0)
|
else if (c > 0)
|
||||||
node = node->next;
|
node = node->next;
|
||||||
else
|
else {
|
||||||
return node;
|
candidate = node;
|
||||||
|
node = node->prev;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches an element in the tree.
|
* Finds the first equal node in the tree.
|
||||||
* If the element is not found, 0 is returned.
|
* If the element is not found, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
* \param data Element used for comparison.
|
* \param data Element used for comparison.
|
||||||
* \return The first element found, or 0 if none.
|
* \return The first equal node in tree order, or 0 if none.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data)
|
tommy_inline tommy_tree_node* tommy_tree_find(tommy_tree* tree, const void* data)
|
||||||
{
|
{
|
||||||
tommy_tree_node* node = tommy_tree_search_node(tree->cmp, tree->root, data);
|
return tommy_tree_find_compare(tree, tree->cmp, data);
|
||||||
|
|
||||||
if (!node)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return node->data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -348,42 +421,65 @@ tommy_inline void* tommy_tree_search(tommy_tree* tree, void* data)
|
||||||
*
|
*
|
||||||
* Like tommy_tree_search() but you can specify a different 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.
|
* Note that this function must define a suborder of the original one.
|
||||||
|
* The first equal 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.
|
* and it can be of a different type than the elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The first equal element in tree order, or 0 if none.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_tree_search_compare(tommy_tree* tree, tommy_compare_func* cmp, void* cmp_arg)
|
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(cmp, tree->root, cmp_arg);
|
tommy_tree_node* node = tommy_tree_find_compare(tree, cmp, cmp_arg);
|
||||||
|
|
||||||
if (!node)
|
return node ? node->data : 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
return node->data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \internal
|
/**
|
||||||
* Searches a node in the tree with key greater or equal than the specified one.
|
* Searches an element in the tree.
|
||||||
* If no such element exists, 0 is returned.
|
* If the element is not found, 0 is returned.
|
||||||
* \param cmp Comparison function.
|
* \param tree The tree.
|
||||||
* \param node Node to start the search from.
|
|
||||||
* \param data Element used for comparison.
|
* \param data Element used for comparison.
|
||||||
* \return The first node found, or 0 if none.
|
* \return The first equal element in tree order, 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_inline void* tommy_tree_search(tommy_tree* tree, const void* data)
|
||||||
{
|
{
|
||||||
|
tommy_tree_node* node = tommy_tree_find(tree, data);
|
||||||
|
|
||||||
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a node in the tree with key greater or equal than the specified one with a specific comparison function.
|
||||||
|
*
|
||||||
|
* Like tommy_tree_find_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 node 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 elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The first node in tree order with key greater or equal, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_tree_node* tommy_tree_find_greater_equal_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
|
||||||
|
{
|
||||||
|
tommy_tree_node* node = tree->root;
|
||||||
tommy_tree_node* candidate = 0;
|
tommy_tree_node* candidate = 0;
|
||||||
|
|
||||||
while (node) {
|
while (node) {
|
||||||
int c = cmp(data, node->data);
|
/* comparators must not change the tree, so retain the data across the call */
|
||||||
|
void* data = node->data;
|
||||||
|
int c = cmp(cmp_arg, data);
|
||||||
|
|
||||||
if (c < 0) {
|
if (c <= 0) {
|
||||||
candidate = node;
|
candidate = node;
|
||||||
node = node->prev;
|
node = node->prev;
|
||||||
} else if (c > 0) {
|
|
||||||
node = node->next;
|
|
||||||
} else {
|
} else {
|
||||||
return node;
|
node = node->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -391,19 +487,15 @@ tommy_inline tommy_tree_node* tommy_tree_search_greater_equal_node(tommy_compare
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches an element in the tree with key greater or equal than the specified one.
|
* Finds a node in the tree with key greater or equal than the specified one.
|
||||||
* If no such element exists, 0 is returned.
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
* \param data Element used for comparison.
|
* \param data Element used for comparison.
|
||||||
* \return The first element found, or 0 if none.
|
* \return The first node 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 tommy_tree_node* tommy_tree_find_greater_equal(tommy_tree* tree, const void* data)
|
||||||
{
|
{
|
||||||
tommy_tree_node* node = tommy_tree_search_greater_equal_node(tree->cmp, tree->root, data);
|
return tommy_tree_find_greater_equal_compare(tree, tree->cmp, data);
|
||||||
|
|
||||||
if (!node)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return node->data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -411,42 +503,60 @@ 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.
|
* 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.
|
* 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.
|
* and it can be of a different type than the elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The first element in tree order with key greater or equal, or 0 if none.
|
||||||
*/
|
*/
|
||||||
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);
|
tommy_tree_node* node = tommy_tree_find_greater_equal_compare(tree, cmp, cmp_arg);
|
||||||
|
|
||||||
if (!node)
|
return node ? node->data : 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
return node->data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \internal
|
/**
|
||||||
* Searches a node in the tree with key less or equal than the specified one.
|
* Searches an element in the tree with key greater or equal than the specified one.
|
||||||
* If no such element exists, 0 is returned.
|
* If no such element exists, 0 is returned.
|
||||||
* \param cmp Comparison function.
|
* \param tree The tree.
|
||||||
* \param node Node to start the search from.
|
|
||||||
* \param data Element used for comparison.
|
* \param data Element used for comparison.
|
||||||
* \return The first node found, or 0 if none.
|
* \return The first element in tree order with key greater or equal, 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_inline void* tommy_tree_search_greater_equal(tommy_tree* tree, const void* data)
|
||||||
{
|
{
|
||||||
|
tommy_tree_node* node = tommy_tree_find_greater_equal(tree, data);
|
||||||
|
|
||||||
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the first node in tree order with key strictly greater than the specified one using a specific comparison function.
|
||||||
|
* The function must define a suborder of the tree comparison function.
|
||||||
|
* Equivalent elements are excluded from the result.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The first strictly greater node, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_tree_node* tommy_tree_find_greater_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
|
||||||
|
{
|
||||||
|
tommy_tree_node* node = tree->root;
|
||||||
tommy_tree_node* candidate = 0;
|
tommy_tree_node* candidate = 0;
|
||||||
|
|
||||||
while (node) {
|
while (node) {
|
||||||
int c = cmp(data, node->data);
|
/* comparators must not change the tree, so retain the data across the call */
|
||||||
|
void* data = node->data;
|
||||||
|
int c = cmp(cmp_arg, data);
|
||||||
|
|
||||||
if (c < 0) {
|
if (c < 0) {
|
||||||
node = node->prev;
|
|
||||||
} else if (c > 0) {
|
|
||||||
candidate = node;
|
candidate = node;
|
||||||
node = node->next;
|
node = node->prev;
|
||||||
} else {
|
} else {
|
||||||
return node;
|
node = node->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,19 +564,92 @@ 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.
|
* Finds the first node in tree order with key strictly greater than the specified one.
|
||||||
* If no such element exists, 0 is returned.
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
* \param data Element used for comparison.
|
* \param data Element used for comparison.
|
||||||
* \return The first element found, or 0 if none.
|
* \return The first strictly greater node, or 0 if none.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_tree_search_less_equal(tommy_tree* tree, void* data)
|
tommy_inline tommy_tree_node* tommy_tree_find_greater(tommy_tree* tree, const void* data)
|
||||||
{
|
{
|
||||||
tommy_tree_node* node = tommy_tree_search_less_equal_node(tree->cmp, tree->root, data);
|
return tommy_tree_find_greater_compare(tree, tree->cmp, data);
|
||||||
|
}
|
||||||
|
|
||||||
if (!node)
|
/**
|
||||||
return 0;
|
* Searches the first element in tree order with key strictly greater than the specified one using a specific comparison function.
|
||||||
|
* The function must define a suborder of the tree comparison function.
|
||||||
|
* Equivalent elements are excluded from the result.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The first strictly greater element, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_tree_search_greater_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
|
||||||
|
{
|
||||||
|
tommy_tree_node* node = tommy_tree_find_greater_compare(tree, cmp, cmp_arg);
|
||||||
|
|
||||||
return node->data;
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the first element in tree order with key strictly greater than the specified one.
|
||||||
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param data Element used for comparison.
|
||||||
|
* \return The first strictly greater element, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_tree_search_greater(tommy_tree* tree, const void* data)
|
||||||
|
{
|
||||||
|
tommy_tree_node* node = tommy_tree_find_greater(tree, data);
|
||||||
|
|
||||||
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a node in the tree with key less or equal than the specified one with a specific comparison function.
|
||||||
|
*
|
||||||
|
* Like tommy_tree_find_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 node 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 elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The last node in tree order with key less or equal, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_tree_node* tommy_tree_find_less_equal_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) {
|
||||||
|
/* comparators must not change the tree, so retain the data across the call */
|
||||||
|
void* data = node->data;
|
||||||
|
int c = cmp(cmp_arg, data);
|
||||||
|
|
||||||
|
if (c < 0) {
|
||||||
|
node = node->prev;
|
||||||
|
} else {
|
||||||
|
candidate = node;
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a node in the tree with key less or equal than the specified one.
|
||||||
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param data Element used for comparison.
|
||||||
|
* \return The last node in tree order with key less or equal, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_tree_node* tommy_tree_find_less_equal(tommy_tree* tree, const void* data)
|
||||||
|
{
|
||||||
|
return tommy_tree_find_less_equal_compare(tree, tree->cmp, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -474,34 +657,138 @@ 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.
|
* 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.
|
* 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.
|
* and it can be of a different type than the elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The last element in tree order with key less or equal, or 0 if none.
|
||||||
*/
|
*/
|
||||||
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 = tommy_tree_find_less_equal_compare(tree, cmp, cmp_arg);
|
||||||
|
|
||||||
if (!node)
|
return node ? node->data : 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
return node->data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches and removes an element.
|
* Searches an element in the tree with key less or equal than the specified one.
|
||||||
* If the element is not found, 0 is returned.
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
* \param data Element used for comparison.
|
* \param data Element used for comparison.
|
||||||
* \return The removed element, or 0 if not found.
|
* \return The last element in tree order with key less or equal, or 0 if none.
|
||||||
*/
|
*/
|
||||||
tommy_inline void* tommy_tree_remove(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_node(tree->cmp, tree->root, data);
|
tommy_tree_node* node = tommy_tree_find_less_equal(tree, data);
|
||||||
|
|
||||||
if (!node)
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the last node in tree order with key strictly less than the specified one using a specific comparison function.
|
||||||
|
* The function must define a suborder of the tree comparison function.
|
||||||
|
* Equivalent elements are excluded from the result.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The last strictly less node, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_tree_node* tommy_tree_find_less_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) {
|
||||||
|
/* comparators must not change the tree, so retain the data across the call */
|
||||||
|
void* data = node->data;
|
||||||
|
int c = cmp(cmp_arg, data);
|
||||||
|
|
||||||
|
if (c > 0) {
|
||||||
|
candidate = node;
|
||||||
|
node = node->next;
|
||||||
|
} else {
|
||||||
|
node = node->prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the last node in tree order with key strictly less than the specified one.
|
||||||
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param data Element used for comparison.
|
||||||
|
* \return The last strictly less node, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline tommy_tree_node* tommy_tree_find_less(tommy_tree* tree, const void* data)
|
||||||
|
{
|
||||||
|
return tommy_tree_find_less_compare(tree, tree->cmp, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the last element in tree order with key strictly less than the specified one using a specific comparison function.
|
||||||
|
* The function must define a suborder of the tree comparison function.
|
||||||
|
* Equivalent elements are excluded from the result.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \return The last strictly less element, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_tree_search_less_compare(tommy_tree* tree, tommy_compare_func* cmp, const void* cmp_arg)
|
||||||
|
{
|
||||||
|
tommy_tree_node* node = tommy_tree_find_less_compare(tree, cmp, cmp_arg);
|
||||||
|
|
||||||
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the last element in tree order with key strictly less than the specified one.
|
||||||
|
* If no such element exists, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param data Element used for comparison.
|
||||||
|
* \return The last strictly less element, or 0 if none.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_tree_search_less(tommy_tree* tree, const void* data)
|
||||||
|
{
|
||||||
|
tommy_tree_node* node = tommy_tree_find_less(tree, data);
|
||||||
|
|
||||||
|
return node ? node->data : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches and removes an element from the tree 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 elements in the tree.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param cmp Comparison function called with cmp_arg and the element in the tree.
|
||||||
|
* \param cmp_arg Search key, which may have a different type from tree elements.
|
||||||
|
* \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* candidate = tommy_tree_find_compare(tree, cmp, cmp_arg);
|
||||||
|
|
||||||
|
if (!candidate)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return tommy_tree_remove_existing(tree, node);
|
return tommy_tree_remove_existing(tree, candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches and removes an element from the tree.
|
||||||
|
* If the element is not found, 0 is returned.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param data Element used for comparison.
|
||||||
|
* \return The first equal element in tree order, or 0 if not found.
|
||||||
|
*/
|
||||||
|
tommy_inline void* tommy_tree_remove(tommy_tree* tree, const void* data)
|
||||||
|
{
|
||||||
|
return tommy_tree_remove_compare(tree, tree->cmp, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -520,46 +807,58 @@ tommy_inline void* tommy_tree_remove(tommy_tree* tree, void* data)
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // creates an object
|
* // creates an element
|
||||||
* struct object* obj = malloc(sizeof(struct object));
|
* struct element* elem = malloc(sizeof(struct element));
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // insert it in the tree
|
* // insert it in the tree
|
||||||
* tommy_tree_insert(&tree, &obj->node, obj);
|
* tommy_tree_insert(&tree, &elem->node, elem);
|
||||||
*
|
*
|
||||||
* ...
|
* ...
|
||||||
*
|
*
|
||||||
* // deallocates all the objects iterating the tree
|
* // deallocates all the elements iterating the tree
|
||||||
* tommy_tree_foreach(&tree, free);
|
* tommy_tree_foreach(&tree, free);
|
||||||
|
* tommy_tree_clear(&tree); // resets the tree before reuse
|
||||||
* \endcode
|
* \endcode
|
||||||
|
*
|
||||||
|
* If the callback frees the elements, the tree retains dangling node
|
||||||
|
* pointers. Call tommy_tree_clear() or tommy_tree_init() before reusing it.
|
||||||
|
* The tree has no storage to release.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param func Function to call with each element.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func)
|
TOMMY_API void tommy_tree_foreach(tommy_tree* tree, tommy_foreach_func* func);
|
||||||
{
|
|
||||||
tommy_tree_foreach_node(tree->root, func);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the specified function with an argument for each element in the tree.
|
* 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.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \param func Function to call with each element.
|
||||||
|
* \param arg Argument to pass to the function.
|
||||||
*/
|
*/
|
||||||
tommy_inline void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg)
|
TOMMY_API void tommy_tree_foreach_arg(tommy_tree* tree, tommy_foreach_arg_func* func, void* arg);
|
||||||
{
|
|
||||||
tommy_tree_foreach_arg_node(tree->root, func, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if empty.
|
* Checks whether the tree is empty.
|
||||||
* \return If the tree is empty.
|
* \param tree The tree.
|
||||||
|
* \return Nonzero if the tree is empty, 0 otherwise.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_bool_t tommy_tree_empty(tommy_tree* tree)
|
tommy_inline tommy_bool_t tommy_tree_empty(const tommy_tree* tree)
|
||||||
{
|
{
|
||||||
return tree->root == 0;
|
return tree->root == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the number of elements.
|
* Gets the number of elements.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \return The number of elements in the tree.
|
||||||
|
* \note This operation is O(1).
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_size_t tommy_tree_count(tommy_tree* tree)
|
tommy_inline tommy_size_t tommy_tree_count(const tommy_tree* tree)
|
||||||
{
|
{
|
||||||
return tree->count;
|
return tree->count;
|
||||||
}
|
}
|
||||||
|
|
@ -567,8 +866,10 @@ tommy_inline tommy_size_t tommy_tree_count(tommy_tree* tree)
|
||||||
/**
|
/**
|
||||||
* Gets the size of allocated memory.
|
* Gets the size of allocated memory.
|
||||||
* It includes the size of the ::tommy_tree_node of the stored elements.
|
* It includes the size of the ::tommy_tree_node of the stored elements.
|
||||||
|
* \param tree The tree.
|
||||||
|
* \return Total memory allocated by the tree, in bytes.
|
||||||
*/
|
*/
|
||||||
TOMMY_API tommy_size_t tommy_tree_memory_usage(tommy_tree* tree);
|
TOMMY_API tommy_size_t tommy_tree_memory_usage(const tommy_tree* tree);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
* Generic types.
|
* Generic types.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __TOMMYTYPES_H
|
#ifndef TOMMY_TYPES_H
|
||||||
#define __TOMMYTYPES_H
|
#define TOMMY_TYPES_H
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* types */
|
/* types */
|
||||||
|
|
@ -14,15 +14,18 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
#include <limits.h>
|
||||||
typedef unsigned tommy_uint32_t; /**< Generic uint32_t type. */
|
typedef unsigned tommy_uint32_t; /**< Generic uint32_t type. */
|
||||||
typedef unsigned _int64 tommy_uint64_t; /**< Generic uint64_t type. */
|
typedef unsigned _int64 tommy_uint64_t; /**< Generic uint64_t type. */
|
||||||
typedef size_t tommy_uintptr_t; /**< Generic uintptr_t type. */
|
typedef size_t tommy_uintptr_t; /**< Generic uintptr_t type. */
|
||||||
#ifdef _WIN64
|
#ifdef _WIN64
|
||||||
#define TOMMY_SIZE_BIT 64
|
#define TOMMY_SIZE_BIT 64
|
||||||
|
#define TOMMY_SIZE_MAX _UI64_MAX
|
||||||
typedef unsigned _int64 tommy_size_t; /**< Generic size_t type. */
|
typedef unsigned _int64 tommy_size_t; /**< Generic size_t type. */
|
||||||
typedef _int64 tommy_ssize_t; /**< Generic ssize_t type. */
|
typedef _int64 tommy_ssize_t; /**< Generic ssize_t type. */
|
||||||
#else
|
#else
|
||||||
#define TOMMY_SIZE_BIT 32
|
#define TOMMY_SIZE_BIT 32
|
||||||
|
#define TOMMY_SIZE_MAX UINT_MAX
|
||||||
typedef unsigned tommy_size_t; /**< Generic size_t type. */
|
typedef unsigned tommy_size_t; /**< Generic size_t type. */
|
||||||
typedef int tommy_ssize_t; /**< Generic ssize_t type. */
|
typedef int tommy_ssize_t; /**< Generic ssize_t type. */
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -33,10 +36,12 @@ typedef uint64_t tommy_uint64_t; /**< Generic uint64_t type. */
|
||||||
typedef uintptr_t tommy_uintptr_t; /**< Generic uintptr_t type. */
|
typedef uintptr_t tommy_uintptr_t; /**< Generic uintptr_t type. */
|
||||||
#if SIZE_MAX == UINT64_MAX
|
#if SIZE_MAX == UINT64_MAX
|
||||||
#define TOMMY_SIZE_BIT 64
|
#define TOMMY_SIZE_BIT 64
|
||||||
|
#define TOMMY_SIZE_MAX SIZE_MAX
|
||||||
typedef uint64_t tommy_size_t; /**< Generic size_t type. */
|
typedef uint64_t tommy_size_t; /**< Generic size_t type. */
|
||||||
typedef int64_t tommy_ssize_t; /**< Generic ssize_t type. */
|
typedef int64_t tommy_ssize_t; /**< Generic ssize_t type. */
|
||||||
#elif SIZE_MAX == UINT32_MAX
|
#elif SIZE_MAX == UINT32_MAX
|
||||||
#define TOMMY_SIZE_BIT 32
|
#define TOMMY_SIZE_BIT 32
|
||||||
|
#define TOMMY_SIZE_MAX SIZE_MAX
|
||||||
typedef uint32_t tommy_size_t; /**< Generic size_t type. */
|
typedef uint32_t tommy_size_t; /**< Generic size_t type. */
|
||||||
typedef int32_t tommy_ssize_t; /**< Generic ssize_t type. */
|
typedef int32_t tommy_ssize_t; /**< Generic ssize_t type. */
|
||||||
#else
|
#else
|
||||||
|
|
@ -51,7 +56,7 @@ typedef int tommy_bool_t; /**< Generic boolean type. */
|
||||||
* Generic unsigned integer type.
|
* Generic unsigned integer type.
|
||||||
*
|
*
|
||||||
* It has no specific size, as it is used to store only small values.
|
* It has no specific size, as it is used to store only small values.
|
||||||
* To make the code more efficient, a full 32 bit integer is used.
|
* To make the code more efficient, a full 32-bit integer is used.
|
||||||
*/
|
*/
|
||||||
typedef tommy_uint32_t tommy_uint_t;
|
typedef tommy_uint32_t tommy_uint_t;
|
||||||
|
|
||||||
|
|
@ -80,7 +85,57 @@ typedef tommy_uint32_t tommy_uint_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic malloc(), calloc(), realloc() and free() functions.
|
* Generic malloc(), calloc(), realloc() and free() functions.
|
||||||
* Redefine them to what you need. By default they map to the C malloc(), calloc(), realloc() and free().
|
*
|
||||||
|
* Redefine them to what you need. By default they map to the C malloc(),
|
||||||
|
* calloc(), realloc() and free().
|
||||||
|
*
|
||||||
|
* \par Infallible Allocation Invariant
|
||||||
|
* Tommy assumes that dynamic memory allocation never fails and that these
|
||||||
|
* functions always return a valid, non-zero pointer. The library does not
|
||||||
|
* check for allocation failures and does not return error codes for
|
||||||
|
* out-of-memory (OOM) conditions.
|
||||||
|
*
|
||||||
|
* \par Rationale
|
||||||
|
* This choice reflects the memory model of modern general-purpose operating
|
||||||
|
* systems:
|
||||||
|
* - **Virtual Memory Overcommit:** On modern systems (such as Linux with default
|
||||||
|
* overcommit heuristics), malloc() reserves virtual address space and almost
|
||||||
|
* never returns 0 on 64-bit systems. Physical memory is committed on first write,
|
||||||
|
* meaning actual exhaustion triggers the kernel OOM Killer (SIGKILL) rather
|
||||||
|
* than an allocation failure. Checking for null returns in userspace often
|
||||||
|
* provides a false sense of security.
|
||||||
|
* - **Swap Thrashing:** In systems backed by swap, severe memory pressure causes
|
||||||
|
* destructive disk thrashing, degrading throughput by orders of magnitude and
|
||||||
|
* rendering the process unusable long before an allocation call fails.
|
||||||
|
*
|
||||||
|
* \par Advantages
|
||||||
|
* - **Zero Error-Path Overhead:** No branch predictions, conditional jumps, or
|
||||||
|
* status checks inside critical insertion and resizing loops, maximizing CPU
|
||||||
|
* cache efficiency and execution speed.
|
||||||
|
* - **Clean and Lean API:** Functions (such as insertion and resizing) can remain
|
||||||
|
* simple (often returning void or direct element pointers) without cluttering
|
||||||
|
* client code with error-propagation boilerplate.
|
||||||
|
* - **Deterministic Consistency:** Avoids complex transactional rollback logic
|
||||||
|
* otherwise required if an allocation failed halfway through dynamic table
|
||||||
|
* growth or node migration.
|
||||||
|
* - **Industry Alignment:** Follows the established design of high-performance
|
||||||
|
* systems libraries (e.g., GLib's g_malloc, Git's xmalloc, Redis's zmalloc,
|
||||||
|
* and default C++ operator new).
|
||||||
|
*
|
||||||
|
* \par Trade-offs and Limitations
|
||||||
|
* - **Embedded and Bare-Metal Systems:** Systems without an MMU/virtual memory
|
||||||
|
* (e.g., RTOS, microcontrollers) or strict overcommit disabled (vm.overcommit_memory=2)
|
||||||
|
* do return 0 on heap exhaustion.
|
||||||
|
* - **Multi-Tenant Services:** Infrastructure daemons (e.g., database engines) that
|
||||||
|
* must isolate a single client's excessive memory request without crashing the
|
||||||
|
* entire process cannot rely on unchecked allocations.
|
||||||
|
*
|
||||||
|
* \par Custom Interception
|
||||||
|
* If your environment requires custom handling (such as memory tracking, structured
|
||||||
|
* aborts, or logging), redefine tommy_malloc, tommy_calloc, tommy_realloc, and
|
||||||
|
* tommy_free before including Tommy headers (or via compiler flags).
|
||||||
|
* Custom allocators must terminate the process (e.g., abort()) or initiate an
|
||||||
|
* unwinding non-local exit (e.g., longjmp or C++ exception) rather than returning 0.
|
||||||
*/
|
*/
|
||||||
#if !defined(tommy_malloc) || !defined(tommy_calloc) || !defined(tommy_realloc) || !defined(tommy_free)
|
#if !defined(tommy_malloc) || !defined(tommy_calloc) || !defined(tommy_realloc) || !defined(tommy_free)
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -99,7 +154,7 @@ typedef tommy_uint32_t tommy_uint_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* modificators */
|
/* modifiers */
|
||||||
|
|
||||||
/** \internal
|
/** \internal
|
||||||
* Definition of TOMMY_API.
|
* Definition of TOMMY_API.
|
||||||
|
|
@ -124,7 +179,7 @@ typedef tommy_uint32_t tommy_uint_t;
|
||||||
* Definition of the restrict keyword if available.
|
* Definition of the restrict keyword if available.
|
||||||
*/
|
*/
|
||||||
#if !defined(tommy_restrict)
|
#if !defined(tommy_restrict)
|
||||||
#if __STDC_VERSION__ >= 199901L
|
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||||
#define tommy_restrict restrict
|
#define tommy_restrict restrict
|
||||||
#elif defined(_MSC_VER) || defined(__GNUC__)
|
#elif defined(_MSC_VER) || defined(__GNUC__)
|
||||||
#define tommy_restrict __restrict
|
#define tommy_restrict __restrict
|
||||||
|
|
@ -159,12 +214,12 @@ typedef tommy_uint32_t tommy_uint_t;
|
||||||
/* key/hash */
|
/* key/hash */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type used in indexed data structures to store the key of an object.
|
* Type used in indexed data structures to store the key of an element.
|
||||||
*/
|
*/
|
||||||
typedef tommy_size_t tommy_key_t;
|
typedef tommy_size_t tommy_key_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type used in hashtables to store the hash of an object.
|
* Type used in hashtables to store the hash of an element.
|
||||||
*/
|
*/
|
||||||
typedef tommy_size_t tommy_hash_t;
|
typedef tommy_size_t tommy_hash_t;
|
||||||
|
|
||||||
|
|
@ -174,11 +229,11 @@ typedef tommy_size_t tommy_hash_t;
|
||||||
/**
|
/**
|
||||||
* Data structure node.
|
* Data structure node.
|
||||||
* This node type is shared between all the data structures and used to store some
|
* This node type is shared between all the data structures and used to store some
|
||||||
* info directly into the objects you want to store.
|
* info directly into the elements you want to store.
|
||||||
*
|
*
|
||||||
* A typical declaration is:
|
* A typical declaration is:
|
||||||
* \code
|
* \code
|
||||||
* struct object {
|
* struct element {
|
||||||
* tommy_node node;
|
* tommy_node node;
|
||||||
* // other fields
|
* // other fields
|
||||||
* };
|
* };
|
||||||
|
|
@ -187,7 +242,7 @@ typedef tommy_size_t tommy_hash_t;
|
||||||
typedef struct tommy_node_struct {
|
typedef struct tommy_node_struct {
|
||||||
/**
|
/**
|
||||||
* Next node.
|
* Next node.
|
||||||
* The tail node has it at 0, like a 0 terminated list.
|
* The tail node has it at 0, like a 0-terminated list.
|
||||||
*/
|
*/
|
||||||
struct tommy_node_struct* next;
|
struct tommy_node_struct* next;
|
||||||
|
|
||||||
|
|
@ -198,8 +253,9 @@ typedef struct tommy_node_struct {
|
||||||
struct tommy_node_struct* prev;
|
struct tommy_node_struct* prev;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pointer to the object containing the node.
|
* Pointer to the element containing the node.
|
||||||
* This field is initialized when inserting nodes into a data structure.
|
* This field is initialized when inserting nodes into a data structure.
|
||||||
|
* The element pointer passed to insertion functions must not be 0.
|
||||||
*/
|
*/
|
||||||
void* data;
|
void* data;
|
||||||
|
|
||||||
|
|
@ -207,6 +263,7 @@ typedef struct tommy_node_struct {
|
||||||
* Index of the node.
|
* Index of the node.
|
||||||
* With tries this field is used to store the key.
|
* With tries this field is used to store the key.
|
||||||
* With hashtables this field is used to store the hash value.
|
* With hashtables this field is used to store the hash value.
|
||||||
|
* With trees this field is used to store the parent pointer and balance factor.
|
||||||
* With lists this field is not used.
|
* With lists this field is not used.
|
||||||
*/
|
*/
|
||||||
tommy_size_t index;
|
tommy_size_t index;
|
||||||
|
|
@ -217,23 +274,20 @@ typedef struct tommy_node_struct {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare function for elements.
|
* Compare function for elements.
|
||||||
* \param obj_a Pointer to the first object to compare.
|
|
||||||
* \param obj_b Pointer to the second object to compare.
|
|
||||||
* \return <0 if the first element is less than the second, ==0 if equal, >0 if greater.
|
|
||||||
*
|
*
|
||||||
* This function is like the C strcmp().
|
* This function is like the C strcmp().
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object {
|
* struct element {
|
||||||
* tommy_node node;
|
* tommy_node node;
|
||||||
* int value;
|
* int value;
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* int compare(const void* obj_a, const void* obj_b)
|
* int compare(const void* elem_a, const void* elem_b)
|
||||||
* {
|
* {
|
||||||
* if (((const struct object*)obj_a)->value < ((const struct object*)obj_b)->value)
|
* if (((const struct element*)elem_a)->value < ((const struct element*)elem_b)->value)
|
||||||
* return -1;
|
* return -1;
|
||||||
* if (((const struct object*)obj_a)->value > ((const struct object*)obj_b)->value)
|
* if (((const struct element*)elem_a)->value > ((const struct element*)elem_b)->value)
|
||||||
* return 1;
|
* return 1;
|
||||||
* return 0;
|
* return 0;
|
||||||
* }
|
* }
|
||||||
|
|
@ -241,64 +295,62 @@ typedef struct tommy_node_struct {
|
||||||
* tommy_list_sort(&list, compare);
|
* tommy_list_sort(&list, compare);
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
|
* \param elem_a Pointer to the first element to compare.
|
||||||
|
* \param elem_b Pointer to the second element to compare.
|
||||||
|
* \return <0 if the first element is less than the second, ==0 if equal, >0 if greater.
|
||||||
*/
|
*/
|
||||||
typedef int tommy_compare_func(const void* obj_a, const void* obj_b);
|
typedef int tommy_compare_func(const void* elem_a, const void* elem_b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search function for elements.
|
* Search function for elements.
|
||||||
* \param arg Pointer to the value to search as passed at the search function.
|
|
||||||
* \param obj Pointer to the object to compare to.
|
|
||||||
* \return ==0 if the value matches the element. !=0 if different.
|
|
||||||
*
|
|
||||||
* The first argument is a pointer to the value to search exactly
|
|
||||||
* as it's passed at the search function called.
|
|
||||||
* The second argument is a pointer to the object inside the hashtable to compare.
|
|
||||||
*
|
|
||||||
* The return value has to be 0 if the values are equal. != 0 if they are different.
|
|
||||||
*
|
*
|
||||||
* \code
|
* \code
|
||||||
* struct object {
|
* struct element {
|
||||||
* tommy_node node;
|
* tommy_node node;
|
||||||
* int value;
|
* int value;
|
||||||
* };
|
* };
|
||||||
*
|
*
|
||||||
* int compare(const void* arg, const void* obj)
|
* int compare(const void* arg, const void* elem)
|
||||||
* {
|
* {
|
||||||
* const int* value_to_find = arg;
|
* const int* value_to_find = arg;
|
||||||
* const struct object* object_to_compare = obj;
|
* const struct element* element_to_compare = elem;
|
||||||
*
|
*
|
||||||
* return *value_to_find != object_to_compare->value;
|
* return *value_to_find != element_to_compare->value;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* int value_to_find = 1;
|
* int value_to_find = 1;
|
||||||
* struct object* obj = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find));
|
* struct element* elem = tommy_hashtable_search(&hashtable, compare, &value_to_find, tommy_inthash_u32(value_to_find));
|
||||||
* if (!obj) {
|
* if (!elem) {
|
||||||
* // not found
|
* // not found
|
||||||
* } else {
|
* } else {
|
||||||
* // found
|
* // found
|
||||||
* }
|
* }
|
||||||
* \endcode
|
* \endcode
|
||||||
*
|
*
|
||||||
|
* \param arg Pointer to the value to search as passed to the search function.
|
||||||
|
* \param elem Pointer to the element to compare to.
|
||||||
|
* \return ==0 if the value matches the element. !=0 if different.
|
||||||
*/
|
*/
|
||||||
typedef int tommy_search_func(const void* arg, const void* obj);
|
typedef int tommy_search_func(const void* arg, const void* elem);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Foreach function.
|
* Foreach function.
|
||||||
* \param obj Pointer to the object to iterate.
|
|
||||||
*
|
*
|
||||||
* A typical example is to use free() to deallocate all the objects in a list.
|
* A typical example is to use free() to deallocate all the elements in a list.
|
||||||
* \code
|
* \code
|
||||||
* tommy_list_foreach(&list, (tommy_foreach_func*)free);
|
* tommy_list_foreach(&list, free);
|
||||||
* \endcode
|
* \endcode
|
||||||
|
*
|
||||||
|
* \param elem Pointer to the element to iterate.
|
||||||
*/
|
*/
|
||||||
typedef void tommy_foreach_func(void* obj);
|
typedef void tommy_foreach_func(void* elem);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Foreach function with an argument.
|
* Foreach function with an argument.
|
||||||
* \param arg Pointer to a generic argument.
|
* \param arg Pointer to a generic argument.
|
||||||
* \param obj Pointer to the object to iterate.
|
* \param elem Pointer to the element to iterate.
|
||||||
*/
|
*/
|
||||||
typedef void tommy_foreach_arg_func(void* arg, void* obj);
|
typedef void tommy_foreach_arg_func(void* arg, void* elem);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/* bit hacks */
|
/* bit hacks */
|
||||||
|
|
@ -315,7 +367,7 @@ typedef void tommy_foreach_arg_func(void* arg, void* obj);
|
||||||
|
|
||||||
/** \internal
|
/** \internal
|
||||||
* Integer log2 for constants.
|
* Integer log2 for constants.
|
||||||
* You can use it only for exact power of 2 up to 256.
|
* You can use it only for exact powers of 2 up to 256.
|
||||||
*/
|
*/
|
||||||
#define TOMMY_ILOG2(value) ((value) == 256 ? 8 : (value) == 128 ? 7 : (value) == 64 ? 6 : (value) == 32 ? 5 : (value) == 16 ? 4 : (value) == 8 ? 3 : (value) == 4 ? 2 : (value) == 2 ? 1 : 0)
|
#define TOMMY_ILOG2(value) ((value) == 256 ? 8 : (value) == 128 ? 7 : (value) == 64 ? 6 : (value) == 32 ? 5 : (value) == 16 ? 4 : (value) == 8 ? 3 : (value) == 4 ? 2 : (value) == 2 ? 1 : 0)
|
||||||
|
|
||||||
|
|
@ -355,9 +407,9 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value)
|
||||||
*/
|
*/
|
||||||
return __builtin_clz(value) ^ 31;
|
return __builtin_clz(value) ^ 31;
|
||||||
#else
|
#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 */
|
/* from http://graphics.stanford.edu/~seander/bithacks.html */
|
||||||
static unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = {
|
static const unsigned char TOMMY_DE_BRUIJN_INDEX_ILOG2[32] = {
|
||||||
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
|
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
|
||||||
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
|
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
|
||||||
};
|
};
|
||||||
|
|
@ -372,13 +424,17 @@ tommy_inline tommy_uint_t tommy_ilog2_u32(tommy_uint32_t value)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TOMMY_SIZE_BIT == 64
|
|
||||||
/**
|
/**
|
||||||
* Bit scan reverse or integer log2 for 64 bits.
|
* Bit scan reverse or integer log2 for 64 bits.
|
||||||
|
* Return the bit index of the most significant 1 bit.
|
||||||
|
*
|
||||||
|
* If no bit is set, the result is undefined.
|
||||||
|
* \param value Value to scan. 0 is not allowed.
|
||||||
|
* \return The index of the most significant bit set.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value)
|
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;
|
unsigned long count;
|
||||||
_BitScanReverse64(&count, value);
|
_BitScanReverse64(&count, value);
|
||||||
return count;
|
return count;
|
||||||
|
|
@ -393,7 +449,6 @@ tommy_inline tommy_uint_t tommy_ilog2_u64(tommy_uint64_t value)
|
||||||
return tommy_ilog2_u32(l);
|
return tommy_ilog2_u32(l);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bit scan forward or trailing zero count.
|
* Bit scan forward or trailing zero count.
|
||||||
|
|
@ -412,24 +467,28 @@ tommy_inline tommy_uint_t tommy_ctz_u32(tommy_uint32_t value)
|
||||||
#elif defined(__GNUC__)
|
#elif defined(__GNUC__)
|
||||||
return __builtin_ctz(value);
|
return __builtin_ctz(value);
|
||||||
#else
|
#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 */
|
/* from http://graphics.stanford.edu/~seander/bithacks.html */
|
||||||
static const unsigned char TOMMY_DE_BRUIJN_INDEX_CTZ[32] = {
|
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,
|
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
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TOMMY_SIZE_BIT == 64
|
|
||||||
/**
|
/**
|
||||||
* Bit scan forward or trailing zero count for 64 bits.
|
* Bit scan forward or trailing zero count for 64 bits.
|
||||||
|
* Return the bit index of the least significant 1 bit.
|
||||||
|
*
|
||||||
|
* If no bit is set, the result is undefined.
|
||||||
|
* \param value Value to scan. 0 is not allowed.
|
||||||
|
* \return The index of the least significant bit set.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
|
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;
|
unsigned long count;
|
||||||
_BitScanForward64(&count, value);
|
_BitScanForward64(&count, value);
|
||||||
return count;
|
return count;
|
||||||
|
|
@ -444,16 +503,16 @@ tommy_inline tommy_uint_t tommy_ctz_u64(tommy_uint64_t value)
|
||||||
return tommy_ctz_u32(h) + 32;
|
return tommy_ctz_u32(h) + 32;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds up to the next power of 2.
|
* Rounds up to the next power of 2.
|
||||||
* For the value 0, the result is undefined.
|
* For the value 0, the result is undefined.
|
||||||
|
* \param value Value to round up.
|
||||||
* \return The smallest power of 2 not less than the specified value.
|
* \return The smallest power of 2 not less than the specified value.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_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 */
|
/* from http://graphics.stanford.edu/~seander/bithacks.html */
|
||||||
|
|
||||||
--value;
|
--value;
|
||||||
|
|
@ -469,6 +528,9 @@ tommy_inline tommy_uint32_t tommy_roundup_pow2_u32(tommy_uint32_t value)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds up to the next power of 2 for 64 bits.
|
* Rounds up to the next power of 2 for 64 bits.
|
||||||
|
* For the value 0, the result is undefined.
|
||||||
|
* \param value Value to round up.
|
||||||
|
* \return The smallest power of 2 not less than the specified value.
|
||||||
*/
|
*/
|
||||||
tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value)
|
tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value)
|
||||||
{
|
{
|
||||||
|
|
@ -485,7 +547,8 @@ tommy_inline tommy_uint64_t tommy_roundup_pow2_u64(tommy_uint64_t value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the specified word has a byte at 0.
|
* Checks if the specified word has a byte at 0.
|
||||||
|
* \param value Value to check.
|
||||||
* \return 0 or 1.
|
* \return 0 or 1.
|
||||||
*/
|
*/
|
||||||
tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
|
tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
|
||||||
|
|
@ -493,7 +556,17 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
|
||||||
return ((value - 0x01010101) & ~value & 0x80808080) != 0;
|
return ((value - 0x01010101) & ~value & 0x80808080) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
|
* Checks if the specified 64-bit word has a byte at 0.
|
||||||
|
* \param value Value to check.
|
||||||
|
* \return 0 or 1.
|
||||||
|
*/
|
||||||
|
tommy_inline int tommy_haszero_u64(tommy_uint64_t value)
|
||||||
|
{
|
||||||
|
return ((value - 0x0101010101010101ULL) & ~value & 0x8080808080808080ULL) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Bit depth mapping.
|
* Bit depth mapping.
|
||||||
*/
|
*/
|
||||||
#if TOMMY_SIZE_BIT == 64
|
#if TOMMY_SIZE_BIT == 64
|
||||||
|
|
@ -507,3 +580,4 @@ tommy_inline int tommy_haszero_u32(tommy_uint32_t value)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue