Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow other primitives as keys #67

Closed
wants to merge 2 commits into from
Closed
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
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/JesperAxelsson/rust-intmap"
keywords = ["hashmap", "u64", "intmap"]

[dependencies]
num-traits = "0.2.18"
serde = { version = "1.0", optional = true, default-features = false }

[dev-dependencies]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 unsigned primitive keys

Might be missing some functionality but you can remove, add, get and clear for now.

Expand Down Expand Up @@ -47,13 +47,13 @@ use intmap::IntMap;

let mut map = IntMap::new();

for i in 0..20_000 {
for i in 0..20_000u32 {
map.insert(i, format!("item: {:?}", i));
}
```

# 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, which multiplies the key with the largest prime for its type. 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]
Expand Down
1 change: 1 addition & 0 deletions integration_tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration_tests/benchmark/benches/basic_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ fn u64_insert_without_capacity_intmap(bencher: Bencher) {
#[bench]
fn u64_resize_intmap(bencher: Bencher) {
bencher.bench_local(|| {
let mut map: IntMap<u64> = IntMap::new();
let mut map: IntMap<u64, u64> = IntMap::new();
map.reserve(VEC_COUNT);
black_box(&map);
});
Expand Down
4 changes: 2 additions & 2 deletions integration_tests/random_ops/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl Ctor {
]
}

pub fn apply(&self) -> (IntMap<u8>, HashMap<u64, u8>) {
pub fn apply(&self) -> (IntMap<u64, u8>, HashMap<u64, u8>) {
match self {
Self::New => (IntMap::new(), HashMap::new()),
Self::WithCapacity(capacity) => (IntMap::with_capacity(capacity.0), HashMap::new()),
Expand Down Expand Up @@ -160,7 +160,7 @@ impl Op {
]
}

pub fn apply(&self, map: &mut IntMap<u8>, reference: &mut HashMap<u64, u8>) {
pub fn apply(&self, map: &mut IntMap<u64, u8>, reference: &mut HashMap<u64, u8>) {
match self {
Self::SetLoadFactor(load_factor) => {
map.set_load_factor(load_factor.0);
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/random_ops/tests/random_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ proptest! {
}
}

fn assert_map(map: &IntMap<u8>, reference: &HashMap<u64, u8>) {
fn assert_map(map: &IntMap<u64, u8>, reference: &HashMap<u64, u8>) {
let debug = false;
if debug {
println!(
Expand Down
2 changes: 1 addition & 1 deletion integration_tests/serde/tests/roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use proptest::prelude::*;
proptest! {
#[test]
fn test_roundtrip(m in hash_map(any::<u64>(), any::<String>(), 0..20)) {
let im: IntMap<_> = m.into_iter().collect();
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);
Expand Down
47 changes: 32 additions & 15 deletions src/entry.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
// ***************** Entry *********************

use crate::IntMap;
use std::ops::{BitAnd, Sub};

use num_traits::{AsPrimitive, WrappingMul};

use crate::{highest_prime::HighestPrime, 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, 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, V> Entry<'a, K, V> {
#[inline]
pub(crate) fn new(key: u64, int_map: &'a mut IntMap<V>) -> Self {
pub(crate) fn new(key: K, int_map: &'a mut IntMap<K, V>) -> Self
where
K: BitAnd + Copy + HighestPrime + PartialEq + WrappingMul,
<K as BitAnd>::Output: AsPrimitive<usize>,
{
let indices = Self::indices(key, int_map);

match indices {
Expand All @@ -28,7 +36,11 @@ impl<'a, V> Entry<'a, V> {
}
}

fn indices(key: u64, int_map: &IntMap<V>) -> Option<(usize, usize)> {
fn indices(key: K, int_map: &IntMap<K, V>) -> Option<(usize, usize)>
where
K: BitAnd + Copy + HighestPrime + PartialEq + WrappingMul,
<K as BitAnd>::Output: AsPrimitive<usize>,
{
if int_map.is_empty() {
return None;
}
Expand All @@ -45,16 +57,16 @@ impl<'a, V> Entry<'a, V> {
}

/// 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, 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, 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
Expand Down Expand Up @@ -90,13 +102,18 @@ 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<V>,
pub struct VacantEntry<'a, K, V: 'a> {
key: K,
int_map: &'a mut IntMap<K, V>,
}

impl<'a, V: 'a> VacantEntry<'a, V> {
pub fn insert(self, value: V) -> &'a mut V {
impl<'a, K, V: 'a> VacantEntry<'a, K, V> {
pub fn insert(self, value: V) -> &'a mut V
where
K: AsPrimitive<usize> + BitAnd + Copy + HighestPrime + PartialEq + Sub + WrappingMul,
<K as BitAnd>::Output: AsPrimitive<usize>,
usize: AsPrimitive<K>,
{
self.int_map.insert(self.key, value);
return self.int_map.get_mut(self.key).unwrap();
}
Expand Down
31 changes: 31 additions & 0 deletions src/highest_prime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
pub trait HighestPrime {
fn highest_prime() -> Self;
}

impl HighestPrime for u64 {
#[inline(always)]
fn highest_prime() -> Self {
18_446_744_073_709_551_557
}
}

impl HighestPrime for u32 {
#[inline(always)]
fn highest_prime() -> Self {
2_147_483_647
}
}

impl HighestPrime for u16 {
#[inline(always)]
fn highest_prime() -> Self {
65_521
}
}

impl HighestPrime for u8 {
#[inline(always)]
fn highest_prime() -> Self {
251
}
}
30 changes: 23 additions & 7 deletions src/iter.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use std::iter::FlatMap as IterFlatMap;
use std::iter::Flatten as IterFlatten;
use std::ops::BitAnd;
use std::ops::Sub;
use std::slice::Iter as SliceIter;
use std::slice::IterMut as SliceIterMut;
use std::vec::Drain as VecDrain;
use std::vec::IntoIter as VecIntoIter;

use num_traits::AsPrimitive;
use num_traits::WrappingMul;

use crate::highest_prime::HighestPrime;
use crate::IntMap;

// ***************** Iter *********************
Expand Down Expand Up @@ -113,9 +119,9 @@ impl<'a, K, V> Iterator for ValuesMut<'a, K, V> {

// ***************** Into Iter *********************

impl<V> IntoIterator for IntMap<V> {
type Item = (u64, V);
type IntoIter = IntoIter<u64, V>;
impl<K, V> IntoIterator for IntMap<K, V> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;

fn into_iter(self) -> Self::IntoIter {
IntoIter::new(self.cache)
Expand Down Expand Up @@ -179,9 +185,14 @@ impl<'a, K, V> Iterator for Drain<'a, K, V> {

// ***************** Extend *********************

impl<V> Extend<(u64, V)> for IntMap<V> {
impl<K, V> Extend<(K, V)> for IntMap<K, V>
where
K: AsPrimitive<usize> + BitAnd + Copy + HighestPrime + PartialEq + Sub + WrappingMul,
<K as BitAnd>::Output: AsPrimitive<usize>,
usize: AsPrimitive<K>,
{
#[inline]
fn extend<T: IntoIterator<Item = (u64, V)>>(&mut self, iter: T) {
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
for elem in iter {
self.insert(elem.0, elem.1);
}
Expand All @@ -190,9 +201,14 @@ impl<V> Extend<(u64, V)> for IntMap<V> {

// ***************** FromIterator *********************

impl<V> std::iter::FromIterator<(u64, V)> for IntMap<V> {
impl<K, V> std::iter::FromIterator<(K, V)> for IntMap<K, V>
where
K: 'static + AsPrimitive<usize> + BitAnd + Copy + HighestPrime + PartialEq + Sub + WrappingMul,
<K as BitAnd>::Output: AsPrimitive<usize>,
usize: AsPrimitive<K>,
{
#[inline]
fn from_iter<T: IntoIterator<Item = (u64, V)>>(iter: T) -> Self {
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let iterator = iter.into_iter();
let (lower_bound, _) = iterator.size_hint();

Expand Down
Loading
Loading