2017-03-06 20:47:26 +01:00
|
|
|
#ifndef hash_map_hh_INCLUDED
|
|
|
|
#define hash_map_hh_INCLUDED
|
|
|
|
|
|
|
|
#include "hash.hh"
|
|
|
|
#include "memory.hh"
|
|
|
|
#include "vector.hh"
|
|
|
|
|
|
|
|
namespace Kakoune
|
|
|
|
{
|
|
|
|
|
|
|
|
template<MemoryDomain domain>
|
|
|
|
struct HashIndex
|
|
|
|
{
|
|
|
|
struct Entry
|
|
|
|
{
|
|
|
|
size_t hash;
|
|
|
|
int index;
|
|
|
|
};
|
|
|
|
|
2017-03-07 16:48:04 +01:00
|
|
|
void resize(size_t new_size)
|
2017-03-06 20:47:26 +01:00
|
|
|
{
|
2017-03-07 16:48:04 +01:00
|
|
|
kak_assert(new_size > m_entries.size());
|
2017-03-06 20:47:26 +01:00
|
|
|
Vector<Entry, domain> old_entries = std::move(m_entries);
|
2017-03-07 16:48:04 +01:00
|
|
|
m_entries.resize(new_size, {0,-1});
|
2017-03-06 20:47:26 +01:00
|
|
|
for (auto& entry : old_entries)
|
|
|
|
{
|
|
|
|
if (entry.index >= 0)
|
|
|
|
add(entry.hash, entry.index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-07 16:48:04 +01:00
|
|
|
void reserve(size_t count)
|
2017-03-06 20:47:26 +01:00
|
|
|
{
|
2017-03-07 16:48:04 +01:00
|
|
|
constexpr float max_fill_rate = 0.5f;
|
|
|
|
const size_t min_size = (size_t)(count / max_fill_rate) + 1;
|
|
|
|
size_t new_size = m_entries.empty() ? 4 : m_entries.size();
|
|
|
|
while (new_size < min_size)
|
|
|
|
new_size *= 2;
|
|
|
|
if (new_size > m_entries.size())
|
|
|
|
resize(new_size);
|
|
|
|
}
|
2017-03-06 20:47:26 +01:00
|
|
|
|
2017-03-07 16:48:04 +01:00
|
|
|
void add(size_t hash, int index)
|
|
|
|
{
|
2017-03-06 20:47:26 +01:00
|
|
|
Entry entry{hash, index};
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
auto target_slot = compute_slot(entry.hash);
|
|
|
|
for (auto slot = target_slot; slot < m_entries.size(); ++slot)
|
|
|
|
{
|
|
|
|
if (m_entries[slot].index == -1)
|
|
|
|
{
|
|
|
|
m_entries[slot] = entry;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Robin hood hashing
|
|
|
|
auto candidate_slot = compute_slot(m_entries[slot].hash);
|
|
|
|
if (target_slot < candidate_slot)
|
|
|
|
{
|
|
|
|
std::swap(m_entries[slot], entry);
|
|
|
|
target_slot = candidate_slot;
|
|
|
|
}
|
|
|
|
}
|
2017-03-07 16:48:04 +01:00
|
|
|
// no free entries found, resize, try again
|
|
|
|
resize(m_entries.size() * 2);
|
2017-03-06 20:47:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void remove(size_t hash, int index)
|
|
|
|
{
|
|
|
|
for (auto slot = compute_slot(hash); slot < m_entries.size(); ++slot)
|
|
|
|
{
|
|
|
|
kak_assert(m_entries[slot].index >= 0);
|
|
|
|
if (m_entries[slot].index == index)
|
|
|
|
{
|
|
|
|
m_entries[slot].index = -1;
|
|
|
|
// Recompact following entries
|
|
|
|
for (auto next = slot+1; next < m_entries.size(); ++next)
|
|
|
|
{
|
|
|
|
if (m_entries[next].index == -1 or
|
|
|
|
compute_slot(m_entries[next].hash) == next)
|
|
|
|
break;
|
|
|
|
kak_assert(compute_slot(m_entries[next].hash) < next);
|
|
|
|
std::swap(m_entries[next-1], m_entries[next]);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ordered_fix_entries(int index)
|
|
|
|
{
|
|
|
|
for (auto& entry : m_entries)
|
|
|
|
{
|
|
|
|
if (entry.index >= index)
|
|
|
|
--entry.index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void unordered_fix_entries(size_t hash, int old_index, int new_index)
|
|
|
|
{
|
|
|
|
for (auto slot = compute_slot(hash); slot < m_entries.size(); ++slot)
|
|
|
|
{
|
|
|
|
if (m_entries[slot].index == old_index)
|
|
|
|
{
|
|
|
|
m_entries[slot].index = new_index;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
kak_assert(false); // entry not found ?!
|
|
|
|
}
|
|
|
|
|
|
|
|
const Entry& operator[](size_t index) const { return m_entries[index]; }
|
|
|
|
size_t size() const { return m_entries.size(); }
|
|
|
|
size_t compute_slot(size_t hash) const
|
|
|
|
{
|
|
|
|
// We assume entries.size() is power of 2
|
2017-03-07 16:48:04 +01:00
|
|
|
return hash & (m_entries.size()-1);
|
2017-03-06 20:47:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void clear() { m_entries.clear(); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
Vector<Entry, domain> m_entries;
|
|
|
|
};
|
|
|
|
|
2017-03-07 01:30:54 +01:00
|
|
|
template<typename Key, typename Value>
|
|
|
|
struct HashItem
|
|
|
|
{
|
|
|
|
Key key;
|
|
|
|
Value value;
|
|
|
|
};
|
|
|
|
|
2017-03-06 20:47:26 +01:00
|
|
|
template<typename Key, typename Value, MemoryDomain domain = MemoryDomain::Undefined>
|
|
|
|
struct HashMap
|
|
|
|
{
|
2017-03-07 01:30:54 +01:00
|
|
|
using Item = HashItem<Key, Value>;
|
2017-03-06 20:47:26 +01:00
|
|
|
|
|
|
|
HashMap() = default;
|
|
|
|
|
|
|
|
HashMap(std::initializer_list<Item> val) : m_items{val}
|
|
|
|
{
|
2017-03-07 16:48:04 +01:00
|
|
|
m_index.reserve(val.size());
|
2017-03-06 20:47:26 +01:00
|
|
|
for (int i = 0; i < m_items.size(); ++i)
|
|
|
|
m_index.add(hash_value(m_items[i].key), i);
|
|
|
|
}
|
|
|
|
|
|
|
|
Value& insert(Item item)
|
|
|
|
{
|
2017-03-07 16:48:04 +01:00
|
|
|
m_index.reserve(m_items.size()+1);
|
2017-03-06 20:47:26 +01:00
|
|
|
m_index.add(hash_value(item.key), (int)m_items.size());
|
|
|
|
m_items.push_back(std::move(item));
|
|
|
|
return m_items.back().value;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename KeyType>
|
|
|
|
using EnableIfHashCompatible = typename std::enable_if<
|
|
|
|
HashCompatible<Key, typename std::decay<KeyType>::type>::value
|
|
|
|
>::type;
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
int find_index(const KeyType& key, size_t hash) const
|
|
|
|
{
|
|
|
|
for (auto slot = m_index.compute_slot(hash); slot < m_index.size(); ++slot)
|
|
|
|
{
|
|
|
|
auto& entry = m_index[slot];
|
|
|
|
if (entry.index == -1)
|
|
|
|
return -1;
|
|
|
|
if (entry.hash == hash and m_items[entry.index].key == key)
|
|
|
|
return entry.index;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
int find_index(const KeyType& key) const { return find_index(key, hash_value(key)); }
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
bool contains(const KeyType& key) const { return find_index(key) >= 0; }
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
Value& operator[](KeyType&& key)
|
|
|
|
{
|
|
|
|
const auto hash = hash_value(key);
|
|
|
|
auto index = find_index(key, hash);
|
|
|
|
if (index >= 0)
|
|
|
|
return m_items[index].value;
|
|
|
|
|
2017-03-07 16:48:04 +01:00
|
|
|
m_index.reserve(m_items.size()+1);
|
2017-03-06 20:47:26 +01:00
|
|
|
m_index.add(hash, (int)m_items.size());
|
|
|
|
m_items.push_back({Key{std::forward<KeyType>(key)}, {}});
|
|
|
|
return m_items.back().value;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
void remove(const KeyType& key)
|
|
|
|
{
|
|
|
|
const auto hash = hash_value(key);
|
|
|
|
int index = find_index(key, hash);
|
|
|
|
if (index >= 0)
|
|
|
|
{
|
|
|
|
m_items.erase(m_items.begin() + index);
|
|
|
|
m_index.remove(hash, index);
|
|
|
|
m_index.ordered_fix_entries(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
void unordered_remove(const KeyType& key)
|
|
|
|
{
|
|
|
|
const auto hash = hash_value(key);
|
|
|
|
int index = find_index(key, hash);
|
|
|
|
if (index >= 0)
|
|
|
|
{
|
|
|
|
std::swap(m_items[index], m_items.back());
|
|
|
|
m_items.pop_back();
|
|
|
|
m_index.remove(hash, index);
|
|
|
|
if (index != m_items.size())
|
|
|
|
m_index.unordered_fix_entries(hash_value(m_items[index].key), m_items.size(), index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void erase(const Key& key) { unordered_remove(key); }
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
void remove_all(const KeyType& key)
|
|
|
|
{
|
|
|
|
const auto hash = hash_value(key);
|
|
|
|
for (int index = find_index(key, hash); index >= 0;
|
|
|
|
index = find_index(key, hash))
|
|
|
|
{
|
|
|
|
m_items.erase(m_items.begin() + index);
|
|
|
|
m_index.remove(hash, index);
|
|
|
|
m_index.ordered_fix_entries(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
using iterator = typename Vector<Item, domain>::iterator;
|
|
|
|
iterator begin() { return m_items.begin(); }
|
|
|
|
iterator end() { return m_items.end(); }
|
|
|
|
|
|
|
|
using const_iterator = typename Vector<Item, domain>::const_iterator;
|
|
|
|
const_iterator begin() const { return m_items.begin(); }
|
|
|
|
const_iterator end() const { return m_items.end(); }
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
iterator find(const KeyType& key)
|
|
|
|
{
|
|
|
|
auto index = find_index(key);
|
|
|
|
return index >= 0 ? begin() + index : end();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename KeyType, typename = EnableIfHashCompatible<KeyType>>
|
|
|
|
const_iterator find(const KeyType& key) const
|
|
|
|
{
|
|
|
|
return const_cast<HashMap*>(this)->find(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear() { m_items.clear(); m_index.clear(); }
|
|
|
|
|
|
|
|
size_t size() const { return m_items.size(); }
|
|
|
|
bool empty() const { return m_items.empty(); }
|
|
|
|
void reserve(size_t size)
|
|
|
|
{
|
|
|
|
m_items.reserve(size);
|
2017-03-07 16:48:04 +01:00
|
|
|
m_index.reserve(size);
|
2017-03-06 20:47:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Equality is taking the order of insertion into account
|
|
|
|
template<MemoryDomain otherDomain>
|
|
|
|
bool operator==(const HashMap<Key, Value, otherDomain>& other) const
|
|
|
|
{
|
|
|
|
return size() == other.size() and
|
|
|
|
std::equal(begin(), end(), other.begin(),
|
|
|
|
[](const Item& lhs, const Item& rhs) {
|
|
|
|
return lhs.key == rhs.key and lhs.value == rhs.value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
template<MemoryDomain otherDomain>
|
|
|
|
bool operator!=(const HashMap<Key, Value, otherDomain>& other) const
|
|
|
|
{
|
|
|
|
return not (*this == other);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Vector<Item, domain> m_items;
|
|
|
|
HashIndex<domain> m_index;
|
|
|
|
};
|
|
|
|
|
|
|
|
void profile_hash_maps();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // hash_map_hh_INCLUDED
|