diff --git a/.notes/hash_generator_1.c b/.notes/hash_generator_1.c new file mode 100644 index 0000000..0265cfa --- /dev/null +++ b/.notes/hash_generator_1.c @@ -0,0 +1,18 @@ +https://www.programiz.com/c-programming/online-compiler/ +#include + +static unsigned int hashUInt(unsigned int x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + +int main() { + //print the index/hash pairs + for (unsigned int i = 0; i < 100; i++) { + printf("{%u:%u}\n", i, hashUInt(i) % 16); + } + + return 0; +} \ No newline at end of file diff --git a/.notes/hash_generator_2.c b/.notes/hash_generator_2.c new file mode 100644 index 0000000..ebe0bc9 --- /dev/null +++ b/.notes/hash_generator_2.c @@ -0,0 +1,23 @@ +https://www.programiz.com/c-programming/online-compiler/ +#include + +static unsigned int hashUInt(unsigned int x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + +int main() { + //find the first number with a specific hash, then print the c-code + for (unsigned int h = 0; h < 20; h++) { + for (unsigned int i = 0; i < 100; i++) { + if (hashUInt(i) % 32 == h) { + printf("Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(%d), TOY_VALUE_TO_INTEGER(42)); //hash: %d\n", i, h); + break; + } + } + } + + return 0; +} \ No newline at end of file diff --git a/source/toy.h b/source/toy.h index 86a1de7..b0360de 100644 --- a/source/toy.h +++ b/source/toy.h @@ -10,7 +10,7 @@ #include "toy_stack.h" #include "toy_bucket.h" #include "toy_string.h" -//TODO: hashtable +#include "toy_table.h" //IR structures and other components #include "toy_ast.h" diff --git a/source/toy_table.c b/source/toy_table.c index 5049112..50a807e 100644 --- a/source/toy_table.c +++ b/source/toy_table.c @@ -9,59 +9,24 @@ #define MIN_CAPACITY 16 //utils -static Toy_Table* adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity) { - //allocate and zero a new table in memory - Toy_Table* newTable = malloc(newCapacity * sizeof(Toy_TableEntry) + sizeof(Toy_Table)); - - newTable->capacity = newCapacity; - newTable->count = 0; - newTable->minPsl = 0; - newTable->maxPsl = 0; - - //unlike other structures, the empty space in a table needs to be null - memset(newTable + 1, 0, newTable->capacity * sizeof(Toy_TableEntry)); - - if (oldTable == NULL) { //for initial allocations - return newTable; - } - - //for each entry in the old table, copy it into the new table - for (int i = 0; i < oldTable->capacity; i++) { - Toy_insertTable(&newTable, oldTable->data[i].key, oldTable->data[i].value); - } - - //clean up and return - free(oldTable); - return newTable; -} - -//exposed functions -Toy_Table* Toy_allocateTable() { - return adjustTableCapacity(NULL, MIN_CAPACITY); -} - -void Toy_freeTable(Toy_Table* table) { - //TODO: slip in a call to free the complex values here - - free(table); -} - -void Toy_insertTable(Toy_Table** table, Toy_Value key, Toy_Value value) { - if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques - fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); - exit(-1); //TODO: #127 - } - - //expand the capacity - if ((*table)->capacity < (*table)->count * (1 / 0.75f)) { - (*table) = adjustTableCapacity(*table, (*table)->capacity * 2); - } - - //insert +static void probeAndInsert(Toy_Table** table, Toy_Value key, Toy_Value value) { + //make the entry unsigned int probe = Toy_hashValue(key) % (*table)->capacity; Toy_TableEntry entry = (Toy_TableEntry){ .key = key, .value = value, .psl = 0 }; + //probe while (true) { + //if we're overriding an existing value + if (TOY_VALUE_IS_EQUAL((*table)->data[probe].key, key)) { + (*table)->data[probe] = entry; + + //TODO: benchmark the psl optimisation + (*table)->minPsl = entry.psl < (*table)->minPsl ? entry.psl : (*table)->minPsl; + (*table)->maxPsl = entry.psl > (*table)->maxPsl ? entry.psl : (*table)->maxPsl; + + return; + } + //if this spot is free, insert and return if (TOY_VALUE_IS_NULL((*table)->data[probe].key)) { (*table)->data[probe] = entry; @@ -88,7 +53,65 @@ void Toy_insertTable(Toy_Table** table, Toy_Value key, Toy_Value value) { } } -Toy_Value Toy_lookupTableValue(Toy_Table** table, Toy_Value key) { +static Toy_Table* adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity) { + //allocate and zero a new table in memory + Toy_Table* newTable = malloc(newCapacity * sizeof(Toy_TableEntry) + sizeof(Toy_Table)); + + if (newTable == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_Table' of %u capacity\n" TOY_CC_RESET, newCapacity); + exit(-1); + } + + newTable->capacity = newCapacity; + newTable->count = 0; + newTable->minPsl = 0; + newTable->maxPsl = 0; + + //unlike other structures, the empty space in a table needs to be null + memset(newTable + 1, 0, newTable->capacity * sizeof(Toy_TableEntry)); + + if (oldTable == NULL) { //for initial allocations + return newTable; + } + + //for each entry in the old table, copy it into the new table + for (int i = 0; i < oldTable->capacity; i++) { + if (!TOY_VALUE_IS_NULL(oldTable->data[i].key)) { + probeAndInsert(&newTable, oldTable->data[i].key, oldTable->data[i].value); + } + } + + //clean up and return + free(oldTable); + return newTable; +} + +//exposed functions +Toy_Table* Toy_allocateTable() { + return adjustTableCapacity(NULL, MIN_CAPACITY); +} + +void Toy_freeTable(Toy_Table* table) { + //TODO: slip in a call to free the complex values here + + free(table); +} + +void Toy_insertTable(Toy_Table** table, Toy_Value key, Toy_Value value) { + if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques + fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); + exit(-1); //TODO: #127 + } + + //expand the capacity + if ((*table)->count > (*table)->capacity * 0.8) { + (*table) = adjustTableCapacity(*table, (*table)->capacity * 2); + } + + probeAndInsert(table, key, value); +} + +Toy_Value Toy_lookupTable(Toy_Table** table, Toy_Value key) { if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); exit(-1); //TODO: #127 @@ -96,7 +119,6 @@ Toy_Value Toy_lookupTableValue(Toy_Table** table, Toy_Value key) { //lookup unsigned int probe = Toy_hashValue(key) % (*table)->capacity; - unsigned int counter = 0; while (true) { //found the entry @@ -104,18 +126,17 @@ Toy_Value Toy_lookupTableValue(Toy_Table** table, Toy_Value key) { return (*table)->data[probe].value; } - //if the psl is too big, or empty slot - if ((*table)->data[probe].psl > counter || TOY_VALUE_IS_NULL((*table)->data[probe].key)) { + //if its an empty slot + if (TOY_VALUE_IS_NULL((*table)->data[probe].key)) { return TOY_VALUE_TO_NULL(); } //adjust and continue probe = (probe + 1) % (*table)->capacity; - counter++; } } -void Toy_removeTableEntry(Toy_Table** table, Toy_Value key) { +void Toy_removeTable(Toy_Table** table, Toy_Value key) { if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); exit(-1); //TODO: #127 @@ -123,7 +144,6 @@ void Toy_removeTableEntry(Toy_Table** table, Toy_Value key) { //lookup unsigned int probe = Toy_hashValue(key) % (*table)->capacity; - unsigned int counter = 0; unsigned int wipe = probe; //wiped at the end while (true) { @@ -132,31 +152,31 @@ void Toy_removeTableEntry(Toy_Table** table, Toy_Value key) { break; } - //if the psl is too big, or empty slot - if ((*table)->data[probe].psl > counter || TOY_VALUE_IS_NULL((*table)->data[probe].key)) { + //if its an empty slot + if (TOY_VALUE_IS_NULL((*table)->data[probe].key)) { return; } //adjust and continue probe = (probe + 1) % (*table)->capacity; - counter++; } - //shift down the later entries (past the probing point) + //shift along the later entries for (unsigned int i = (*table)->minPsl; i < (*table)->maxPsl; i++) { unsigned int p = (probe + i + 0) % (*table)->capacity; //prev unsigned int u = (probe + i + 1) % (*table)->capacity; //current - //if the psl is too big, or an empty slot, stop - if ((*table)->data[u].psl > (counter + i) || TOY_VALUE_IS_NULL((*table)->data[u].key)) { - break; - } - (*table)->data[p] = (*table)->data[u]; (*table)->data[p].psl--; - wipe = wipe % (*table)->capacity; + + //if you hit something where it should be, or nothing at all, stop + if (TOY_VALUE_IS_NULL((*table)->data[u].key) || (*table)->data[p].psl == 0) { + wipe = u; + break; + } } //finally, wipe the removed entry (*table)->data[wipe] = (Toy_TableEntry){ .key = TOY_VALUE_TO_NULL(), .value = TOY_VALUE_TO_NULL(), .psl = 0 }; + (*table)->count--; } \ No newline at end of file diff --git a/source/toy_table.h b/source/toy_table.h index 500718d..367d34c 100644 --- a/source/toy_table.h +++ b/source/toy_table.h @@ -23,5 +23,5 @@ typedef struct Toy_Table { //32 | 64 BITNESS TOY_API Toy_Table* Toy_allocateTable(); TOY_API void Toy_freeTable(Toy_Table* table); TOY_API void Toy_insertTable(Toy_Table** table, Toy_Value key, Toy_Value value); -TOY_API Toy_Value Toy_lookupTableValue(Toy_Table** table, Toy_Value key); -TOY_API void Toy_removeTableEntry(Toy_Table** table, Toy_Value key); +TOY_API Toy_Value Toy_lookupTable(Toy_Table** table, Toy_Value key); +TOY_API void Toy_removeTable(Toy_Table** table, Toy_Value key); diff --git a/tests/cases/test_table.c b/tests/cases/test_table.c index 3221196..5b14569 100644 --- a/tests/cases/test_table.c +++ b/tests/cases/test_table.c @@ -4,17 +4,610 @@ #include int test_table_allocation() { + //allocate and free a table + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //check + if (table == NULL) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a table\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + return 0; +} + +int test_table_simple_insert_lookup_and_remove() { + //simple insert + { + //setup + Toy_Table* table = Toy_allocateTable(); + + Toy_Value key = TOY_VALUE_TO_INTEGER(1); + Toy_Value value = TOY_VALUE_TO_INTEGER(42); + + //insert + Toy_insertTable(&table, key, value); + if (table == NULL || + table->capacity != 16 || + table->count != 1) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to insert into a table\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //lookup + Toy_Value result = Toy_lookupTable(&table, TOY_VALUE_TO_INTEGER(1)); + + //check lookup + if (table == NULL || + table->capacity != 16 || + table->count != 1 || + TOY_VALUE_AS_INTEGER(result) != 42) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to lookup from a table\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //remove + Toy_removeTable(&table, TOY_VALUE_TO_INTEGER(1)); + + //check remove + if (table == NULL || + table->capacity != 16 || + table->count != 0) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to remove from a table\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + return 0; +} + +//macros are a godsend +#define TEST_ENTRY_STATE(i, k, v, p) \ + TOY_VALUE_IS_INTEGER(table->data[i].key) != true || \ + TOY_VALUE_AS_INTEGER(table->data[i].key) != k || \ + TOY_VALUE_IS_INTEGER(table->data[i].value) != true || \ + TOY_VALUE_AS_INTEGER(table->data[i].value) != v || \ + table->data[i].psl != p + +int test_table_contents_no_expansion() { + //single insert + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //insert a key and value + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(1), TOY_VALUE_TO_INTEGER(42)); + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 1 || + + TEST_ENTRY_STATE(7, 1, 42, 0) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data, single insert {1:42}\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, no collisions + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(1), TOY_VALUE_TO_INTEGER(42)); //hash: 7 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(2), TOY_VALUE_TO_INTEGER(69)); //hash: 8 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(3), TOY_VALUE_TO_INTEGER(420)); //hash: 5 + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 3 || + + TEST_ENTRY_STATE(7, 1, 42, 0) || + TEST_ENTRY_STATE(8, 2, 69, 0) || + TEST_ENTRY_STATE(5, 3, 420, 0) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data, multiple inserts, no collisions {1:42},{2:69},{3:420}\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, with collisions + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(1), TOY_VALUE_TO_INTEGER(42)); //hash: 7 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(14), TOY_VALUE_TO_INTEGER(69)); //hash: 7 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(76), TOY_VALUE_TO_INTEGER(420)); //hash: 7 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(80), TOY_VALUE_TO_INTEGER(8891)); //hash: 7 + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 4 || + + TEST_ENTRY_STATE(7, 1, 42, 0) || + TEST_ENTRY_STATE(8, 14, 69, 1) || + TEST_ENTRY_STATE(9, 76, 420, 2) || + TEST_ENTRY_STATE(10, 80, 8891, 3) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data, muiltiple inserts, with collisions {1:42},{14:69},{76:420},{80:8891}\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, with collisions, modulo wrap + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(17), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(33), TOY_VALUE_TO_INTEGER(69)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(70), TOY_VALUE_TO_INTEGER(420)); //hash: 15 + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 3 || + + TEST_ENTRY_STATE(15, 17, 42, 0) || + TEST_ENTRY_STATE(0, 33, 69, 1) || + TEST_ENTRY_STATE(1, 70, 420, 2) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data, muiltiple inserts, with collisions, modulo wrap {17:42},{33:69},{70:420}\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //lookup, with collisions, modulo wrap + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(17), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(33), TOY_VALUE_TO_INTEGER(69)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(70), TOY_VALUE_TO_INTEGER(420)); //hash: 15 + + //lookup + Toy_Value result = Toy_lookupTable(&table, TOY_VALUE_TO_INTEGER(33)); + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 3 || + + TOY_VALUE_IS_INTEGER(result) != true || + TOY_VALUE_AS_INTEGER(result) != 69 + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Bad result from table lookup with collisions and modulo wrap\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, with collisions, modulo wrap, psl overlap + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(17), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(33), TOY_VALUE_TO_INTEGER(69)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(70), TOY_VALUE_TO_INTEGER(420)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(35), TOY_VALUE_TO_INTEGER(8891)); //hash: 1 + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 4 || + + TEST_ENTRY_STATE(15, 17, 42, 0) || + TEST_ENTRY_STATE(0, 33, 69, 1) || + TEST_ENTRY_STATE(1, 70, 420, 2) || + TEST_ENTRY_STATE(2, 35, 8891, 1) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data, muiltiple inserts, with collisions, modulo wrap, psl overlap {17:42},{33:69},{70:420},{35:8891}\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, with collisions, modulo wrap, psl overlap, psl shift + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(17), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(33), TOY_VALUE_TO_INTEGER(69)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(70), TOY_VALUE_TO_INTEGER(420)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(35), TOY_VALUE_TO_INTEGER(8891)); //hash: 1 + + //remove + Toy_removeTable(&table, TOY_VALUE_TO_INTEGER(33)); + + //check the state + if (table == NULL || + table->capacity != 16 || + table->count != 3 || + + TEST_ENTRY_STATE(15, 17, 42, 0) || + TEST_ENTRY_STATE(0, 70, 420, 1) || + TEST_ENTRY_STATE(1, 35, 8891, 0) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data, muiltiple inserts, with collisions, modulo wrap, psl overlap, psl shift {17:42},{*33:69},{70:420},{35:8891}\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + return 0; +} + +int test_table_contents_with_expansions() { + //simple expansion + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //insert a key and value + for (int i = 0; i < 20; i++) { + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(i), TOY_VALUE_TO_INTEGER(42)); + } + + //check the state + if (table == NULL || + table->capacity != 32 || + table->count != 20 + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to expand table capacity\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //expansion, multiple inserts, no collisions + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(0), TOY_VALUE_TO_INTEGER(42)); //hash: 0 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(35), TOY_VALUE_TO_INTEGER(42)); //hash: 1 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(19), TOY_VALUE_TO_INTEGER(42)); //hash: 2 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(8), TOY_VALUE_TO_INTEGER(42)); //hash: 3 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(10), TOY_VALUE_TO_INTEGER(42)); //hash: 4 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(3), TOY_VALUE_TO_INTEGER(42)); //hash: 5 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(28), TOY_VALUE_TO_INTEGER(42)); //hash: 6 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(1), TOY_VALUE_TO_INTEGER(42)); //hash: 7 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(6), TOY_VALUE_TO_INTEGER(42)); //hash: 8 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(93), TOY_VALUE_TO_INTEGER(42)); //hash: 9 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(85), TOY_VALUE_TO_INTEGER(42)); //hash: 10 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(9), TOY_VALUE_TO_INTEGER(42)); //hash: 11 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(11), TOY_VALUE_TO_INTEGER(42)); //hash: 12 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(22), TOY_VALUE_TO_INTEGER(42)); //hash: 13 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(13), TOY_VALUE_TO_INTEGER(42)); //hash: 14 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(17), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(43), TOY_VALUE_TO_INTEGER(42)); //hash: 16 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(4), TOY_VALUE_TO_INTEGER(42)); //hash: 17 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(5), TOY_VALUE_TO_INTEGER(42)); //hash: 18 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(7), TOY_VALUE_TO_INTEGER(42)); //hash: 19 + + //check the state + if (table == NULL || + table->capacity != 32 || + table->count != 20 || + + //effectively, check that each key was placed in the correct position after the expansion + TEST_ENTRY_STATE(0, 0, 42, 0) || + TEST_ENTRY_STATE(1, 35, 42, 0) || + TEST_ENTRY_STATE(2, 19, 42, 0) || + TEST_ENTRY_STATE(3, 8, 42, 0) || + TEST_ENTRY_STATE(4, 10, 42, 0) || + TEST_ENTRY_STATE(5, 3, 42, 0) || + TEST_ENTRY_STATE(6, 28, 42, 0) || + TEST_ENTRY_STATE(7, 1, 42, 0) || + TEST_ENTRY_STATE(8, 6, 42, 0) || + TEST_ENTRY_STATE(9, 93, 42, 0) || + TEST_ENTRY_STATE(10, 85, 42, 0) || + TEST_ENTRY_STATE(11, 9, 42, 0) || + TEST_ENTRY_STATE(12, 11, 42, 0) || + TEST_ENTRY_STATE(13, 22, 42, 0) || + TEST_ENTRY_STATE(14, 13, 42, 0) || + TEST_ENTRY_STATE(15, 17, 42, 0) || + TEST_ENTRY_STATE(16, 43, 42, 0) || + TEST_ENTRY_STATE(17, 4, 42, 0) || + TEST_ENTRY_STATE(18, 5, 42, 0) || + TEST_ENTRY_STATE(19, 7, 42, 0) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data after expansion, multiple inserts, no collisions\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, with collisions + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(0), TOY_VALUE_TO_INTEGER(42)); //hash: 0 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(35), TOY_VALUE_TO_INTEGER(42)); //hash: 1 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(19), TOY_VALUE_TO_INTEGER(42)); //hash: 2 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(8), TOY_VALUE_TO_INTEGER(42)); //hash: 3 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(10), TOY_VALUE_TO_INTEGER(42)); //hash: 4 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(3), TOY_VALUE_TO_INTEGER(42)); //hash: 5 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(28), TOY_VALUE_TO_INTEGER(42)); //hash: 6 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(1), TOY_VALUE_TO_INTEGER(42)); //hash: 7 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(6), TOY_VALUE_TO_INTEGER(42)); //hash: 8 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(93), TOY_VALUE_TO_INTEGER(42)); //hash: 9 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(85), TOY_VALUE_TO_INTEGER(42)); //hash: 10 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(9), TOY_VALUE_TO_INTEGER(42)); //hash: 11 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(11), TOY_VALUE_TO_INTEGER(42)); //hash: 12 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(22), TOY_VALUE_TO_INTEGER(42)); //hash: 13 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(13), TOY_VALUE_TO_INTEGER(42)); //hash: 14 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(17), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(43), TOY_VALUE_TO_INTEGER(42)); //hash: 16 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(4), TOY_VALUE_TO_INTEGER(42)); //hash: 17 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(5), TOY_VALUE_TO_INTEGER(42)); //hash: 18 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(7), TOY_VALUE_TO_INTEGER(42)); //hash: 19 + + //insert one more + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(70), TOY_VALUE_TO_INTEGER(42)); //hash: 15 + + //check the state + if (table == NULL || + table->capacity != 32 || + table->count != 21 || + + TEST_ENTRY_STATE(0, 0, 42, 0) || + TEST_ENTRY_STATE(1, 35, 42, 0) || + TEST_ENTRY_STATE(2, 19, 42, 0) || + TEST_ENTRY_STATE(3, 8, 42, 0) || + TEST_ENTRY_STATE(4, 10, 42, 0) || + TEST_ENTRY_STATE(5, 3, 42, 0) || + TEST_ENTRY_STATE(6, 28, 42, 0) || + TEST_ENTRY_STATE(7, 1, 42, 0) || + TEST_ENTRY_STATE(8, 6, 42, 0) || + TEST_ENTRY_STATE(9, 93, 42, 0) || + TEST_ENTRY_STATE(10, 85, 42, 0) || + TEST_ENTRY_STATE(11, 9, 42, 0) || + TEST_ENTRY_STATE(12, 11, 42, 0) || + TEST_ENTRY_STATE(13, 22, 42, 0) || + TEST_ENTRY_STATE(14, 13, 42, 0) || + TEST_ENTRY_STATE(15, 17, 42, 0) || + + TEST_ENTRY_STATE(16, 70, 42, 1) || //the collision + + TEST_ENTRY_STATE(17, 43, 42, 1) || + TEST_ENTRY_STATE(18, 4, 42, 1) || + TEST_ENTRY_STATE(19, 5, 42, 1) || + TEST_ENTRY_STATE(20, 7, 42, 1) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data after expansion, muiltiple inserts, with collisions\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //multiple inserts, with collisions, modulo wrap + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(123), TOY_VALUE_TO_INTEGER(42)); //hash: 20 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(42), TOY_VALUE_TO_INTEGER(42)); //hash: 21 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(132), TOY_VALUE_TO_INTEGER(42)); //hash: 22 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(34), TOY_VALUE_TO_INTEGER(42)); //hash: 23 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(2), TOY_VALUE_TO_INTEGER(42)); //hash: 24 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(32), TOY_VALUE_TO_INTEGER(42)); //hash: 25 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(21), TOY_VALUE_TO_INTEGER(42)); //hash: 26 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(44), TOY_VALUE_TO_INTEGER(42)); //hash: 27 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(104), TOY_VALUE_TO_INTEGER(42)); //hash: 28 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(15), TOY_VALUE_TO_INTEGER(42)); //hash: 29 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(57), TOY_VALUE_TO_INTEGER(42)); //hash: 30 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(33), TOY_VALUE_TO_INTEGER(42)); //hash: 31 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(0), TOY_VALUE_TO_INTEGER(42)); //hash: 32 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(35), TOY_VALUE_TO_INTEGER(42)); //hash: 33 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(19), TOY_VALUE_TO_INTEGER(42)); //hash: 34 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(8), TOY_VALUE_TO_INTEGER(42)); //hash: 35 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(10), TOY_VALUE_TO_INTEGER(42)); //hash: 36 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(3), TOY_VALUE_TO_INTEGER(42)); //hash: 37 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(28), TOY_VALUE_TO_INTEGER(42)); //hash: 38 + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(1), TOY_VALUE_TO_INTEGER(42)); //hash: 39 + + //insert one more + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(79), TOY_VALUE_TO_INTEGER(42)); //hash: 23 + + //check the state + if (table == NULL || + table->capacity != 32 || + table->count != 21 || + + TEST_ENTRY_STATE(20, 123, 42, 0) || + TEST_ENTRY_STATE(21, 42, 42, 0) || + TEST_ENTRY_STATE(22, 132, 42, 0) || + TEST_ENTRY_STATE(23, 34, 42, 0) || + + TEST_ENTRY_STATE(24, 79, 42, 1) || //the collision + + TEST_ENTRY_STATE(25, 2, 42, 1) || + TEST_ENTRY_STATE(26, 32, 42, 1) || + TEST_ENTRY_STATE(27, 21, 42, 1) || + TEST_ENTRY_STATE(28, 44, 42, 1) || + TEST_ENTRY_STATE(29, 104, 42, 1) || + TEST_ENTRY_STATE(30, 15, 42, 1) || + TEST_ENTRY_STATE(31, 57, 42, 1) || + TEST_ENTRY_STATE(0, 33, 42, 1) || + TEST_ENTRY_STATE(1, 0, 42, 1) || + TEST_ENTRY_STATE(2, 35, 42, 1) || + TEST_ENTRY_STATE(3, 19, 42, 1) || + TEST_ENTRY_STATE(4, 8, 42, 1) || + TEST_ENTRY_STATE(5, 10, 42, 1) || + TEST_ENTRY_STATE(6, 3, 42, 1) || + TEST_ENTRY_STATE(7, 28, 42, 1) || + TEST_ENTRY_STATE(8, 1, 42, 1) + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unrecognized state from table data after expansion, muiltiple inserts, with collisions, modulo wrap\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //lookup, with collisions, modulo wrap + { + //setup + Toy_Table* table = Toy_allocateTable(); + + //inserts + for (int i = 0; i < 20; i++) { //enough to expand + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(i), TOY_VALUE_TO_INTEGER(100 - i)); + } + + //lookup + Toy_Value result = Toy_lookupTable(&table, TOY_VALUE_TO_INTEGER(15)); + + //check the state + if (table == NULL || + table->capacity != 32 || + table->count != 20 || + + TOY_VALUE_IS_INTEGER(result) != true || + TOY_VALUE_AS_INTEGER(result) != 85 + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Bad result from table lookup after expansion, with collisions and modulo wrap\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + + //Skipped: multiple inserts, with collisions, modulo wrap, psl overlap + //Skipped: multiple inserts, with collisions, modulo wrap, psl overlap, psl shift + + //Note: since psl overlap and psl shift both work without expansion, I'm leaving these tests unimplemented due to exhaustion. + + return 0; +} + +int test_table_expansions_under_stress() { + //multiple expansions, find one value + { + //setup + Toy_Table* table = Toy_allocateTable(); + + int top = 300; + + //insert keys and values + for (int i = 0; i < 300; i++) { + Toy_insertTable(&table, TOY_VALUE_TO_INTEGER(i), TOY_VALUE_TO_INTEGER(top - i)); + } + + Toy_Value result = Toy_lookupTable(&table, TOY_VALUE_TO_INTEGER(265)); + + //check the state + if (table == NULL || + table->capacity != 512 || + table->count != 300 || + + TOY_VALUE_IS_INTEGER(result) != true || + TOY_VALUE_AS_INTEGER(result) != 35 + ) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Table expansions under stress failed\n" TOY_CC_RESET); + Toy_freeTable(table); + return -1; + } + + //free + Toy_freeTable(table); + } + return 0; } int main() { - //not finished yet - fprintf(stderr, TOY_CC_WARN "'Toy_Table' is not yet tested\n" TOY_CC_RESET); - return 0; - //run each test set, returning the total errors given int total = 0, res = 0; + //Note: there's some utility c programs in .notes called "hash_generator" that can help + { res = test_table_allocation(); if (res == 0) { @@ -23,5 +616,37 @@ int main() { total += res; } + { + res = test_table_simple_insert_lookup_and_remove(); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + + { + res = test_table_contents_no_expansion(); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + + { + res = test_table_contents_with_expansions(); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + + { + res = test_table_expansions_under_stress(); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + return total; }