Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions data_structure/fast_hash_map.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#pragma once

#include <array>
#include <bit>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <random>
#include <type_traits>

// Fixed-capacity hash map for integer keys.
// N must be a power of two. At most N distinct keys can be stored.
template <typename K, typename V, std::size_t N> struct HashMap {
static_assert(std::has_single_bit(N));
static_assert(std::is_integral_v<K>);
static_assert(sizeof(K) <= sizeof(std::uint64_t));

private:
std::array<K, N> keys;
std::array<V, N> values;
std::array<std::uint32_t, N> 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<std::uint64_t>(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(); }
};
27 changes: 27 additions & 0 deletions data_structure/fast_hash_map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Fast hash map
documentation_of: ./fast_hash_map.hpp
---

整数をキーとする固定容量の高速な連想配列.衝突は線形探索で解決する.ハッシュに使う乗数は実行時刻を seed として実行ごとに生成されるため,固定 seed を狙った衝突攻撃を受けにくい.

各スロットに世代番号を保持しており,`clear()` / `reset()` は現世代を更新するだけなので $O(1)$ で動作する.

## 使用方法

```cpp
// N は 2 の冪で,格納する相異なるキー数以上にする.
HashMap<long long, long long, 1 << 20> 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)
59 changes: 59 additions & 0 deletions data_structure/test/fast_hash_map.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#define PROBLEM "https://judge.yosupo.jp/problem/associative_array"
#include "../fast_hash_map.hpp"
#include <cassert>
#include <iostream>

HashMap<unsigned long long, unsigned long long, 1 << 21> 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<int, int, 1> 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<int, int, 8> 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);
}
Loading