libluna: Add HashTable
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
da1439126a
commit
d589834eb7
@ -25,6 +25,7 @@ set(FREESTANDING_SOURCES
|
||||
src/PathParser.cpp
|
||||
src/UBSAN.cpp
|
||||
src/Base64.cpp
|
||||
src/Hash.cpp
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
|
29
libluna/include/luna/Hash.h
Normal file
29
libluna/include/luna/Hash.h
Normal 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;
|
||||
}
|
||||
}
|
146
libluna/include/luna/HashTable.h
Normal file
146
libluna/include/luna/HashTable.h
Normal 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
14
libluna/src/Hash.cpp
Normal 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);
|
||||
}
|
@ -17,6 +17,7 @@ luna_test(libluna/TestVector.cpp TestVector)
|
||||
luna_test(libluna/TestBase64.cpp TestBase64)
|
||||
luna_test(libluna/TestUtf8.cpp TestUtf8)
|
||||
luna_test(libluna/TestFormat.cpp TestFormat)
|
||||
luna_test(libluna/TestHashTable.cpp TestHashTable)
|
||||
|
||||
luna_app(run-tests.cpp run-tests)
|
||||
endif()
|
||||
|
79
tests/libluna/TestHashTable.cpp
Normal file
79
tests/libluna/TestHashTable.cpp
Normal 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 {};
|
||||
}
|
@ -17,7 +17,7 @@ Result<int> luna_main(int argc, char** argv)
|
||||
|
||||
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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user