libluna: Add HashTable
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
apio 2023-06-15 15:50:04 +02:00
parent da1439126a
commit d589834eb7
Signed by: apio
GPG Key ID: B8A7D06E42258954
7 changed files with 271 additions and 1 deletions

View File

@ -25,6 +25,7 @@ set(FREESTANDING_SOURCES
src/PathParser.cpp src/PathParser.cpp
src/UBSAN.cpp src/UBSAN.cpp
src/Base64.cpp src/Base64.cpp
src/Hash.cpp
) )
set(SOURCES set(SOURCES

View File

@ -0,0 +1,29 @@
#pragma once
#include <luna/CString.h>
#include <luna/Types.h>
u64 hash_memory(const void* mem, usize size, u64 salt);
template <typename T> u64 hash(const T& value, u64 salt)
{
return hash_memory(&value, sizeof(value), salt);
}
template <> u64 hash(const char* const& value, u64 salt);
template <typename T> static void swap(T* a, T* b)
{
char* x = (char*)a;
char* y = (char*)b;
usize size = sizeof(T);
while (size--)
{
char t = *x;
*x = *y;
*y = t;
x += 1;
y += 1;
}
}

View File

@ -0,0 +1,146 @@
#pragma once
#include <luna/Hash.h>
#include <luna/Heap.h>
#include <luna/Option.h>
template <typename T> class HashTable
{
static constexpr usize GROW_RATE = 2;
static constexpr usize GROW_FACTOR = 16;
public:
Result<bool> try_set(const T& value)
{
T copy { value };
return try_set(move(copy));
}
Result<bool> try_set(T&& value)
{
if (should_grow()) TRY(rehash(m_capacity + GROW_FACTOR));
u64 index = hash(value, m_salt) % m_capacity;
while (true)
{
auto& bucket = m_buckets[index];
if (bucket.has_value())
{
if (*bucket == value) return false;
index++;
continue;
}
bucket = { move(value) };
m_size++;
break;
}
return true;
}
T* try_find(const T& value)
{
if (!m_size) return nullptr;
check(m_capacity);
const u64 index = hash(value, m_salt) % m_capacity;
usize i = index;
do {
auto& bucket = m_buckets[index];
if (bucket.has_value())
{
if (*bucket == value) return bucket.value_ptr();
i++;
}
return nullptr;
} while (i != index);
return nullptr;
}
bool try_remove(const T& value)
{
if (!m_size) return false;
check(m_capacity);
const u64 index = hash(value, m_salt) % m_capacity;
usize i = index;
do {
auto& bucket = m_buckets[index];
if (bucket.has_value())
{
if (*bucket == value)
{
bucket = {};
m_size--;
if (i != index) rehash(m_capacity);
return true;
}
i++;
}
return false;
} while (i != index);
return false;
}
void clear()
{
for (usize i = 0; i < m_capacity; i++) m_buckets[i].~Option();
free_impl(m_buckets);
m_capacity = m_size = 0;
}
~HashTable()
{
clear();
}
private:
bool should_grow()
{
return (m_capacity == 0) || ((m_size * GROW_RATE) >= m_capacity);
}
Result<void> rehash(usize new_capacity)
{
HashTable<T> new_table;
TRY(new_table.initialize(new_capacity));
if (m_capacity != 0)
{
for (usize i = 0; i < m_capacity; i++)
{
auto& opt = m_buckets[i];
if (opt.has_value())
{
auto value = opt.release_value();
TRY(new_table.try_set(move(value)));
}
}
}
swap(this, &new_table);
return {};
}
Result<void> initialize(usize initial_capacity)
{
check(m_buckets == nullptr);
m_capacity = initial_capacity;
m_buckets = (Option<T>*)TRY(calloc_impl(initial_capacity, sizeof(Option<T>), false));
return {};
}
Option<T>* m_buckets { nullptr };
usize m_capacity { 0 };
usize m_size { 0 };
// FIXME: Randomize this to protect against hash table attacks.
u64 m_salt { 0 };
};

14
libluna/src/Hash.cpp Normal file
View File

@ -0,0 +1,14 @@
#include <luna/Hash.h>
u64 hash_memory(const void* mem, usize size, u64 salt)
{
const char* p = (const char*)mem;
u64 h = salt;
while (--size) h = h * 101 + (u64)*p++;
return h;
}
template <> u64 hash(const char* const& value, u64 salt)
{
return hash_memory(value, strlen(value), salt);
}

View File

@ -17,6 +17,7 @@ luna_test(libluna/TestVector.cpp TestVector)
luna_test(libluna/TestBase64.cpp TestBase64) luna_test(libluna/TestBase64.cpp TestBase64)
luna_test(libluna/TestUtf8.cpp TestUtf8) luna_test(libluna/TestUtf8.cpp TestUtf8)
luna_test(libluna/TestFormat.cpp TestFormat) luna_test(libluna/TestFormat.cpp TestFormat)
luna_test(libluna/TestHashTable.cpp TestHashTable)
luna_app(run-tests.cpp run-tests) luna_app(run-tests.cpp run-tests)
endif() endif()

View File

@ -0,0 +1,79 @@
#include <luna/HashTable.h>
#include <os/File.h>
#include <test.h>
struct TwoInts
{
int a;
int b;
bool operator==(TwoInts other) const
{
return other.a == a;
}
};
template <> u64 hash(const TwoInts& value, u64 salt)
{
return hash(value.a, salt);
}
TestResult test_empty_hash_table()
{
HashTable<int> table;
validate(table.try_find(0) == nullptr);
test_success;
}
TestResult test_hash_table_find()
{
HashTable<int> table;
validate(TRY(table.try_set(0)));
validate(table.try_find(0));
validate(table.try_find(1) == nullptr);
test_success;
}
TestResult test_hash_table_remove()
{
HashTable<int> table;
validate(TRY(table.try_set(0)));
validate(table.try_find(0));
validate(table.try_remove(0));
validate(table.try_find(0) == nullptr);
test_success;
}
TestResult test_hash_table_duplicates()
{
HashTable<TwoInts> table;
validate(TRY(table.try_set(TwoInts { 1, 5 })));
validate(!TRY(table.try_set(TwoInts { 1, 3 })));
validate(table.try_find(TwoInts { 1, 0 })->b == 5);
test_success;
}
Result<void> test_main()
{
test_prelude;
run_test(test_empty_hash_table);
run_test(test_hash_table_find);
run_test(test_hash_table_remove);
run_test(test_hash_table_duplicates);
return {};
}

View File

@ -17,7 +17,7 @@ Result<int> luna_main(int argc, char** argv)
auto dir = TRY(os::Directory::open(test_dir)); auto dir = TRY(os::Directory::open(test_dir));
auto files = TRY(dir->list(os::Directory::Filter::Hidden)); auto files = TRY(dir->list_names(os::Directory::Filter::Hidden));
for (const auto& program : files) for (const auto& program : files)
{ {