├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src └── main.rs └── unsafe.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "cfg-if" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 8 | 9 | [[package]] 10 | name = "getrandom" 11 | version = "0.2.2" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 14 | dependencies = [ 15 | "cfg-if", 16 | "libc", 17 | "wasi", 18 | ] 19 | 20 | [[package]] 21 | name = "libc" 22 | version = "0.2.94" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" 25 | 26 | [[package]] 27 | name = "ppv-lite86" 28 | version = "0.2.10" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 31 | 32 | [[package]] 33 | name = "rand" 34 | version = "0.8.3" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 37 | dependencies = [ 38 | "libc", 39 | "rand_chacha", 40 | "rand_core", 41 | "rand_hc", 42 | ] 43 | 44 | [[package]] 45 | name = "rand_chacha" 46 | version = "0.3.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 49 | dependencies = [ 50 | "ppv-lite86", 51 | "rand_core", 52 | ] 53 | 54 | [[package]] 55 | name = "rand_core" 56 | version = "0.6.2" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 59 | dependencies = [ 60 | "getrandom", 61 | ] 62 | 63 | [[package]] 64 | name = "rand_hc" 65 | version = "0.3.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 68 | dependencies = [ 69 | "rand_core", 70 | ] 71 | 72 | [[package]] 73 | name = "rust-hash-table" 74 | version = "0.1.0" 75 | dependencies = [ 76 | "rand", 77 | ] 78 | 79 | [[package]] 80 | name = "wasi" 81 | version = "0.10.2+wasi-snapshot-preview1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 84 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-hash-table" 3 | version = "0.1.0" 4 | authors = ["rexim "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rand = "0.8.3" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hash Table Implementation in [Rust](https://rust-lang.org/) 2 | 3 | ![unsafe](./unsafe.gif) 4 | 5 | Purely for educational and recreational purposes. For real world production please use [std::collections::HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html). 6 | 7 | For resolving collisions we use [Open Addressing](https://en.wikipedia.org/wiki/Hash_table#Open_addressing). For the hash function we use [djb2](http://www.cse.yorku.ca/~oz/hash.html) 8 | 9 | ## Quick Start 10 | 11 | ```console 12 | $ cargo run --release 13 | ``` 14 | 15 | ## References 16 | 17 | - https://en.wikipedia.org/wiki/Hash_table 18 | - https://github.com/rust-lang/hashbrown 19 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | mod pepeja { 4 | use std::cmp::PartialEq; 5 | use std::fmt::Debug; 6 | 7 | pub trait Hashable { 8 | fn hash(&self) -> usize; 9 | } 10 | 11 | impl Hashable for String { 12 | // http://www.cse.yorku.ca/~oz/hash.html 13 | fn hash(&self) -> usize { 14 | let mut result: usize = 5381; 15 | for c in self.bytes() { 16 | result = ((result << 5).wrapping_add(result)).wrapping_add(c.into()); 17 | } 18 | result 19 | } 20 | } 21 | 22 | impl Hashable for usize { 23 | fn hash(&self) -> usize { 24 | *self 25 | } 26 | } 27 | 28 | #[derive(Default, Clone)] 29 | struct HashCell { 30 | key: Key, 31 | value: Value, 32 | taken: bool, 33 | } 34 | 35 | pub struct HashTable { 36 | cells: Vec>, 37 | taken_count: usize, 38 | } 39 | 40 | impl HashTable 41 | where 42 | Key: Clone + Default + Debug + PartialEq + Hashable, 43 | Value: Clone + Default + Debug, 44 | { 45 | pub fn new() -> Self { 46 | const INITIAL_CAPACITY: usize = 11; 47 | Self { 48 | cells: vec![HashCell::<_, _>::default(); INITIAL_CAPACITY], 49 | taken_count: 0, 50 | } 51 | } 52 | 53 | pub fn extend(&mut self) { 54 | assert!(self.cells.len() > 0); 55 | let mut new_self = Self { 56 | cells: vec![HashCell::<_, _>::default(); self.cells.len() * 2 + 1], 57 | taken_count: 0, 58 | }; 59 | 60 | for cell in self.cells.iter() { 61 | if cell.taken { 62 | new_self.insert(cell.key.clone(), cell.value.clone()); 63 | } 64 | } 65 | 66 | *self = new_self; 67 | } 68 | 69 | pub fn insert(&mut self, key: Key, new_value: Value) { 70 | if let Some(old_value) = self.get_mut(&key) { 71 | *old_value = new_value; 72 | } else { 73 | if self.taken_count >= self.cells.len() { 74 | self.extend(); 75 | } 76 | assert!(self.taken_count < self.cells.len()); 77 | 78 | let mut index = key.hash() % self.cells.len(); 79 | 80 | while self.cells[index].taken { 81 | index = (index + 1) % self.cells.len(); 82 | } 83 | 84 | self.cells[index].taken = true; 85 | self.cells[index].key = key; 86 | self.cells[index].value = new_value; 87 | self.taken_count += 1; 88 | } 89 | } 90 | 91 | fn get_index(&self, key: &Key) -> Option { 92 | let mut index = key.hash() % self.cells.len(); 93 | for _ in 0..self.cells.len() { 94 | if !self.cells[index].taken { 95 | break; 96 | } 97 | 98 | if self.cells[index].key == *key { 99 | break; 100 | } 101 | 102 | index = (index + 1) % self.cells.len(); 103 | } 104 | 105 | if self.cells[index].taken && self.cells[index].key == *key { 106 | Some(index) 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | #[allow(dead_code)] 113 | pub fn get(&self, key: &Key) -> Option<&Value> { 114 | if let Some(index) = self.get_index(key) { 115 | Some(&self.cells[index].value) 116 | } else { 117 | None 118 | } 119 | } 120 | 121 | pub fn get_mut(&mut self, key: &Key) -> Option<&mut Value> { 122 | if let Some(index) = self.get_index(key) { 123 | Some(&mut self.cells[index].value) 124 | } else { 125 | None 126 | } 127 | } 128 | } 129 | } 130 | 131 | fn benchmark_our_virgin_table(n: usize) { 132 | use pepeja::*; 133 | let mut hash = HashTable::::new(); 134 | for _ in 0..n { 135 | let key = rand::random::(); 136 | if let Some(value) = hash.get_mut(&key) { 137 | *value += 1; 138 | } else { 139 | hash.insert(key, 1); 140 | } 141 | } 142 | } 143 | 144 | fn benchmark_std_chad_table(n: usize) { 145 | use std::collections::HashMap; 146 | 147 | let mut hash = HashMap::::new(); 148 | for _ in 0..n { 149 | let key = rand::random::(); 150 | if let Some(value) = hash.get_mut(&key) { 151 | *value += 1; 152 | } else { 153 | hash.insert(key, 1); 154 | } 155 | } 156 | } 157 | 158 | fn main() { 159 | const N: usize = 100_000; 160 | 161 | let begin = Instant::now(); 162 | benchmark_our_virgin_table(N); 163 | println!("Our Virgin Table: {}", begin.elapsed().as_secs_f32()); 164 | 165 | let begin = Instant::now(); 166 | benchmark_std_chad_table(N); 167 | println!("std Chad Table: {}", begin.elapsed().as_secs_f32()); 168 | } 169 | -------------------------------------------------------------------------------- /unsafe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/rust-hash-table/7899231a9f319ad6e260d0827fb399146036f8b8/unsafe.gif --------------------------------------------------------------------------------