Skip to content

Commit 8838aef

Browse files
committed
Add hash table -- the core of all unordered_* data structures
1 parent 889faa6 commit 8838aef

3 files changed

Lines changed: 278 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mystd is a stl-like library.
1717
- [x] index and array -> my::arraybased::list
1818
- [x] pointer and heap memory -> my::heapbased::list
1919
- [x] stack (adapter for deque, list, vector)
20-
- [q] queue (adapter for deque, list)
20+
- [x] queue (adapter for deque, list)
2121
- [x] deque
2222
- [x] on C-array (cyclic buffer)
2323
- [x] list of fixed blocks -> std::deque

include/mystd/hashtable.hpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#pragma once
2+
3+
#include <functional>
4+
#include <memory>
5+
#include <utility>
6+
#include <vector>
7+
8+
namespace my {
9+
10+
/// @brief The policy for map/set multimap/multiset.
11+
enum class InsertPolicy { UniqueKeys, AllowDuplicates };
12+
13+
/// @brief The core of others hash tables based data structures.
14+
/// @tparam Value
15+
/// @tparam Key
16+
/// @tparam KeyOfValue -- rule how to extract Key
17+
/// @tparam Hash -- rule how to compute hash of Key
18+
/// @tparam KeyEqual -- rule how to equal to Key
19+
/// @tparam Policy -- rule if Key is unique or not
20+
template <class Value, class Key, class KeyOfValue, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>,
21+
InsertPolicy Policy = InsertPolicy::UniqueKeys, class Allocator = std::allocator<Value>>
22+
class hashtable {
23+
public:
24+
using key_type = Key;
25+
using value_type = Value;
26+
using reference = Value&;
27+
using const_reference = const Value&;
28+
using size_type = std::size_t;
29+
30+
static constexpr float max_load_factor = 0.75f;
31+
static constexpr size_type reallocation_factor = 2;
32+
33+
private:
34+
struct Node {
35+
Value value;
36+
size_type hash;
37+
Node* next;
38+
39+
Node(const_reference v, size_type h, Node* n = nullptr) : value(v), hash(h), next(n) {}
40+
};
41+
42+
using node_allocator_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Node>;
43+
44+
std::vector<Node*> buckets;
45+
size_type size_ = 0;
46+
Hash hasher;
47+
KeyEqual equal;
48+
KeyOfValue key_of_value;
49+
node_allocator_type alloc;
50+
51+
size_type get_index_(const key_type& k, size_type bucket_count) const { return hasher(k) % bucket_count; }
52+
53+
void rehash_() {
54+
if (load_factor() > max_load_factor) {
55+
rehash(buckets.size() * reallocation_factor);
56+
}
57+
}
58+
59+
public:
60+
template <bool IsConst>
61+
class iterator_basic {
62+
public:
63+
using iterator_category = std::forward_iterator_tag;
64+
using value_type = std::conditional_t<IsConst, const Value, Value>;
65+
using pointer = std::conditional_t<IsConst, const Value*, Value*>;
66+
using reference = std::conditional_t<IsConst, const Value&, Value&>;
67+
using difference_type = std::ptrdiff_t;
68+
using buckets_pointer = std::conditional_t<IsConst, const std::vector<Node*>*, std::vector<Node*>*>;
69+
70+
private:
71+
Node* node;
72+
buckets_pointer buckets;
73+
size_type bucket_idx;
74+
75+
void skip_empty() {
76+
while (!node && bucket_idx + 1 < buckets->size()) {
77+
node = (*buckets)[++bucket_idx];
78+
}
79+
}
80+
81+
public:
82+
iterator_basic(Node* n, std::vector<Node*>* b, size_type i) : node(n), buckets(b), bucket_idx(i) {}
83+
84+
reference operator*() const { return node->value; }
85+
86+
pointer operator->() const { return &node->value; }
87+
88+
iterator_basic& operator++() {
89+
if (node) {
90+
node = node->next;
91+
if (!node) {
92+
skip_empty();
93+
if (!node) {
94+
bucket_idx = buckets->size();
95+
}
96+
}
97+
}
98+
return *this;
99+
}
100+
101+
bool operator==(const iterator_basic& other) const {
102+
return node == other.node && bucket_idx == other.bucket_idx && buckets == other.buckets;
103+
}
104+
bool operator!=(const iterator_basic& other) const { return !(*this == other); }
105+
};
106+
107+
using iterator = iterator_basic<false>;
108+
using const_iterator = iterator_basic<true>;
109+
110+
explicit hashtable(size_type bucket_count = 8) : buckets(bucket_count, nullptr) {}
111+
112+
~hashtable() { clear(); }
113+
114+
void clear() {
115+
for (Node* node : buckets) {
116+
while (node) {
117+
Node* tmp = node->next;
118+
std::allocator_traits<node_allocator_type>::destroy(alloc, node);
119+
alloc.deallocate(node, 1);
120+
node = tmp;
121+
}
122+
}
123+
buckets.assign(buckets.size(), nullptr);
124+
size_ = 0;
125+
}
126+
127+
std::pair<iterator, bool> insert(const_reference v) {
128+
const key_type& k = key_of_value(v);
129+
size_type h = hasher(k);
130+
size_type idx = h % buckets.size();
131+
132+
if constexpr (Policy == InsertPolicy::UniqueKeys) {
133+
for (Node* node = buckets[idx]; node; node = node->next) {
134+
if (node->hash == h && equal(key_of_value(node->value), k)) {
135+
return {iterator(node, &buckets, idx), false};
136+
}
137+
}
138+
}
139+
140+
Node* new_node = alloc.allocate(1);
141+
std::allocator_traits<node_allocator_type>::construct(alloc, new_node, v, h, buckets[idx]);
142+
buckets[idx] = new_node;
143+
++size_;
144+
145+
rehash_();
146+
147+
return {iterator(new_node, &buckets, idx), true};
148+
}
149+
150+
iterator find(const key_type& k) {
151+
size_type h = hasher(k);
152+
size_type idx = h % buckets.size();
153+
for (Node* node = buckets[idx]; node; node = node->next) {
154+
if (node->hash == h && equal(key_of_value(node->value), k)) {
155+
return iterator(node, &buckets, idx);
156+
}
157+
}
158+
return end();
159+
}
160+
161+
iterator begin() {
162+
for (size_type i = 0, ie = buckets.size(); i < ie; ++i) {
163+
if (buckets[i]) {
164+
return iterator(buckets[i], &buckets, i);
165+
}
166+
}
167+
return end();
168+
}
169+
170+
iterator end() { return iterator(nullptr, &buckets, buckets.size()); }
171+
172+
size_type size() const { return size_; }
173+
174+
size_type bucket_count() const { return buckets.size(); }
175+
176+
float load_factor() const { return static_cast<float>(size_) / buckets.size(); }
177+
178+
void rehash(size_type new_count) {
179+
std::vector<Node*> new_buckets(new_count, nullptr);
180+
181+
for (Node* node : buckets) {
182+
while (node) {
183+
Node* next = node->next;
184+
size_type idx = node->hash % new_count;
185+
node->next = new_buckets[idx];
186+
new_buckets[idx] = node;
187+
node = next;
188+
}
189+
}
190+
191+
buckets.swap(new_buckets);
192+
}
193+
};
194+
195+
} // namespace my
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <mystd/hashtable.hpp>
4+
#include <utility>
5+
6+
namespace my::testing {
7+
8+
template <class Key>
9+
struct Identity {
10+
const Key& operator()(const Key& k) const noexcept { return k; }
11+
};
12+
13+
template <class Key, class Value>
14+
struct KeyOfValue {
15+
const Key& operator()(const std::pair<const Key, Value>& v) const noexcept { return v.first; }
16+
};
17+
18+
TEST(HashTableTest, Initialization) {
19+
my::hashtable<int, int, Identity<int>> hset;
20+
hset.insert(1);
21+
hset.insert(1);
22+
hset.insert(1);
23+
hset.insert(1);
24+
EXPECT_EQ(hset.size(), 1);
25+
26+
hset.insert(2);
27+
EXPECT_EQ(hset.size(), 2);
28+
29+
my::hashtable<std::pair<char, int>, int, KeyOfValue<char, int>> hmap;
30+
hmap.insert(std::pair<char, int>('a', 10));
31+
hmap.insert(std::pair<char, int>('a', 10));
32+
hmap.insert(std::pair<char, int>('a', 10));
33+
hmap.insert(std::pair<char, int>('a', 10));
34+
EXPECT_EQ(hmap.size(), 1);
35+
36+
hmap.insert(std::pair<char, int>('b', 20));
37+
EXPECT_EQ(hmap.size(), 2);
38+
}
39+
40+
TEST(HashTableTest, Iterators) {
41+
my::hashtable<int, int, Identity<int>> hset;
42+
my::hashtable<std::pair<char, int>, int, KeyOfValue<char, int>> hmap;
43+
44+
for (size_t i = 0; i < 100; ++i) {
45+
hset.insert(i);
46+
hmap.insert({i, i * 10});
47+
}
48+
49+
auto it_set = hset.begin();
50+
auto ite_set = hset.end();
51+
for (; it_set != ite_set; ++it_set) {
52+
// EXPECT_TRUE(*it_set);
53+
}
54+
55+
auto it_map = hmap.begin();
56+
auto ite_map = hmap.end();
57+
58+
while (it_map != ite_map) {
59+
EXPECT_EQ(it_map->second, it_map->first * 10);
60+
++it_map;
61+
}
62+
}
63+
64+
TEST(HashTableTest, Find) {
65+
my::hashtable<int, int, Identity<int>> hset;
66+
my::hashtable<std::pair<char, int>, int, KeyOfValue<char, int>> hmap;
67+
68+
for (size_t i = 0; i < 100; ++i) {
69+
hset.insert(i);
70+
hmap.insert({i, i * 10});
71+
}
72+
73+
for (size_t i = 0; i < 100; ++i) {
74+
EXPECT_EQ(*hset.find(i), i);
75+
}
76+
77+
for (size_t i = 0; i < 100; ++i) {
78+
EXPECT_EQ((*hmap.find(i)).second, i * 10);
79+
}
80+
}
81+
82+
} // namespace my::testing

0 commit comments

Comments
 (0)