mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Add srb2::HashMap
This commit is contained in:
parent
8e7de22df5
commit
00d60fca4f
2 changed files with 667 additions and 0 deletions
|
|
@ -1,4 +1,5 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
hash_map.hpp
|
||||
memory.cpp
|
||||
memory.h
|
||||
spmc_queue.hpp
|
||||
|
|
|
|||
666
src/core/hash_map.hpp
Normal file
666
src/core/hash_map.hpp
Normal file
|
|
@ -0,0 +1,666 @@
|
|||
// DR. ROBOTNIK'S RING RACERS
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2025 by Ronald "Eidolon" Kinard
|
||||
// Copyright (C) 2025 by Kart Krew
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef SRB2_CORE_HASH_MAP_HPP
|
||||
#define SRB2_CORE_HASH_MAP_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
|
||||
namespace srb2
|
||||
{
|
||||
|
||||
template <typename K, typename V>
|
||||
class HashMap
|
||||
{
|
||||
struct Elem;
|
||||
|
||||
using Hasher = std::hash<K>;
|
||||
using KeyEqual = std::equal_to<K>;
|
||||
|
||||
public:
|
||||
using Entry = std::pair<K, V>;
|
||||
|
||||
class Iter
|
||||
{
|
||||
const HashMap* self_;
|
||||
uint32_t bucket_;
|
||||
Elem* cur_;
|
||||
|
||||
Iter(const HashMap* self, uint32_t bucket, Elem* cur)
|
||||
: self_(self)
|
||||
, bucket_(bucket)
|
||||
, cur_(cur)
|
||||
{}
|
||||
|
||||
friend class HashMap;
|
||||
friend class ConstIter;
|
||||
|
||||
public:
|
||||
Iter() : Iter(nullptr, 0, nullptr) {}
|
||||
Iter(const Iter&) = default;
|
||||
Iter(Iter&&) noexcept = default;
|
||||
~Iter() = default;
|
||||
Iter& operator=(const Iter&) = default;
|
||||
Iter& operator=(Iter&&) noexcept = default;
|
||||
|
||||
Entry& operator*() const noexcept { return cur_->entry; }
|
||||
Entry* operator->() const noexcept { return &cur_->entry; }
|
||||
bool operator==(const Iter& r) const noexcept
|
||||
{
|
||||
return self_ == r.self_ && bucket_ == r.bucket_ && cur_ == r.cur_;
|
||||
}
|
||||
bool operator!=(const Iter& r) const noexcept { return !(*this == r); }
|
||||
|
||||
Iter& operator++()
|
||||
{
|
||||
if (cur_ == nullptr)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (cur_->next == nullptr)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (bucket_ < self_->buckets_ - 1)
|
||||
{
|
||||
bucket_++;
|
||||
cur_ = self_->heads_[bucket_];
|
||||
}
|
||||
else
|
||||
{
|
||||
self_ = nullptr;
|
||||
bucket_ = 0;
|
||||
cur_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
} while (cur_ == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
cur_ = cur_->next;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iter operator++(int)
|
||||
{
|
||||
Iter itr = *this;
|
||||
++(*this);
|
||||
return itr;
|
||||
}
|
||||
};
|
||||
|
||||
class ConstIter
|
||||
{
|
||||
mutable Iter iter_;
|
||||
|
||||
friend class HashMap;
|
||||
public:
|
||||
ConstIter() : iter_() {}
|
||||
ConstIter(const ConstIter&) = default;
|
||||
ConstIter(ConstIter&&) noexcept = default;
|
||||
~ConstIter() = default;
|
||||
ConstIter& operator=(const ConstIter&) = default;
|
||||
ConstIter& operator=(ConstIter&&) noexcept = default;
|
||||
|
||||
ConstIter(const Iter& iter) : iter_(iter) {}
|
||||
|
||||
const Entry& operator*() const noexcept { return iter_.cur_->entry; }
|
||||
const Entry* operator->() const noexcept { return &iter_.cur_->entry; }
|
||||
bool operator==(const ConstIter& r) const noexcept { return iter_ == r.iter_; }
|
||||
bool operator!=(const ConstIter& r) const noexcept { return !(*this == r); }
|
||||
ConstIter& operator++() noexcept
|
||||
{
|
||||
++iter_;
|
||||
return *this;
|
||||
}
|
||||
ConstIter operator++(int) noexcept
|
||||
{
|
||||
ConstIter itr = *this;
|
||||
++*this;
|
||||
return itr;
|
||||
}
|
||||
};
|
||||
|
||||
// iter traits
|
||||
using key_type = K;
|
||||
using mapped_type = V;
|
||||
using value_type = Entry;
|
||||
using size_type = uint32_t;
|
||||
using difference_type = int64_t;
|
||||
using hasher = Hasher;
|
||||
using key_equal = KeyEqual;
|
||||
using reference = Entry&;
|
||||
using const_reference = const Entry&;
|
||||
using pointer = Entry*;
|
||||
using const_pointer = const Entry*;
|
||||
using iterator = Iter;
|
||||
using const_iterator = ConstIter;
|
||||
|
||||
private:
|
||||
struct Elem
|
||||
{
|
||||
Elem* prev;
|
||||
Elem* next;
|
||||
Entry entry;
|
||||
};
|
||||
|
||||
Elem** heads_ = nullptr;
|
||||
size_t size_ = 0;
|
||||
uint32_t buckets_;
|
||||
Hasher hasher_;
|
||||
KeyEqual key_equal_;
|
||||
|
||||
constexpr static uint32_t kDefaultBuckets = 16;
|
||||
|
||||
void init_buckets()
|
||||
{
|
||||
if (buckets_ == 0)
|
||||
{
|
||||
buckets_ = kDefaultBuckets;
|
||||
}
|
||||
|
||||
std::allocator<Elem*> allocator;
|
||||
heads_ = allocator.allocate(buckets_);
|
||||
for (uint32_t i = 0; i < buckets_; i++)
|
||||
{
|
||||
heads_[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void init_if_needed()
|
||||
{
|
||||
if (heads_ == nullptr)
|
||||
{
|
||||
init_buckets();
|
||||
}
|
||||
}
|
||||
|
||||
void rehash_if_needed(size_t target_size)
|
||||
{
|
||||
if (target_size >= buckets_)
|
||||
{
|
||||
rehash(std::max<size_t>(target_size, buckets_ * 2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
friend class Iter;
|
||||
friend class ConstIter;
|
||||
|
||||
public:
|
||||
HashMap() : heads_(nullptr), size_(0), buckets_(0), hasher_(), key_equal_()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
HashMap(uint32_t buckets)
|
||||
: heads_(nullptr)
|
||||
, size_(0)
|
||||
, buckets_(buckets)
|
||||
, hasher_()
|
||||
, key_equal_()
|
||||
{
|
||||
init_buckets();
|
||||
}
|
||||
|
||||
HashMap(const HashMap& r)
|
||||
{
|
||||
*this = r;
|
||||
}
|
||||
|
||||
HashMap(HashMap&& r) noexcept
|
||||
{
|
||||
*this = std::move(r);
|
||||
};
|
||||
|
||||
~HashMap()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
HashMap(std::initializer_list<Entry> list) : HashMap(list.size())
|
||||
{
|
||||
for (auto v : list)
|
||||
{
|
||||
insert(v);
|
||||
}
|
||||
}
|
||||
|
||||
HashMap& operator=(const HashMap& r)
|
||||
{
|
||||
clear();
|
||||
buckets_ = r.buckets_;
|
||||
if (buckets_ == 0)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
init_buckets();
|
||||
for (auto itr = r.begin(); itr != r.end(); itr++)
|
||||
{
|
||||
insert({itr->first, itr->second});
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
HashMap& operator=(HashMap&& r) noexcept
|
||||
{
|
||||
std::swap(buckets_, r.buckets_);
|
||||
std::swap(size_, r.size_);
|
||||
std::swap(heads_, r.heads_);
|
||||
std::swap(hasher_, r.hasher_);
|
||||
std::swap(key_equal_, r.key_equal_);
|
||||
return *this;
|
||||
};
|
||||
|
||||
constexpr bool empty() const noexcept { return size_ == 0; }
|
||||
constexpr size_t size() const noexcept { return size_; }
|
||||
constexpr uint32_t buckets() const noexcept { return buckets_; }
|
||||
|
||||
Iter begin() noexcept
|
||||
{
|
||||
if (size_ == 0)
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
Iter ret = end();
|
||||
for (uint32_t i = 0; i < buckets_; i++)
|
||||
{
|
||||
Elem* ptr = heads_[i];
|
||||
if (ptr != nullptr)
|
||||
{
|
||||
ret = Iter { this, i, ptr };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConstIter cbegin() const noexcept
|
||||
{
|
||||
ConstIter ret = end();
|
||||
for (uint32_t i = 0; i < buckets_; i++)
|
||||
{
|
||||
Elem* ptr = heads_[i];
|
||||
if (ptr != nullptr)
|
||||
{
|
||||
ret = ConstIter { Iter { this, i, ptr } };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConstIter begin() const noexcept { return cbegin(); }
|
||||
|
||||
constexpr Iter end() noexcept { return Iter(); }
|
||||
|
||||
constexpr ConstIter cend() const noexcept { return ConstIter(); }
|
||||
|
||||
constexpr ConstIter end() const noexcept { return cend(); }
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (heads_)
|
||||
{
|
||||
std::allocator<Elem> elem_allocator;
|
||||
for (uint32_t i = 0; i < buckets_; i++)
|
||||
{
|
||||
auto itr = heads_[i];
|
||||
while (itr != nullptr)
|
||||
{
|
||||
auto nextitr = itr->next;
|
||||
itr->~Elem();
|
||||
elem_allocator.deallocate(itr, 1);
|
||||
itr = nextitr;
|
||||
}
|
||||
}
|
||||
|
||||
std::allocator<Elem*> buckets_allocator;
|
||||
buckets_allocator.deallocate(heads_, buckets_);
|
||||
}
|
||||
heads_ = nullptr;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
Iter find(const K& key)
|
||||
{
|
||||
if (buckets_ == 0 || heads_ == nullptr)
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
uint32_t bucket = (hasher_)(key) % buckets_;
|
||||
for (Elem* p = heads_[bucket]; p != nullptr; p = p->next)
|
||||
{
|
||||
if ((key_equal_)(p->entry.first, key))
|
||||
{
|
||||
return Iter { this, bucket, p };
|
||||
}
|
||||
};
|
||||
return end();
|
||||
}
|
||||
|
||||
ConstIter find(const K& key) const
|
||||
{
|
||||
Iter iter = const_cast<HashMap*>(this)->find(key);
|
||||
return ConstIter { iter };
|
||||
}
|
||||
|
||||
std::pair<Iter, bool> insert(const Entry& value)
|
||||
{
|
||||
Entry copy = value;
|
||||
return insert(std::move(copy));
|
||||
}
|
||||
|
||||
void rehash(uint32_t count)
|
||||
{
|
||||
count = size_ > count ? size_ : count;
|
||||
HashMap rehashed { count };
|
||||
for (Iter itr = begin(); itr != end();)
|
||||
{
|
||||
Iter itrcopy = itr;
|
||||
++itr;
|
||||
|
||||
uint32_t oldbucket = itrcopy.bucket_;
|
||||
if (heads_[oldbucket] == itrcopy.cur_)
|
||||
{
|
||||
heads_[oldbucket] = nullptr;
|
||||
}
|
||||
itrcopy.self_ = &rehashed;
|
||||
itrcopy.bucket_ = (rehashed.hasher_)(itrcopy.cur_->entry.first) % count;
|
||||
Elem* p = rehashed.heads_[itrcopy.bucket_];
|
||||
if (p == nullptr)
|
||||
{
|
||||
p = rehashed.heads_[itrcopy.bucket_] = itrcopy.cur_;
|
||||
size_ -= 1;
|
||||
rehashed.size_ += 1;
|
||||
|
||||
if (p->next)
|
||||
{
|
||||
p->next->prev = nullptr;
|
||||
}
|
||||
if (p->prev)
|
||||
{
|
||||
p->prev->next = nullptr;
|
||||
}
|
||||
p->next = nullptr;
|
||||
p->prev = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; p != nullptr; p = p->next)
|
||||
{
|
||||
if (p->next != nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
p->next = itrcopy.cur_;
|
||||
size_ -= 1;
|
||||
rehashed.size_ += 1;
|
||||
p->next->prev = p;
|
||||
p->next->next = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (heads_)
|
||||
{
|
||||
std::allocator<Elem*> buckets_allocator;
|
||||
buckets_allocator.deallocate(heads_, buckets_);
|
||||
heads_ = nullptr;
|
||||
}
|
||||
|
||||
*this = std::move(rehashed);
|
||||
}
|
||||
|
||||
std::pair<Iter, bool> insert(Entry&& value)
|
||||
{
|
||||
std::pair<Iter, bool> ret { end(), false };
|
||||
std::allocator<Elem> allocator;
|
||||
|
||||
init_if_needed();
|
||||
|
||||
rehash_if_needed(size_ + 1);
|
||||
|
||||
uint32_t bucket = (hasher_)(value.first) % buckets_;
|
||||
Elem* p = heads_[bucket];
|
||||
if (p == nullptr)
|
||||
{
|
||||
// Make a new slot
|
||||
ret.second = true;
|
||||
Elem* newslot = allocator.allocate(1);
|
||||
newslot->prev = nullptr;
|
||||
newslot->next = nullptr;
|
||||
heads_[bucket] = newslot;
|
||||
new (&newslot->entry) Entry { std::move(value) };
|
||||
size_++;
|
||||
ret.first = Iter { this, bucket, newslot };
|
||||
return ret;
|
||||
}
|
||||
|
||||
for(; p != nullptr;)
|
||||
{
|
||||
if ((key_equal_)(p->entry.first, value.first))
|
||||
{
|
||||
ret.second = false;
|
||||
ret.first = Iter { this, bucket, p };
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (p->next == nullptr)
|
||||
{
|
||||
// Make a new slot
|
||||
ret.second = true;
|
||||
Elem* newslot = allocator.allocate(1);
|
||||
newslot->prev = p;
|
||||
newslot->next = nullptr;
|
||||
p->next = newslot;
|
||||
new (&newslot->entry) Entry { std::move(value) };
|
||||
size_++;
|
||||
ret.first = Iter { this, bucket, newslot };
|
||||
return ret;
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
std::pair<Iter, bool> insert_or_assign(const K& key, M&& value)
|
||||
{
|
||||
K kcopy = key;
|
||||
return insert_or_assign(std::move(kcopy), std::forward<M>(value));
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
std::pair<Iter, bool> insert_or_assign(K&& key, M&& value)
|
||||
{
|
||||
std::pair<Iter, bool> ret { find(key), false };
|
||||
if (ret.first != end())
|
||||
{
|
||||
ret.first->second = std::forward<M>(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = insert({ std::move(key), std::forward<M>(value) });
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
std::pair<Iter, bool> emplace(Args&&... args)
|
||||
{
|
||||
Entry entry { std::forward<Args>(args)... };
|
||||
return insert(std::move(entry));
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
std::pair<Iter, bool> try_emplace(const K& key, Args&&... args)
|
||||
{
|
||||
Iter itr = find(key);
|
||||
if (itr != end())
|
||||
{
|
||||
return { itr, false };
|
||||
}
|
||||
|
||||
return insert({ key, V(std::forward<Args>(args)...) });
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
std::pair<Iter, bool> try_emplace(K&& key, Args... args)
|
||||
{
|
||||
std::pair<Iter, bool> ret { end(), false };
|
||||
return insert({ std::move(key), V(std::forward<Args>(args)...)});
|
||||
}
|
||||
|
||||
V& operator[](const K& key)
|
||||
{
|
||||
K copy { key };
|
||||
return this->operator[](std::move(copy));
|
||||
}
|
||||
|
||||
V& operator[](K&& key)
|
||||
{
|
||||
std::allocator<Elem> allocator;
|
||||
init_if_needed();
|
||||
|
||||
rehash_if_needed(size_ + 1);
|
||||
|
||||
uint32_t bucket = (hasher_)(key) % buckets_;
|
||||
Elem* p = heads_[bucket];
|
||||
if (p == nullptr)
|
||||
{
|
||||
// Make a new slot
|
||||
Elem* newslot = allocator.allocate(1);
|
||||
newslot->prev = nullptr;
|
||||
newslot->next = nullptr;
|
||||
heads_[bucket] = newslot;
|
||||
new (&newslot->entry) Entry { std::move(key), {} };
|
||||
size_++;
|
||||
return newslot->entry.second;
|
||||
}
|
||||
for (; p != nullptr;)
|
||||
{
|
||||
if ((key_equal_)(p->entry.first, key))
|
||||
{
|
||||
return p->entry.second;
|
||||
}
|
||||
|
||||
if (p->next == nullptr)
|
||||
{
|
||||
// Make a new slot
|
||||
Elem* newslot = allocator.allocate(1);
|
||||
newslot->prev = p;
|
||||
newslot->next = nullptr;
|
||||
p->next = newslot;
|
||||
new (&newslot->entry) Entry { std::move(key), {} };
|
||||
size_++;
|
||||
return newslot->entry.second;
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
return end()->second;
|
||||
}
|
||||
|
||||
V& at(const K& key)
|
||||
{
|
||||
auto itr = find(key);
|
||||
if (itr == end())
|
||||
{
|
||||
throw std::out_of_range("key not found");
|
||||
}
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
const V& at(const K& key) const
|
||||
{
|
||||
auto itr = find(key);
|
||||
if (itr == cend())
|
||||
{
|
||||
throw std::out_of_range("key not found");
|
||||
}
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
Iter erase(Iter pos)
|
||||
{
|
||||
Iter ret = pos;
|
||||
if (pos == end())
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
std::allocator<Elem> allocator;
|
||||
uint32_t bucket = (hasher_)(pos.cur_->entry.first) % buckets_;
|
||||
Elem* p = heads_[bucket];
|
||||
for (; p != nullptr;)
|
||||
{
|
||||
if ((key_equal_)(p->entry.first, pos.cur_->entry.first))
|
||||
{
|
||||
// found it; remove, return next iter
|
||||
++ret;
|
||||
if (p->next != nullptr)
|
||||
{
|
||||
p->next->prev = p->prev;
|
||||
}
|
||||
if (p->prev != nullptr)
|
||||
{
|
||||
p->prev->next = p->next;
|
||||
}
|
||||
if (p == heads_[bucket])
|
||||
{
|
||||
heads_[bucket] = p->next;
|
||||
}
|
||||
p->~Elem();
|
||||
allocator.deallocate(p, 1);
|
||||
size_ -= 1;
|
||||
return ret;
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Iter erase(ConstIter pos)
|
||||
{
|
||||
return erase(pos.iter_);
|
||||
}
|
||||
|
||||
uint32_t erase(const K& key)
|
||||
{
|
||||
Iter pos = find(key);
|
||||
if (pos != end())
|
||||
{
|
||||
erase(pos);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace srb2
|
||||
|
||||
#endif // SRB2_CORE_HASH_MAP_HPP
|
||||
Loading…
Add table
Reference in a new issue