diff --git a/Cargo.toml b/Cargo.toml index 721500e..380bf7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,12 +3,12 @@ name = "intmap" version = "2.0.0" edition = "2021" rust-version = "1.58" -description = "Specialized HashMap for u64 keys" +description = "Specialized HashMap for integer keys" authors = ["Jesper Axelsson "] readme = "README.md" license = "MIT" repository = "https://github.com/JesperAxelsson/rust-intmap" -keywords = ["hashmap", "u64", "intmap"] +keywords = ["hashmap", "int", "intmap"] [dependencies] serde = { version = "1.0", optional = true, default-features = false } diff --git a/README.md b/README.md index 50dc929..9979100 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![crates.io](https://img.shields.io/crates/v/intmap.svg)](https://crates.io/crates/intmap) # rust-intmap -Specialized hashmap for `u64` keys +Specialized hashmap for integer keys. Might be missing some functionality but you can remove, add, get and clear for now. @@ -53,12 +53,12 @@ for i in 0..20_000 { ``` # How can it be so much faster? -I use a specialized hash function for `u64` which multiplies the key with the largest prime for `u64`. By keeping the internal cache a power 2 you can avoid the expensive modulus operator as mentioned in [this Stack Overflow post](http://stackoverflow.com/questions/6670715/mod-of-power-2-on-bitwise-operators). The hash function looks like this: +I use a specialized hash function for integers which multiplies the key with the their largest prime. By keeping the internal cache a power 2 you can avoid the expensive modulus operator as mentioned in [this Stack Overflow post](http://stackoverflow.com/questions/6670715/mod-of-power-2-on-bitwise-operators). The hash function looks like this: ```rust #[inline] fn hash_u64(seed: u64) -> u64 { - let a = 11400714819323198549u64; + let a = 18446744073709551611u64; let val = a.wrapping_mul(seed); val } diff --git a/integration_tests/Cargo.lock b/integration_tests/Cargo.lock index 26b81e6..3e1d2b9 100644 --- a/integration_tests/Cargo.lock +++ b/integration_tests/Cargo.lock @@ -215,6 +215,7 @@ version = "0.1.0" dependencies = [ "intmap", "proptest", + "serde", "serde_json", ] diff --git a/integration_tests/benchmark/Cargo.toml b/integration_tests/benchmark/Cargo.toml index 0fb7137..165c50e 100644 --- a/integration_tests/benchmark/Cargo.toml +++ b/integration_tests/benchmark/Cargo.toml @@ -16,3 +16,7 @@ rand = "0.8.5" [[bench]] name = "basic_bench" harness = false + +[[bench]] +name = "key_comparison" +harness = false diff --git a/integration_tests/benchmark/benches/basic_bench.rs b/integration_tests/benchmark/benches/basic_bench.rs index dd8ed71..7edd386 100644 --- a/integration_tests/benchmark/benches/basic_bench.rs +++ b/integration_tests/benchmark/benches/basic_bench.rs @@ -17,13 +17,13 @@ fn main() { #[bench] fn u64_insert_built_in(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = HashMap::with_capacity(data.len()); + let mut map: HashMap = HashMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } }); } @@ -33,10 +33,10 @@ fn u64_insert_without_capacity_built_in(bencher: Bencher) { let data = get_random_range(VEC_COUNT); bencher.bench_local(|| { - let mut map = HashMap::new(); + let mut map: HashMap = HashMap::new(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } black_box(&map); @@ -46,10 +46,10 @@ fn u64_insert_without_capacity_built_in(bencher: Bencher) { #[bench] fn u64_get_built_in(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map: HashMap<&u64, &u64> = HashMap::with_capacity(data.len()); + let mut map: HashMap = HashMap::with_capacity(data.len()); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } bencher.bench_local(|| { @@ -87,13 +87,14 @@ impl std::hash::BuildHasher for NoOpHasher { #[bench] fn u64_insert_no_op(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = HashMap::with_capacity_and_hasher(data.len(), NoOpHasher(0)); + let mut map: HashMap = + HashMap::with_capacity_and_hasher(data.len(), NoOpHasher(0)); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } }); } @@ -103,10 +104,10 @@ fn u64_insert_without_capacity_no_op(bencher: Bencher) { let data = get_random_range(VEC_COUNT); bencher.bench_local(|| { - let mut map = HashMap::with_hasher(NoOpHasher(0)); + let mut map: HashMap = HashMap::with_hasher(NoOpHasher(0)); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } black_box(&map); @@ -116,11 +117,11 @@ fn u64_insert_without_capacity_no_op(bencher: Bencher) { #[bench] fn u64_get_no_op(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map: HashMap<&u64, &u64, NoOpHasher> = + let mut map: HashMap = HashMap::with_capacity_and_hasher(data.len(), NoOpHasher(0)); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } bencher.bench_local(|| { @@ -135,13 +136,13 @@ fn u64_get_no_op(bencher: Bencher) { #[bench] fn u64_insert_brown(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = BrownMap::with_capacity(data.len()); + let mut map: BrownMap = BrownMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } }); } @@ -151,10 +152,10 @@ fn u64_insert_without_capacity_brown(bencher: Bencher) { let data = get_random_range(VEC_COUNT); bencher.bench_local(|| { - let mut map = BrownMap::new(); + let mut map: BrownMap = BrownMap::new(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } black_box(&map); @@ -164,10 +165,10 @@ fn u64_insert_without_capacity_brown(bencher: Bencher) { #[bench] fn u64_get_brown(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map: BrownMap<&u64, &u64> = BrownMap::with_capacity(data.len()); + let mut map: BrownMap = BrownMap::with_capacity(data.len()); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } bencher.bench_local(|| { @@ -182,13 +183,13 @@ fn u64_get_brown(bencher: Bencher) { #[bench] fn u64_insert_ahash(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = AHashMap::with_capacity(data.len()); + let mut map: AHashMap = AHashMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } }); } @@ -198,10 +199,10 @@ fn u64_insert_without_capacity_ahash(bencher: Bencher) { let data = get_random_range(VEC_COUNT); bencher.bench_local(|| { - let mut map = AHashMap::new(); + let mut map: AHashMap = AHashMap::new(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } black_box(&map); @@ -211,10 +212,10 @@ fn u64_insert_without_capacity_ahash(bencher: Bencher) { #[bench] fn u64_get_ahash(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map: AHashMap<&u64, &u64> = AHashMap::with_capacity(data.len()); + let mut map: AHashMap = AHashMap::with_capacity(data.len()); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } bencher.bench_local(|| { @@ -229,13 +230,13 @@ fn u64_get_ahash(bencher: Bencher) { #[bench] fn u64_insert_indexmap(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = IndexMap::with_capacity(data.len()); + let mut map: IndexMap = IndexMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } }); } @@ -243,10 +244,10 @@ fn u64_insert_indexmap(bencher: Bencher) { #[bench] fn u64_get_indexmap(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map: IndexMap<&u64, &u64> = IndexMap::with_capacity(data.len()); + let mut map: IndexMap = IndexMap::with_capacity(data.len()); for s in data.iter() { - black_box(map.insert(s, s)); + black_box(map.insert(*s, *s)); } bencher.bench_local(|| { @@ -261,13 +262,13 @@ fn u64_get_indexmap(bencher: Bencher) { #[bench] fn u64_insert_intmap(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = IntMap::with_capacity(data.len()); + let mut map: IntMap = IntMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert(*s, s)); + black_box(map.insert(*s, *s)); } }); } @@ -275,13 +276,13 @@ fn u64_insert_intmap(bencher: Bencher) { #[bench] fn u64_insert_intmap_checked(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = IntMap::with_capacity(data.len()); + let mut map: IntMap = IntMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); for s in data.iter() { - black_box(map.insert_checked(*s, s)); + black_box(map.insert_checked(*s, *s)); } }); } @@ -290,7 +291,7 @@ fn u64_insert_intmap_checked(bencher: Bencher) { fn u64_insert_intmap_entry(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = IntMap::with_capacity(data.len()); + let mut map: IntMap = IntMap::with_capacity(data.len()); bencher.bench_local(|| { map.clear(); @@ -298,7 +299,7 @@ fn u64_insert_intmap_entry(bencher: Bencher) { for s in data.iter() { black_box(match map.entry(*s) { Entry::Occupied(_) => panic!("unexpected while insert, i = {}", s), - Entry::Vacant(entry) => entry.insert(s), + Entry::Vacant(entry) => entry.insert(*s), }); } }); @@ -309,10 +310,10 @@ fn u64_insert_without_capacity_intmap(bencher: Bencher) { let data = get_random_range(VEC_COUNT); bencher.bench_local(|| { - let mut map = IntMap::new(); + let mut map: IntMap = IntMap::new(); for s in data.iter() { - black_box(map.insert(*s, s)); + black_box(map.insert(*s, *s)); } black_box(&map); @@ -322,7 +323,7 @@ fn u64_insert_without_capacity_intmap(bencher: Bencher) { #[bench] fn u64_resize_intmap(bencher: Bencher) { bencher.bench_local(|| { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.reserve(VEC_COUNT); black_box(&map); }); @@ -332,9 +333,9 @@ fn u64_resize_intmap(bencher: Bencher) { fn u64_get_intmap(bencher: Bencher) { let data = get_random_range(VEC_COUNT); - let mut map = IntMap::with_capacity(data.len()); + let mut map: IntMap = IntMap::with_capacity(data.len()); for s in data.iter() { - map.insert(*s, s); + map.insert(*s, *s); } bencher.bench_local(|| { diff --git a/integration_tests/benchmark/benches/key_comparison.rs b/integration_tests/benchmark/benches/key_comparison.rs new file mode 100644 index 0000000..772f2d3 --- /dev/null +++ b/integration_tests/benchmark/benches/key_comparison.rs @@ -0,0 +1,87 @@ +use divan::{bench, black_box, Bencher}; +use intmap::{IntKey, IntMap}; +use rand::distributions::{Distribution, Standard}; + +const VEC_COUNT: usize = 10_000; + +fn main() { + divan::main(); +} + +#[bench(types = [u16, u32, u64, u128])] +fn insert(bencher: Bencher) +where + K: IntKey + Ord + PartialEq, + Standard: Distribution, +{ + let data = get_random_range::(VEC_COUNT); + let mut map: IntMap = IntMap::with_capacity(data.len()); + + bencher.bench_local(|| { + map.clear(); + + for (k, v) in data.iter() { + black_box(map.insert(*k, *v)); + } + }); +} + +#[bench(types = [u16, u32, u64, u128])] +fn insert_without_capacity(bencher: Bencher) +where + K: IntKey + Ord + PartialEq, + Standard: Distribution, +{ + let data = get_random_range::(VEC_COUNT); + + bencher.bench_local(|| { + let mut map: IntMap = IntMap::new(); + + for (k, v) in data.iter() { + black_box(map.insert(*k, *v)); + } + + black_box(&map); + }); +} + +#[bench(types = [u16, u32, u64, u128])] +fn get(bencher: Bencher) +where + K: IntKey + Ord + PartialEq, + Standard: Distribution, +{ + let data = get_random_range::(VEC_COUNT); + + let mut map: IntMap = IntMap::with_capacity(data.len()); + for (k, v) in data.iter() { + map.insert(*k, *v); + } + + bencher.bench_local(|| { + for (k, _) in data.iter() { + black_box(map.contains_key(*k)); + } + }); +} + +fn get_random_range(count: usize) -> Vec<(K, u64)> +where + K: Ord + PartialEq, + Standard: Distribution, +{ + use rand::prelude::StdRng; + use rand::{Rng, SeedableRng}; + + let mut vec = Vec::new(); + let mut rng = StdRng::seed_from_u64(4242); + + for _ in 0..count { + vec.push((rng.gen::(), rng.gen::())); + } + + vec.sort(); + vec.dedup(); + + vec +} diff --git a/integration_tests/random_ops/src/lib.rs b/integration_tests/random_ops/src/lib.rs index 3c7031a..b9b622a 100644 --- a/integration_tests/random_ops/src/lib.rs +++ b/integration_tests/random_ops/src/lib.rs @@ -1,9 +1,45 @@ -use std::collections::HashMap; +use std::ops::RangeInclusive; +use std::{collections::HashMap, fmt::Debug, hash::Hash}; -use intmap::IntMap; +use intmap::{IntKey, IntMap}; use proptest::collection::vec; use proptest::prelude::*; +pub trait TestIntKey: IntKey + Debug + Eq + Hash + 'static { + type Range: Strategy; + + fn small_range() -> Self::Range; + fn total_range() -> Self::Range; +} + +macro_rules! impl_test_int_for_int { + ($int:ident) => { + impl TestIntKey for $int { + type Range = RangeInclusive; + + fn small_range() -> Self::Range { + 0..=10 + } + fn total_range() -> Self::Range { + 0..=(Self::MAX) + } + } + }; +} + +impl_test_int_for_int!(u8); +impl_test_int_for_int!(u16); +impl_test_int_for_int!(u32); +impl_test_int_for_int!(u64); +impl_test_int_for_int!(u128); +impl_test_int_for_int!(usize); +impl_test_int_for_int!(i8); +impl_test_int_for_int!(i16); +impl_test_int_for_int!(i32); +impl_test_int_for_int!(i64); +impl_test_int_for_int!(isize); +impl_test_int_for_int!(i128); + #[derive(Clone, Debug)] pub struct Capacity(usize); @@ -25,15 +61,15 @@ impl LoadFactor { } #[derive(Clone, Debug)] -pub struct Key(u64); +pub struct Key(K); -impl Key { +impl Key { fn arb() -> impl Strategy { prop_oneof![ - // Small keys with high probability for collision - 10 => 0u64..=10u64, + // Keys with high probability for collision + 10 => K::small_range(), // Totally arbitrary keys - 1 => any::(), + 1 => K::total_range(), ] .prop_map(Self) } @@ -49,9 +85,9 @@ impl Value { } #[derive(Clone, Debug)] -pub struct Pairs(Vec<(u64, u8)>); +pub struct Pairs(Vec<(K, u8)>); -impl Pairs { +impl Pairs { fn arb() -> impl Strategy { vec( (Key::arb().prop_map(|k| k.0), Value::arb().prop_map(|v| v.0)), @@ -62,14 +98,14 @@ impl Pairs { } #[derive(Clone, Debug)] -pub enum Ctor { +pub enum Ctor { New, WithCapacity(Capacity), Default, - FromIter(Pairs), + FromIter(Pairs), } -impl Ctor { +impl Ctor { pub fn arb() -> impl Strategy { prop_oneof![ Just(Self::New), @@ -79,7 +115,7 @@ impl Ctor { ] } - pub fn apply(&self) -> (IntMap, HashMap) { + pub fn apply(&self) -> (IntMap, HashMap) { match self { Self::New => (IntMap::new(), HashMap::new()), Self::WithCapacity(capacity) => (IntMap::with_capacity(capacity.0), HashMap::new()), @@ -93,16 +129,16 @@ impl Ctor { } #[derive(Clone, Debug)] -pub enum Op { +pub enum Op { SetLoadFactor(LoadFactor), GetLoadFactor, Reserve(Capacity), - Insert((Key, Value)), - InsertChecked((Key, Value)), - Get(Key), - GetMut(Key), - Remove(Key), - ContainsKey(Key), + Insert((Key, Value)), + InsertChecked((Key, Value)), + Get(Key), + GetMut(Key), + Remove(Key), + ContainsKey(Key), Clear, Retain(Value), IsEmpty, @@ -117,13 +153,13 @@ pub enum Op { LoadRate, Capacity, Collisions, - Entry(Key), + Entry(Key), Clone, Debug, - Extend(Pairs), + Extend(Pairs), } -impl Op { +impl Op { pub fn arb_vec(max_size: usize) -> impl Strategy> { vec(Op::arb(), 0..=max_size) } @@ -160,7 +196,7 @@ impl Op { ] } - pub fn apply(&self, map: &mut IntMap, reference: &mut HashMap) { + pub fn apply(&self, map: &mut IntMap, reference: &mut HashMap) { match self { Self::SetLoadFactor(load_factor) => { map.set_load_factor(load_factor.0); @@ -241,7 +277,7 @@ impl Op { *map = map.clone(); } Self::Debug => { - format!("{map:?}"); + let _string = format!("{map:?}"); } Self::Extend(pairs) => { map.extend(pairs.0.clone()); diff --git a/integration_tests/random_ops/tests/random_ops.rs b/integration_tests/random_ops/tests/random_ops.rs index 9b17cc0..dac96f6 100644 --- a/integration_tests/random_ops/tests/random_ops.rs +++ b/integration_tests/random_ops/tests/random_ops.rs @@ -1,38 +1,94 @@ use std::collections::HashMap; -use intmap::IntMap; -use intmap_integration_test_random_ops::{Ctor, Op}; +use intmap::{IntKey, IntMap}; +use intmap_integration_test_random_ops::{Ctor, Op, TestIntKey}; use proptest::prelude::*; proptest! { - // This test performs random operations on IntMap to ensure that no operation - // fails due to violated invariants. Also all mutable operations are performed - // on an reference implementation (HashMap). The elements of the final IntMap - // are compared with the elements of the reference implementation. - #[test] - fn test_random_ops( - ctor in Ctor::arb(), - ops in Op::arb_vec(200), - ) { - let (mut map, mut reference) = ctor.apply(); - assert_map(&map, &reference); + #[test] + fn test_random_ops_u8(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } + + #[test] + fn test_random_ops_u16(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } + + #[test] + fn test_random_ops_u32(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } + + #[test] + fn test_random_ops_u64(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } - for op in ops { - op.apply(&mut map, &mut reference); - assert_map(&map, &reference); - } + #[test] + fn test_random_ops_u128(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } - let mut map_values = map.iter().collect::>(); - map_values.sort_by_key(|(&key, _)| key); + #[test] + fn test_random_ops_usize(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } - let mut reference_values = reference.iter().collect::>(); - reference_values.sort_by_key(|(&key, _)| key); + #[test] + fn test_random_ops_i8(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } - assert_eq!(map_values, reference_values); + #[test] + fn test_random_ops_i16(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); } + + #[test] + fn test_random_ops_i32(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } + + #[test] + fn test_random_ops_i64(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } + + #[test] + fn test_random_ops_i128(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } + + #[test] + fn test_random_ops_isize(ctor in Ctor::::arb(), ops in Op::::arb_vec(200)) { + test_random_ops(ctor, ops); + } +} + +// This test performs random operations on IntMap to ensure that no operation +// fails due to violated invariants. Also all mutable operations are performed +// on an reference implementation (HashMap). The elements of the final IntMap +// are compared with the elements of the reference implementation. +fn test_random_ops(ctor: Ctor, ops: Vec>) { + let (mut map, mut reference) = ctor.apply(); + assert_map(&map, &reference); + + for op in ops { + op.apply(&mut map, &mut reference); + assert_map(&map, &reference); + } + + let mut map_values = map.iter().collect::>(); + map_values.sort_by_key(|(key, _)| *key); + + let mut reference_values = reference.iter().collect::>(); + reference_values.sort_by_key(|(key, _)| *key); + + assert_eq!(map_values, reference_values); } -fn assert_map(map: &IntMap, reference: &HashMap) { +fn assert_map(map: &IntMap, reference: &HashMap) { let debug = false; if debug { println!( diff --git a/integration_tests/serde/Cargo.toml b/integration_tests/serde/Cargo.toml index 9a5f3d9..1d48833 100644 --- a/integration_tests/serde/Cargo.toml +++ b/integration_tests/serde/Cargo.toml @@ -8,4 +8,5 @@ publish = false [dev-dependencies] intmap = { path = "../..", features = ["serde"] } proptest = "1.0.0" +serde = "1.0.0" serde_json = "1.0.1" diff --git a/integration_tests/serde/tests/roundtrip.rs b/integration_tests/serde/tests/roundtrip.rs index 82664ed..282a7d6 100644 --- a/integration_tests/serde/tests/roundtrip.rs +++ b/integration_tests/serde/tests/roundtrip.rs @@ -1,13 +1,80 @@ -use intmap::IntMap; +use std::collections::HashMap; +use std::fmt::Debug; + +use intmap::{IntKey, IntMap}; use proptest::collection::hash_map; use proptest::prelude::*; +use serde::{de::DeserializeOwned, Serialize}; proptest! { #[test] - fn test_roundtrip(m in hash_map(any::(), any::(), 0..20)) { - let im: IntMap<_> = m.into_iter().collect(); - let bytes = serde_json::to_vec(&im).unwrap(); - let im_copy = serde_json::from_slice(&bytes[..]).unwrap(); - prop_assert_eq!(im, im_copy); + fn test_roundtrip_u8(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_u16(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_u32(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_u64(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_u128(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_usize(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_i8(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_i16(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_i32(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_i64(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; } + + #[test] + fn test_roundtrip_i128(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } + + #[test] + fn test_roundtrip_isize(m in hash_map(any::(), any::(), 0..20)) { + test_roundtrip(m)?; + } +} + +fn test_roundtrip(m: HashMap) -> Result<(), TestCaseError> +where + K: IntKey + Serialize + DeserializeOwned + Debug, +{ + let im: IntMap = m.into_iter().collect(); + let bytes = serde_json::to_vec(&im).unwrap(); + let im_copy = serde_json::from_slice(&bytes[..]).unwrap(); + prop_assert_eq!(im, im_copy); + Ok(()) } diff --git a/src/entry.rs b/src/entry.rs index 96149e6..08db1c6 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,21 +1,21 @@ // ***************** Entry ********************* -use crate::IntMap; +use crate::{int::SealedInt, IntKey, IntMap}; /// A view into a single entry in a [`IntMap`], which may either be vacant or occupied. /// /// The entry can be constructed by calling [`IntMap::entry`] with a key. It allows inspection /// and in-place manipulation of its value without repeated lookups. -pub enum Entry<'a, V: 'a> { +pub enum Entry<'a, K: IntKey, V: 'a> { /// The entry is occupied. - Occupied(OccupiedEntry<'a, V>), + Occupied(OccupiedEntry<'a, K, V>), /// The entry is vacant. - Vacant(VacantEntry<'a, V>), + Vacant(VacantEntry<'a, K, V>), } -impl<'a, V> Entry<'a, V> { +impl<'a, K: IntKey, V> Entry<'a, K, V> { #[inline] - pub(crate) fn new(key: u64, int_map: &'a mut IntMap) -> Self { + pub(crate) fn new(key: K, int_map: &'a mut IntMap) -> Self { let indices = Self::indices(key, int_map); match indices { @@ -28,33 +28,34 @@ impl<'a, V> Entry<'a, V> { } } - fn indices(key: u64, int_map: &IntMap) -> Option<(usize, usize)> { + fn indices(key: K, int_map: &IntMap) -> Option<(usize, usize)> { if int_map.is_empty() { return None; } - let cache_ix = int_map.calc_index(key); + let int = key.into_int(); + let cache_ix = int.calc_index(int_map.mod_mask); let vals = &int_map.cache[cache_ix]; let vals_ix = { vals.iter() } .enumerate() - .find_map(|(vals_ix, &(k, _))| (k == key).then(|| vals_ix))?; + .find_map(|(vals_ix, (k, _))| (k.into_int() == int).then(|| vals_ix))?; Some((cache_ix, vals_ix)) } } /// A view into an occupied entry in a [`IntMap`]. It is part of the [`Entry`] enum. -pub struct OccupiedEntry<'a, V: 'a> { +pub struct OccupiedEntry<'a, K: IntKey, V: 'a> { // Index to vals, guaranteed to be valid vals_ix: usize, // Element of IntMap::cache, guaranteed to be non-empty - vals: &'a mut Vec<(u64, V)>, + vals: &'a mut Vec<(K, V)>, // IntMap::count, guaranteed to be non-zero count: &'a mut usize, } -impl<'a, V> OccupiedEntry<'a, V> { +impl<'a, K: IntKey, V> OccupiedEntry<'a, K, V> { /// Gets a reference to the value in the entry. pub fn get(&self) -> &V { // Safety: We didn't modify the cache since we calculated the index @@ -90,12 +91,12 @@ impl<'a, V> OccupiedEntry<'a, V> { } /// A view into a vacant entry in a [`IntMap`]. It is part of the [`Entry`] enum. -pub struct VacantEntry<'a, V: 'a> { - key: u64, - int_map: &'a mut IntMap, +pub struct VacantEntry<'a, K: IntKey, V: 'a> { + key: K, + int_map: &'a mut IntMap, } -impl<'a, V: 'a> VacantEntry<'a, V> { +impl<'a, K: IntKey, V: 'a> VacantEntry<'a, K, V> { pub fn insert(self, value: V) -> &'a mut V { self.int_map.insert(self.key, value); return self.int_map.get_mut(self.key).unwrap(); diff --git a/src/int.rs b/src/int.rs new file mode 100644 index 0000000..b46aec7 --- /dev/null +++ b/src/int.rs @@ -0,0 +1,89 @@ +use std::fmt::Debug; + +/// An integer that can be used as underlying key for [`IntMap`]. +/// +/// Note that this is a sealed trait that cannot be implemented externally. Consider implementing +/// [`IntKey`] instead. +/// +/// [`IntMap`]: crate::IntMap +/// [`IntKey`]: crate::IntKey +pub trait Int: SealedInt {} + +impl Int for u8 {} +impl Int for u16 {} +impl Int for u32 {} +impl Int for u64 {} +impl Int for u128 {} +impl Int for usize {} +impl Int for i8 {} +impl Int for i16 {} +impl Int for i32 {} +impl Int for i64 {} +impl Int for i128 {} +impl Int for isize {} + +pub trait SealedInt: Copy + PartialEq + Debug + SerdeInt { + fn calc_index(self, mod_mask: usize) -> usize; +} + +#[cfg(not(feature = "serde"))] +pub trait SerdeInt {} + +#[cfg(feature = "serde")] +pub trait SerdeInt: serde::Serialize + for<'de> serde::Deserialize<'de> {} + +macro_rules! impl_sealed_int_for_int_with_highest_prime { + ($uint:ident, $prime:expr) => { + impl SealedInt for $uint { + #[inline(always)] + fn calc_index(self, mod_mask: usize) -> usize { + let hash = $prime.wrapping_mul(self); + // Faster modulus + (hash as usize) & mod_mask + } + } + + impl SerdeInt for $uint {} + }; +} + +macro_rules! impl_sealed_int_for_int_with_cast { + ($int:ident as $uint:ident) => { + impl SealedInt for $int { + #[inline(always)] + fn calc_index(self, mod_mask: usize) -> usize { + (self as $uint).calc_index(mod_mask) + } + } + + impl SerdeInt for $int {} + }; +} + +// Source: https://t5k.org/lists/2small/ +// Checked with: https://www.numberempire.com/primenumbers.php +const U8_PRIME_MAX: u8 = u8::MAX - 4; // 251 +const U16_PRIME_MAX: u16 = u16::MAX - 14; // 65521 +const U32_PRIME_MAX: u32 = u32::MAX - 4; // 4294967291 +const U64_PRIME_MAX: u64 = u64::MAX - 58; // 18446744073709551557 +const U128_PRIME_MAX: u128 = u128::MAX - 158; // 340282366920938463463374607431768211297 + +impl_sealed_int_for_int_with_highest_prime!(u8, U8_PRIME_MAX); +impl_sealed_int_for_int_with_highest_prime!(u16, U16_PRIME_MAX); +impl_sealed_int_for_int_with_highest_prime!(u32, U32_PRIME_MAX); +impl_sealed_int_for_int_with_highest_prime!(u64, U64_PRIME_MAX); +impl_sealed_int_for_int_with_highest_prime!(u128, U128_PRIME_MAX); + +#[cfg(target_pointer_width = "16")] +impl_sealed_int_for_int_with_cast!(usize as u16); +#[cfg(target_pointer_width = "32")] +impl_sealed_int_for_int_with_cast!(usize as u32); +#[cfg(target_pointer_width = "64")] +impl_sealed_int_for_int_with_cast!(usize as u64); + +impl_sealed_int_for_int_with_cast!(i8 as u8); +impl_sealed_int_for_int_with_cast!(i16 as u16); +impl_sealed_int_for_int_with_cast!(i32 as u32); +impl_sealed_int_for_int_with_cast!(i64 as u64); +impl_sealed_int_for_int_with_cast!(i128 as u64); +impl_sealed_int_for_int_with_cast!(isize as usize); diff --git a/src/int_key.rs b/src/int_key.rs new file mode 100644 index 0000000..75fbd22 --- /dev/null +++ b/src/int_key.rs @@ -0,0 +1,107 @@ +use std::num::{ + NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU16, NonZeroU32, + NonZeroU64, NonZeroU8, NonZeroUsize, Saturating, Wrapping, +}; + +use crate::Int; + +/// A type that can be used as key for [`IntMap`]. +/// +/// The type needs to be integer based, i.e. it can be represented by a unique integer. +/// This can be useful for types that wraps integers for type safety (e.g. [`Ipv4Addr`]) +/// or for enforcing invariants (e.g. [`NonZeroU64`]). +/// +/// [`IntMap`]: crate::IntMap +/// [`Ipv4Addr`]: std::net::Ipv4Addr +/// [`NonZeroU64`]: std::num::NonZeroU64 +pub trait IntKey: Copy { + /// The underlying integer that will be used as actual key. + type Int: Int; + /// Converts the key into the underlying integer. + /// + /// [`IntMap`] assumes that this is a cheap operation and that two different values + /// don't return the same integer. + fn into_int(self) -> Self::Int; +} + +macro_rules! impl_int_key_for_int { + ($self:ident) => { + impl IntKey for $self { + type Int = $self; + + fn into_int(self) -> Self::Int { + self + } + } + }; +} + +impl_int_key_for_int!(u8); +impl_int_key_for_int!(u16); +impl_int_key_for_int!(u32); +impl_int_key_for_int!(u64); +impl_int_key_for_int!(u128); +impl_int_key_for_int!(usize); +impl_int_key_for_int!(i8); +impl_int_key_for_int!(i16); +impl_int_key_for_int!(i32); +impl_int_key_for_int!(i64); +impl_int_key_for_int!(i128); +impl_int_key_for_int!(isize); + +macro_rules! impl_int_key_for_non_zero_int { + ($non_zero_int:ident as $int:ident) => { + impl IntKey for $non_zero_int { + type Int = $int; + + fn into_int(self) -> Self::Int { + self.get() + } + } + }; +} + +impl_int_key_for_non_zero_int!(NonZeroU8 as u8); +impl_int_key_for_non_zero_int!(NonZeroU16 as u16); +impl_int_key_for_non_zero_int!(NonZeroU32 as u32); +impl_int_key_for_non_zero_int!(NonZeroU64 as u64); +impl_int_key_for_non_zero_int!(NonZeroUsize as usize); +impl_int_key_for_non_zero_int!(NonZeroI8 as i8); +impl_int_key_for_non_zero_int!(NonZeroI16 as i16); +impl_int_key_for_non_zero_int!(NonZeroI32 as i32); +impl_int_key_for_non_zero_int!(NonZeroI64 as i64); +impl_int_key_for_non_zero_int!(NonZeroIsize as isize); + +impl IntKey for Saturating { + type Int = K::Int; + + fn into_int(self) -> Self::Int { + self.0.into_int() + } +} + +impl IntKey for Wrapping { + type Int = K::Int; + + fn into_int(self) -> Self::Int { + self.0.into_int() + } +} + +impl IntKey for std::net::Ipv4Addr { + type Int = u32; + + fn into_int(self) -> Self::Int { + // Copied from Ipv4Addr::to_bits, which does not exist for our MSRV + u32::from_be_bytes(self.octets()) + } +} + +impl IntKey for std::net::Ipv6Addr { + type Int = u128; + + fn into_int(self) -> Self::Int { + // Copied from Ipv6Addr::to_bits, which does not exist for our MSRV + u128::from_be_bytes(self.octets()) + } +} diff --git a/src/iter.rs b/src/iter.rs index 545ee0a..04595b3 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -5,15 +5,16 @@ use std::slice::IterMut as SliceIterMut; use std::vec::Drain as VecDrain; use std::vec::IntoIter as VecIntoIter; +use crate::IntKey; use crate::IntMap; // ***************** Iter ********************* -pub struct Iter<'a, K: 'a, V: 'a> { +pub struct Iter<'a, K: IntKey, V> { inner: IterFlatten>>, } -impl<'a, K, V> Iter<'a, K, V> { +impl<'a, K: IntKey, V> Iter<'a, K, V> { pub(crate) fn new(vec: &'a [Vec<(K, V)>]) -> Self { Iter { inner: vec.iter().flatten(), @@ -21,7 +22,7 @@ impl<'a, K, V> Iter<'a, K, V> { } } -impl<'a, K, V> Iterator for Iter<'a, K, V> { +impl<'a, K: IntKey, V> Iterator for Iter<'a, K, V> { type Item = (&'a K, &'a V); #[inline] @@ -32,11 +33,11 @@ impl<'a, K, V> Iterator for Iter<'a, K, V> { // ***************** Iter Mut ********************* -pub struct IterMut<'a, K: 'a, V: 'a> { +pub struct IterMut<'a, K: IntKey, V> { inner: IterFlatten>>, } -impl<'a, K, V> IterMut<'a, K, V> { +impl<'a, K: IntKey, V> IterMut<'a, K, V> { pub(crate) fn new(vec: &'a mut [Vec<(K, V)>]) -> IterMut<'a, K, V> { IterMut { inner: vec.iter_mut().flatten(), @@ -44,7 +45,7 @@ impl<'a, K, V> IterMut<'a, K, V> { } } -impl<'a, K, V> Iterator for IterMut<'a, K, V> { +impl<'a, K: IntKey, V> Iterator for IterMut<'a, K, V> { type Item = (&'a K, &'a mut V); #[inline] @@ -55,11 +56,11 @@ impl<'a, K, V> Iterator for IterMut<'a, K, V> { // ***************** Keys Iter ********************* -pub struct Keys<'a, K: 'a, V: 'a> { +pub struct Keys<'a, K: IntKey, V> { pub(crate) inner: Iter<'a, K, V>, } -impl<'a, K, V> Iterator for Keys<'a, K, V> { +impl<'a, K: IntKey, V> Iterator for Keys<'a, K, V> { type Item = &'a K; #[inline] @@ -74,11 +75,11 @@ impl<'a, K, V> Iterator for Keys<'a, K, V> { // ***************** Values Iter ********************* -pub struct Values<'a, K: 'a, V: 'a> { +pub struct Values<'a, K: IntKey, V> { pub(crate) inner: Iter<'a, K, V>, } -impl<'a, K, V> Iterator for Values<'a, K, V> { +impl<'a, K: IntKey, V> Iterator for Values<'a, K, V> { type Item = &'a V; #[inline] @@ -93,11 +94,11 @@ impl<'a, K, V> Iterator for Values<'a, K, V> { // ***************** Values Mut ********************* -pub struct ValuesMut<'a, K: 'a, V: 'a> { +pub struct ValuesMut<'a, K: IntKey, V> { pub(crate) inner: IterMut<'a, K, V>, } -impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { +impl<'a, K: IntKey, V> Iterator for ValuesMut<'a, K, V> { type Item = &'a mut V; #[inline] @@ -113,20 +114,20 @@ impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { // ***************** Into Iter ********************* -impl IntoIterator for IntMap { - type Item = (u64, V); - type IntoIter = IntoIter; +impl IntoIterator for IntMap { + type Item = (K, V); + type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { IntoIter::new(self.cache) } } -pub struct IntoIter { +pub struct IntoIter { inner: IterFlatten>>, } -impl IntoIter { +impl IntoIter { pub(crate) fn new(vec: Vec>) -> Self { IntoIter { inner: vec.into_iter().flatten(), @@ -134,7 +135,7 @@ impl IntoIter { } } -impl Iterator for IntoIter { +impl Iterator for IntoIter { type Item = (K, V); #[inline] @@ -146,7 +147,7 @@ impl Iterator for IntoIter { // ***************** Drain Iter ********************* #[allow(clippy::type_complexity)] -pub struct Drain<'a, K: 'a, V: 'a> { +pub struct Drain<'a, K: IntKey, V> { count: &'a mut usize, inner: IterFlatMap< SliceIterMut<'a, Vec<(K, V)>>, @@ -155,7 +156,7 @@ pub struct Drain<'a, K: 'a, V: 'a> { >, } -impl<'a, K, V> Drain<'a, K, V> { +impl<'a, K: IntKey, V> Drain<'a, K, V> { pub(crate) fn new(vec: &'a mut [Vec<(K, V)>], count: &'a mut usize) -> Drain<'a, K, V> { Drain { count, @@ -164,7 +165,7 @@ impl<'a, K, V> Drain<'a, K, V> { } } -impl<'a, K, V> Iterator for Drain<'a, K, V> { +impl<'a, K: IntKey, V> Iterator for Drain<'a, K, V> { type Item = (K, V); #[inline] @@ -179,9 +180,9 @@ impl<'a, K, V> Iterator for Drain<'a, K, V> { // ***************** Extend ********************* -impl Extend<(u64, V)> for IntMap { +impl Extend<(K, V)> for IntMap { #[inline] - fn extend>(&mut self, iter: T) { + fn extend>(&mut self, iter: T) { for elem in iter { self.insert(elem.0, elem.1); } @@ -190,9 +191,9 @@ impl Extend<(u64, V)> for IntMap { // ***************** FromIterator ********************* -impl std::iter::FromIterator<(u64, V)> for IntMap { +impl std::iter::FromIterator<(K, V)> for IntMap { #[inline] - fn from_iter>(iter: T) -> Self { + fn from_iter>(iter: T) -> Self { let iterator = iter.into_iter(); let (lower_bound, _) = iterator.size_hint(); diff --git a/src/lib.rs b/src/lib.rs index 2530273..ad81f95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![forbid(unsafe_code)] -//! Specialized hashmap for `u64` keys. +//! Specialized hashmap for integer based keys. //! //! For more information see the [README](https://github.com/JesperAxelsson/rust-intmap/blob/master/README.md). //! @@ -12,30 +12,36 @@ mod serde; mod entry; +mod int; +mod int_key; mod iter; use core::iter::{IntoIterator, Iterator}; -use iter::*; +use int::SealedInt; pub use entry::*; +pub use int::Int; +pub use int_key::IntKey; +use iter::*; // Test examples from the README. #[doc = include_str!("../README.md")] #[cfg(doctest)] pub struct ReadmeDoctests; -/// A hashmap that maps `u64` to `V`. +/// A hashmap that maps an integer based `K` to `V`. #[derive(Clone)] -pub struct IntMap { +pub struct IntMap { // The slots for the key/value pairs. // // The number of slots is what we call "capacity". Two or more key/value pairs occupy the same // slot if they have a hash collision. - cache: Vec>, + // The size of `cache` as binary exponent. The actual size of `cache` is `2^size`. + cache: Vec>, // The size of `cache` as binary exponent. The actual size of `cache` is `2^size`. size: u32, // A bit mask for calculating an index for `cache`. Must be recomputed if `size` changes. - mod_mask: u64, + mod_mask: usize, // The number of stored key/value pairs. count: usize, // The ratio between key/value pairs and available slots that we try to ensure. @@ -44,7 +50,7 @@ pub struct IntMap { load_factor: usize, } -impl IntMap { +impl IntMap { /// Creates a new [`IntMap`]. /// /// The [`IntMap`] is initially created with a capacity of 0, so it will not allocate until it @@ -58,7 +64,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// assert_eq!(map, IntMap::default()); /// ``` pub const fn new() -> Self { @@ -70,7 +76,9 @@ impl IntMap { load_factor: 909, // 90.9% } } +} +impl IntMap { /// Creates a new [`IntMap`] with at least the given capacity. /// /// If the capacity is 0, the [`IntMap`] will not allocate. Otherwise the capacity is rounded @@ -81,7 +89,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::with_capacity(20); + /// let mut map: IntMap = IntMap::with_capacity(20); /// ``` pub fn with_capacity(capacity: usize) -> Self { let mut map = Self::new(); @@ -99,7 +107,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::with_capacity(20); + /// let mut map: IntMap = IntMap::with_capacity(20); /// map.set_load_factor(0.909); // Sets load factor to 90.9% /// ``` pub fn set_load_factor(&mut self, load_factor: f32) { @@ -129,18 +137,19 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map = IntMap::new(); + /// let mut map: IntMap:: = IntMap::new(); /// assert_eq!(map.insert(21, "Eat my shorts"), None); /// assert_eq!(map.insert(21, "Ay, caramba"), Some("Eat my shorts")); /// assert_eq!(map.get(21), Some(&"Ay, caramba")); /// ``` - pub fn insert(&mut self, key: u64, value: V) -> Option { + pub fn insert(&mut self, key: K, value: V) -> Option { self.ensure_load_rate(); - let ix = self.calc_index(key); + let int = key.into_int(); + let ix = int.calc_index(self.mod_mask); let vals = &mut self.cache[ix]; - let pos = vals.iter().position(|kv| kv.0 == key); + let pos = vals.iter().position(|kv| kv.0.into_int() == int); let old = if let Some(pos) = pos { Some(vals.swap_remove(pos).1) @@ -164,18 +173,19 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map = IntMap::new(); + /// let mut map: IntMap:: = IntMap::new(); /// assert!(map.insert_checked(21, "Eat my shorts")); /// assert!(!map.insert_checked(21, "Ay, caramba")); /// assert_eq!(map.get(21), Some(&"Eat my shorts")); /// ``` - pub fn insert_checked(&mut self, key: u64, value: V) -> bool { + pub fn insert_checked(&mut self, key: K, value: V) -> bool { self.ensure_load_rate(); - let ix = self.calc_index(key); + let int = key.into_int(); + let ix = int.calc_index(self.mod_mask); let vals = &mut self.cache[ix]; - if vals.iter().any(|kv| kv.0 == key) { + if vals.iter().any(|kv| kv.0.into_int() == int) { return false; } @@ -192,23 +202,25 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(21, 42); /// let val = map.get(21); /// assert!(val.is_some()); /// assert_eq!(*val.unwrap(), 42); /// assert!(map.contains_key(21)); /// ``` - pub fn get(&self, key: u64) -> Option<&V> { + pub fn get(&self, key: K) -> Option<&V> { if self.is_empty() { return None; } - let ix = self.calc_index(key); + let int = key.into_int(); + let ix = int.calc_index(self.mod_mask); let vals = &self.cache[ix]; - vals.iter().find_map(|kv| (kv.0 == key).then(|| &kv.1)) + vals.iter() + .find_map(|kv| (kv.0.into_int() == int).then(|| &kv.1)) } /// Gets the mutable value for the given key from the [`IntMap`]. @@ -218,7 +230,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(21, 42); /// /// assert_eq!(*map.get(21).unwrap(), 42); @@ -230,18 +242,19 @@ impl IntMap { /// } /// assert_eq!(*map.get(21).unwrap(), 43); /// ``` - pub fn get_mut(&mut self, key: u64) -> Option<&mut V> { + pub fn get_mut(&mut self, key: K) -> Option<&mut V> { if self.is_empty() { return None; } - let ix = self.calc_index(key); + let int = key.into_int(); + let ix = int.calc_index(self.mod_mask); let vals = &mut self.cache[ix]; return vals .iter_mut() - .find_map(|kv| (kv.0 == key).then(move || &mut kv.1)); + .find_map(|kv| (kv.0.into_int() == int).then(move || &mut kv.1)); } /// Removes the value for given key from the [`IntMap`] and returns it. @@ -251,26 +264,27 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(21, 42); /// let val = map.remove(21); /// assert!(val.is_some()); /// assert_eq!(val.unwrap(), 42); /// assert!(!map.contains_key(21)); /// ``` - pub fn remove(&mut self, key: u64) -> Option { + pub fn remove(&mut self, key: K) -> Option { if self.is_empty() { return None; } - let ix = self.calc_index(key); + let int = key.into_int(); + let ix = int.calc_index(self.mod_mask); let vals = &mut self.cache[ix]; for i in 0..vals.len() { - let peek = vals[i].0; + let peek = &vals[i].0; - if peek == key { + if peek.into_int() == int { self.count -= 1; let kv = vals.swap_remove(i); return Some(kv.1); @@ -287,11 +301,11 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(21, 42); /// assert!(map.contains_key(21)); /// ``` - pub fn contains_key(&self, key: u64) -> bool { + pub fn contains_key(&self, key: K) -> bool { self.get(key).is_some() } @@ -302,7 +316,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(21, 42); /// map.clear(); /// assert_eq!(map.len(), 0); @@ -324,7 +338,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(1, 11); /// map.insert(2, 12); /// map.insert(4, 13); @@ -338,7 +352,7 @@ impl IntMap { /// ``` pub fn retain(&mut self, mut f: F) where - F: FnMut(u64, &V) -> bool, + F: FnMut(K, &V) -> bool, { let mut removed = 0; for vals in &mut self.cache { @@ -361,7 +375,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::new(); + /// let mut map: IntMap = IntMap::new(); /// map.insert(21, 42); /// assert!(!map.is_empty()); /// map.remove(21); @@ -374,27 +388,27 @@ impl IntMap { //**** Iterators ***** /// Returns an [`Iterator`] over all key/value pairs. - pub fn iter(&self) -> Iter { + pub fn iter(&self) -> Iter { Iter::new(&self.cache) } /// Returns an [`Iterator`] over all key/value pairs with mutable value. - pub fn iter_mut(&mut self) -> IterMut { + pub fn iter_mut(&mut self) -> IterMut { IterMut::new(&mut self.cache) } /// Returns an [`Iterator`] over all keys. - pub fn keys(&self) -> Keys { + pub fn keys(&self) -> Keys { Keys { inner: self.iter() } } /// Returns an [`Iterator`] over all values. - pub fn values(&self) -> Values { + pub fn values(&self) -> Values { Values { inner: self.iter() } } /// Returns an [`Iterator`] over all mutable values. - pub fn values_mut(&mut self) -> ValuesMut { + pub fn values_mut(&mut self) -> ValuesMut { ValuesMut { inner: self.iter_mut(), } @@ -405,44 +419,32 @@ impl IntMap { /// /// If the [`Iterator`] is droppend then all remaining key/value pairs will be removed from /// the [`IntMap`]. - pub fn drain(&mut self) -> Drain { + pub fn drain(&mut self) -> Drain { Drain::new(&mut self.cache, &mut self.count) } //**** Internal hash stuff ***** - #[inline(always)] - fn hash_u64(seed: u64) -> u64 { - let a = 11400714819323198549u64; - a.wrapping_mul(seed) - } - - #[inline(always)] - pub(crate) fn calc_index(&self, key: u64) -> usize { - let hash = Self::hash_u64(key); - // Faster modulus - (hash & self.mod_mask) as usize - } - #[inline(always)] fn lim(&self) -> usize { if self.size == 0 { 0 } else { - 2u64.pow(self.size) as usize + 2usize.pow(self.size) } } fn increase_cache(&mut self) { self.size += 1; let new_lim = self.lim(); - self.mod_mask = (new_lim as u64) - 1; + self.mod_mask = new_lim - 1; - let mut vec: Vec> = (0..new_lim).map(|_| Vec::new()).collect(); + let mut vec: Vec> = (0..new_lim).map(|_| Vec::new()).collect(); std::mem::swap(&mut self.cache, &mut vec); for k in vec.into_iter().flatten() { - let ix = self.calc_index(k.0); + let int = k.0.into_int(); + let ix = int.calc_index(self.mod_mask); let vals = &mut self.cache[ix]; vals.push(k); @@ -488,7 +490,7 @@ impl IntMap { /// ``` /// use intmap::IntMap; /// - /// let mut map: IntMap = IntMap::with_capacity(2); + /// let mut map: IntMap = IntMap::with_capacity(2); /// map.set_load_factor(2.0); /// assert_eq!(map.load_rate(), 0.0); /// map.insert(1, 42); @@ -523,7 +525,7 @@ impl IntMap { /// /// Only for testing. #[doc(hidden)] - pub fn collisions(&self) -> IntMap { + pub fn collisions(&self) -> IntMap { let mut map = IntMap::new(); for s in self.cache.iter() { @@ -567,12 +569,12 @@ impl IntMap { /// assert_eq!(counters.get(50), Some(&3)); /// assert_eq!(counters.get(60), Some(&1)); /// ``` - pub fn entry(&mut self, key: u64) -> Entry { + pub fn entry(&mut self, key: K) -> Entry { Entry::new(key, self) } } -impl Default for IntMap { +impl Default for IntMap { fn default() -> Self { Self::new() } @@ -580,21 +582,23 @@ impl Default for IntMap { // ***************** Equality ********************* -impl PartialEq for IntMap +impl PartialEq for IntMap where + K: IntKey, V: PartialEq, { - fn eq(&self, other: &IntMap) -> bool { - self.iter().all(|(k, a)| other.get(*k) == Some(a)) - && other.iter().all(|(k, a)| self.get(*k) == Some(a)) + fn eq(&self, other: &IntMap) -> bool { + self.iter().all(|(&k, a)| other.get(k) == Some(a)) + && other.iter().all(|(&k, a)| self.get(k) == Some(a)) } } -impl Eq for IntMap where V: Eq {} +impl Eq for IntMap {} // ***************** Debug ********************* -impl std::fmt::Debug for IntMap +impl std::fmt::Debug for IntMap where + K: IntKey + std::fmt::Debug, V: std::fmt::Debug, { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/src/serde.rs b/src/serde.rs index 0eabaf1..f6ea26c 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,11 +1,15 @@ -use crate::IntMap; +use crate::{IntKey, IntMap}; use serde::{ de::{Deserializer, MapAccess, Visitor}, ser::SerializeMap, Deserialize, Serialize, Serializer, }; -impl Serialize for IntMap { +impl Serialize for IntMap +where + K: IntKey + Serialize, + V: Serialize, +{ fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -18,7 +22,11 @@ impl Serialize for IntMap { } } -impl<'de, T: Deserialize<'de>> Deserialize<'de> for IntMap { +impl<'de, K, V> Deserialize<'de> for IntMap +where + K: IntKey + Deserialize<'de>, + V: Deserialize<'de>, +{ fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -27,11 +35,11 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for IntMap { } } -struct IntMapVisitor { - marker: std::marker::PhantomData IntMap>, +struct IntMapVisitor { + marker: std::marker::PhantomData IntMap>, } -impl IntMapVisitor { +impl IntMapVisitor { fn new() -> Self { IntMapVisitor { marker: std::marker::PhantomData, @@ -39,11 +47,12 @@ impl IntMapVisitor { } } -impl<'de, V> Visitor<'de> for IntMapVisitor +impl<'de, K, V> Visitor<'de> for IntMapVisitor where + K: IntKey + Deserialize<'de>, V: Deserialize<'de>, { - type Value = IntMap; + type Value = IntMap; fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "IntMap<{}>", std::any::type_name::()) diff --git a/tests/basic_test.rs b/tests/basic_test.rs index 534df30..27702d6 100644 --- a/tests/basic_test.rs +++ b/tests/basic_test.rs @@ -29,7 +29,7 @@ mod tests { fn intmap_get_insert_impl() { let count = 20_000; let data = get_random_range(count); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); println!(); println!("Starting test"); @@ -72,7 +72,7 @@ mod tests { #[test] fn reserve() { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.reserve(9001); } @@ -162,7 +162,7 @@ mod tests { #[test] fn single_add_get() { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.insert(21, 42); let val = map.get(21); assert!(val.is_some()); @@ -172,7 +172,7 @@ mod tests { #[test] fn map_iter() { let count = 20_000; - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -187,7 +187,7 @@ mod tests { fn map_iter_keys() { let count = 20_000; let data: Vec<_> = (0..count).collect(); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -202,7 +202,7 @@ mod tests { fn map_iter_values() { let count = 20_000; let data: Vec<_> = (0..count).collect(); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -216,7 +216,7 @@ mod tests { #[test] fn map_iter_values_mut() { let count = 20_000; - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -234,7 +234,7 @@ mod tests { #[test] fn map_mut_iter() { let count = 20_000; - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -251,7 +251,7 @@ mod tests { #[test] fn map_iter_empty() { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.clear(); if let Some(kv) = map.iter().next() { @@ -261,7 +261,7 @@ mod tests { #[test] fn map_mut_iter_empty() { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.clear(); if let Some(kv) = map.iter_mut().next() { @@ -272,7 +272,7 @@ mod tests { #[test] fn map_into_iter() { let count = 20_000; - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -286,7 +286,7 @@ mod tests { #[test] fn map_drain() { let count = 20_000; - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -300,7 +300,7 @@ mod tests { #[test] fn map_into_iter_empty() { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.clear(); if let Some(kv) = map.into_iter().next() { @@ -311,8 +311,8 @@ mod tests { #[test] fn extend_two_maps() { let count = 20_000; - let mut map_1: IntMap = IntMap::new(); - let mut map_2: IntMap = IntMap::new(); + let mut map_1: IntMap = IntMap::new(); + let mut map_2: IntMap = IntMap::new(); for i in 0..count { map_1.insert(i, i); @@ -335,7 +335,7 @@ mod tests { fn from_iter_collect() { let count = 20_000; - let map = (0..count).map(|i| (i, i * i)).collect::>(); + let map = (0..count).map(|i| (i, i * i)).collect::>(); for k in 0..count { assert!(map.contains_key(k)); @@ -350,17 +350,20 @@ mod tests { fn map_equality() { let count = 5_000; - let map_1 = (0..count).map(|i| (i, i * i)).collect::>(); + let map_1 = (0..count).map(|i| (i, i * i)).collect::>(); - let map_2 = (0..count).rev().map(|i| (i, i * i)).collect::>(); + let map_2 = (0..count) + .rev() + .map(|i| (i, i * i)) + .collect::>(); assert_eq!(map_1, map_2); } #[test] fn map_inequality() { - let map_1 = (0..10).map(|i| (i, i * i)).collect::>(); - let map_2 = (0..5).rev().map(|i| (i, i * i)).collect::>(); + let map_1 = (0..10).map(|i| (i, i * i)).collect::>(); + let map_2 = (0..5).rev().map(|i| (i, i * i)).collect::>(); assert_ne!(map_1, map_2); assert_ne!(map_2, map_1); @@ -370,7 +373,7 @@ mod tests { fn entry_api() { let count = 20_000; let data: Vec = (0..count).collect(); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); // Insert values 0..19999 for i in 0..count { @@ -421,7 +424,7 @@ mod tests { #[test] fn test_debug_features() { let count = 20_000; - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..count { map.insert(i, i); @@ -432,7 +435,7 @@ mod tests { assert!(map.load_rate() > 0.70); assert!(map.collisions().is_empty()); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); for i in 0..3 { map.insert(i, i + 1); } @@ -442,7 +445,7 @@ mod tests { #[test] fn load_factor() { - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.set_load_factor(0.0); assert_eq!(map.get_load_factor(), 0.0); @@ -455,7 +458,7 @@ mod tests { assert!(map.load_rate() <= 1.); assert!(map.collisions().is_empty()); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.set_load_factor(0.1); assert_eq!(map.get_load_factor(), 0.1); @@ -468,7 +471,7 @@ mod tests { assert!(map.load_rate() <= 10.); assert!(map.collisions().is_empty()); - let mut map: IntMap = IntMap::new(); + let mut map: IntMap = IntMap::new(); map.set_load_factor(2.); assert_eq!(map.get_load_factor(), 2.);