use std::hash::{BuildHasher, Hash, Hasher};
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct RendezvousHasher<H, B>
where
H: Hasher,
B: BuildHasher<Hasher = H>,
{
build_hasher: B,
_marker: PhantomData<H>,
}
impl<H, B> RendezvousHasher<H, B>
where
H: Hasher,
B: BuildHasher<Hasher = H>,
{
#[inline]
pub fn new(build_hasher: B) -> Self {
Self {
build_hasher,
_marker: PhantomData,
}
}
#[inline]
pub fn select<'a, K, N>(&self, key: &K, nodes: &'a [N]) -> Option<&'a N>
where
K: Hash,
N: Hash,
{
nodes
.iter()
.enumerate()
.max_by_key(|(_, node)| {
let mut hasher = self.build_hasher.build_hasher();
key.hash(&mut hasher);
node.hash(&mut hasher);
hasher.finish()
})
.map(move |(_, node)| node)
}
#[inline]
pub fn select_index<K, N>(&self, key: &K, nodes: &[N]) -> Option<usize>
where
K: Hash,
N: Hash,
{
nodes
.iter()
.enumerate()
.max_by_key(|(_, node)| {
let mut hasher = self.build_hasher.build_hasher();
key.hash(&mut hasher);
node.hash(&mut hasher);
hasher.finish()
})
.map(|(idx, _)| idx)
}
#[inline]
pub fn rank<'a, K, N>(&self, key: &K, nodes: &'a [N]) -> Vec<&'a N>
where
K: Hash,
N: Hash,
{
let mut ranked: Vec<_> = nodes.iter().collect();
ranked.sort_unstable_by_key(|node| {
let mut hasher = self.build_hasher.build_hasher();
key.hash(&mut hasher);
node.hash(&mut hasher);
std::cmp::Reverse(hasher.finish())
});
ranked
}
}
pub fn with_default_hasher<H, B>() -> RendezvousHasher<H, B>
where
H: Hasher + Default,
B: BuildHasher<Hasher = H> + Default,
{
RendezvousHasher::new(B::default())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fnv::Fnv1aHasher64;
use std::collections::hash_map::RandomState;
use std::hash::BuildHasherDefault;
#[test]
fn test_select_with_default_hasher() {
let hasher = RendezvousHasher::<_, RandomState>::new(RandomState::new());
let nodes = vec!["node1", "node2", "node3", "node4"];
let node = hasher.select(&"test_key", &nodes).unwrap();
let node2 = hasher.select(&"test_key", &nodes).unwrap();
assert_eq!(node, node2);
let node_a = hasher.select(&"key_a", &nodes);
let node_b = hasher.select(&"key_b", &nodes);
println!("node_a: {:?}, node_b: {:?}", node_a, node_b);
}
#[test]
fn test_select_with_fnv_hasher() {
let hasher =
RendezvousHasher::<_, BuildHasherDefault<Fnv1aHasher64>>::new(BuildHasherDefault::<
Fnv1aHasher64,
>::default());
let nodes = vec!["node1", "node2", "node3", "node4", "node5"];
let keys = vec!["key1", "key2", "key3", "key4", "key5"];
for key in &keys {
let node = hasher.select(key, &nodes).unwrap();
println!("Key: {}, Selected node: {}", key, node);
}
let node1 = hasher.select(&"consistent_key", &nodes).unwrap();
let node2 = hasher.select(&"consistent_key", &nodes).unwrap();
assert_eq!(node1, node2);
}
#[test]
fn test_node_removal() {
let hasher = RendezvousHasher::<_, RandomState>::new(RandomState::new());
let nodes = vec!["node1", "node2", "node3", "node4", "node5"];
let keys: Vec<String> = (0..100).map(|i| format!("key_{}", i)).collect();
let mut assignments = Vec::new();
for key in &keys {
let node = hasher.select(key, &nodes).unwrap();
assignments.push((key, *node));
}
let reduced_nodes = vec!["node1", "node2", "node3", "node4"];
let mut reassigned = 0;
for (key, original_node) in &assignments {
let new_node = hasher.select(key, &reduced_nodes).unwrap();
if *new_node != *original_node {
reassigned += 1;
}
}
println!(
"Reassigned {}/{} keys after removing a node",
reassigned,
keys.len()
);
assert!(reassigned > 0);
assert!(reassigned < 40); }
#[test]
fn test_rank() {
let hasher =
RendezvousHasher::<_, BuildHasherDefault<Fnv1aHasher64>>::new(BuildHasherDefault::<
Fnv1aHasher64,
>::default());
let nodes = vec!["node1", "node2", "node3", "node4"];
let ranked = hasher.rank(&"test_ranking", &nodes);
assert_eq!(ranked.len(), nodes.len());
let selected = hasher.select(&"test_ranking", &nodes).unwrap();
assert_eq!(ranked[0], selected);
let ranked2 = hasher.rank(&"test_ranking", &nodes);
assert_eq!(ranked, ranked2);
}
#[test]
fn test_select_index() {
let hasher = RendezvousHasher::<_, RandomState>::new(RandomState::new());
let nodes = vec!["node1", "node2", "node3", "node4"];
let idx = hasher.select_index(&"test_key", &nodes).unwrap();
let node = nodes[idx];
let direct_node = hasher.select(&"test_key", &nodes).unwrap();
assert_eq!(&node, direct_node);
}
#[test]
fn test_empty_nodes() {
let hasher = RendezvousHasher::<_, RandomState>::new(RandomState::new());
let empty: Vec<&str> = vec![];
assert_eq!(hasher.select(&"key", &empty), None);
assert_eq!(hasher.select_index(&"key", &empty), None);
assert!(hasher.rank(&"key", &empty).is_empty());
}
}