diff --git a/data_structure/fast_hash_map.hpp b/data_structure/fast_hash_map.hpp new file mode 100644 index 00000000..2dbc4484 --- /dev/null +++ b/data_structure/fast_hash_map.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// Fixed-capacity hash map for integer keys. +// N must be a power of two. At most N distinct keys can be stored. +template struct HashMap { + static_assert(std::has_single_bit(N)); + static_assert(std::is_integral_v); + static_assert(sizeof(K) <= sizeof(std::uint64_t)); + +private: + std::array keys; + std::array values; + std::array versions{}; + + std::uint32_t version = 1; + std::size_t count = 0; + std::uint64_t multiplier; + + static std::uint64_t make_multiplier() noexcept { + // Use a nondeterministic seed + std::mt19937_64 mt(std::chrono::steady_clock::now().time_since_epoch().count()); + return mt() | 1; + } + + std::size_t hash(K key) const noexcept { + if constexpr (N == 1) { + return 0; + } else { + constexpr int shift = 64 - std::countr_zero(N); + return (static_cast(key) * multiplier) >> shift; + } + } + +public: + HashMap() : multiplier(make_multiplier()) {} + + void set(K key, V value) noexcept { + std::size_t pos = hash(key); + for (std::size_t step = 0; step < N; ++step) { + if (versions[pos] != version) { + keys[pos] = key; + values[pos] = value; + versions[pos] = version; + assert(count < N); + ++count; + return; + } + if (keys[pos] == key) { + values[pos] = value; + return; + } + pos = (pos + 1) & (N - 1); + } + assert(false && "HashMap capacity exceeded"); + } + + V get(K key) const noexcept { + std::size_t pos = hash(key); + for (std::size_t step = 0; step < N; ++step) { + if (versions[pos] != version) return V{}; + if (keys[pos] == key) return values[pos]; + pos = (pos + 1) & (N - 1); + } + return V{}; + } + + std::size_t size() const noexcept { return count; } + bool empty() const noexcept { return count == 0; } + + void clear() noexcept { + ++version; + count = 0; + } + void reset() noexcept { clear(); } +}; diff --git a/data_structure/fast_hash_map.md b/data_structure/fast_hash_map.md new file mode 100644 index 00000000..61761341 --- /dev/null +++ b/data_structure/fast_hash_map.md @@ -0,0 +1,27 @@ +--- +title: Fast hash map +documentation_of: ./fast_hash_map.hpp +--- + +整数をキーとする固定容量の高速な連想配列.衝突は線形探索で解決する.ハッシュに使う乗数は実行時刻を seed として実行ごとに生成されるため,固定 seed を狙った衝突攻撃を受けにくい. + +各スロットに世代番号を保持しており,`clear()` / `reset()` は現世代を更新するだけなので $O(1)$ で動作する. + +## 使用方法 + +```cpp +// N は 2 の冪で,格納する相異なるキー数以上にする. +HashMap mp; + +mp.set(10, 20); // key 10 に value 20 を設定 +mp.get(10); // 20(存在しないキーに対しては V{}) +mp.size(); // 1 +mp.empty(); // false +mp.clear(); // 全要素を O(1) で削除 +``` + +キーは 64 bit 以下の整数型でなければならない.計算量は `set()` / `get()` が平均 $O(1)$,`clear()` / `reset()` / `size()` / `empty()` が $O(1)$.格納する相異なるキー数が `N` を超えないようにする必要がある. + +## 問題例 + +- [Library Checker: Associative Array](https://judge.yosupo.jp/problem/associative_array) diff --git a/data_structure/test/fast_hash_map.test.cpp b/data_structure/test/fast_hash_map.test.cpp new file mode 100644 index 00000000..8eb2e332 --- /dev/null +++ b/data_structure/test/fast_hash_map.test.cpp @@ -0,0 +1,59 @@ +#define PROBLEM "https://judge.yosupo.jp/problem/associative_array" +#include "../fast_hash_map.hpp" +#include +#include + +HashMap mp; + +int main() { + std::cin.tie(nullptr), std::ios::sync_with_stdio(false); + + int Q; + std::cin >> Q; + while (Q--) { + int type; + unsigned long long key; + std::cin >> type >> key; + if (type == 0) { + unsigned long long value; + std::cin >> value; + mp.set(key, value); + } else { + std::cout << mp.get(key) << '\n'; + } + } + + mp.clear(); + assert(mp.empty()); + assert(mp.size() == 0); + assert(mp.get(1) == 0); + + mp.set(1, 2); + mp.set(1, 3); + assert(mp.size() == 1); + assert(mp.get(1) == 3); + + mp.reset(); + assert(mp.empty()); + assert(mp.get(1) == 0); + + HashMap one; + assert(one.get(-1) == 0); + one.set(-1, 4); + assert(one.size() == 1); + assert(one.get(-1) == 4); + assert(one.get(0) == 0); + one.clear(); + one.set(0, 5); + assert(one.get(0) == 5); + + HashMap full; + for (int i = 0; i < 8; ++i) full.set(i, i + 1); + assert(full.size() == 8); + for (int i = 0; i < 8; ++i) assert(full.get(i) == i + 1); + assert(full.get(8) == 0); + full.clear(); + full.set(8, 9); + assert(full.size() == 1); + assert(full.get(8) == 9); +}