Skip to content

Commit 2ee80da

Browse files
committed
correct some comments, split lib.rs
1 parent bd4083f commit 2ee80da

3 files changed

Lines changed: 288 additions & 308 deletions

File tree

crates/consistent-choose-k/src/choose_k.rs

Lines changed: 9 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ impl Sample {
2121
/// Implementation of a consistent choose k hashing algorithm.
2222
/// It returns k distinct consistent hashes in the range `0..n`.
2323
/// The hashes are consistent when `n` changes and when `k` changes!
24-
/// I.e. on average exactly `1/(n+1)` (resp. `1/(k+1)`) many hashes will change
25-
/// when `n` (resp. `k`) increases by one. Additionally, the returned `k` tuple
24+
/// I.e. one hash changes with probability `k/(n+1)` when `n` increases by one,
25+
/// resp. one hash gets added when `k` is increased. Additionally, the returned `k` tuple
2626
/// is guaranteed to be uniformely chosen from all possible `n-choose-k` tuples.
2727
///
2828
/// Also implements `Iterator` to yield the next sample when k is increased.
2929
/// Note: since this hashing algorithm implements choose k semantics, all the returned samples are distinct.
30-
/// Note: they won't be sorted by their position, since the order is changing when k is changing.
30+
/// Note: The `Iterator` won't output the samples ordered by position.
3131
///
3232
/// # Example
3333
/// ```
@@ -78,14 +78,14 @@ impl<H: ManySeqBuilder> ConsistentChooseKHasher<H> {
7878
iter
7979
}
8080

81-
/// Returns an iterator over the sampled positions in increasing order.
81+
/// Returns an iterator over the `k` sampled positions in increasing order.
8282
///
8383
/// Time: O(1)
8484
pub fn positions(&self) -> impl Iterator<Item = usize> + '_ {
8585
self.samples.iter().map(|s| s.pos)
8686
}
8787

88-
/// Returns the underlying samples.
88+
/// Returns the `k` underlying samples.
8989
pub fn samples(&self) -> &[Sample] {
9090
&self.samples
9191
}
@@ -149,19 +149,20 @@ impl<H: ManySeqBuilder> ConsistentChooseKHasher<H> {
149149
if let Some(last) = self.samples.last().copied() {
150150
if last.pos < sk.pos {
151151
self.samples.push(sk);
152+
k
152153
} else if last.pos == sk.pos {
153154
let i = self.shrink_n_inner(last.pos);
154155
self.samples.push(sk);
155-
return i;
156+
i
156157
} else {
157158
let i = self.shrink_n_inner(last.pos);
158159
self.samples.push(last);
159-
return i;
160+
i
160161
}
161162
} else {
162163
self.samples.push(sk);
164+
k
163165
}
164-
k
165166
}
166167
}
167168

@@ -213,22 +214,6 @@ mod tests {
213214
}
214215
}
215216

216-
#[test]
217-
fn test_ranking_k_equals_1() {
218-
for key in 0..500 {
219-
let hasher = hasher_for_key(key);
220-
for n in 1..50 {
221-
let first = ConsistentChooseKHasher::new(hasher.clone(), n)
222-
.next()
223-
.unwrap();
224-
let prev: Vec<usize> = ConsistentChooseKHasher::new_with_k(hasher.clone(), n, 1)
225-
.positions()
226-
.collect();
227-
assert_eq!(first, prev[0]);
228-
}
229-
}
230-
}
231-
232217
#[test]
233218
fn test_ranking_k_equals_n() {
234219
// When exhausted, the ranking contains all nodes 0..n.
@@ -243,22 +228,6 @@ mod tests {
243228
}
244229
}
245230

246-
#[test]
247-
fn test_partial_iteration() {
248-
// Taking fewer than n elements must still be correct.
249-
for key in 0..100 {
250-
let hasher = hasher_for_key(key);
251-
let n = 20;
252-
let full: Vec<usize> = ConsistentChooseKHasher::new(hasher.clone(), n).collect();
253-
for take in 1..=n {
254-
let partial: Vec<usize> = ConsistentChooseKHasher::new(hasher.clone(), n)
255-
.take(take)
256-
.collect();
257-
assert_eq!(&partial[..], &full[..take]);
258-
}
259-
}
260-
}
261-
262231
#[test]
263232
fn test_uniform_k() {
264233
const K: usize = 3;
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
use std::hash::{Hash, Hasher};
2+
3+
/// A trait which behaves like a pseudo-random number generator.
4+
/// It is used to generate consistent hashes within one bucket.
5+
/// Note: the hasher must have been seeded with the key during construction.
6+
pub trait HashSequence {
7+
fn next(&mut self) -> u64;
8+
}
9+
10+
/// A trait for building a special bit mask and sequences of hashes for different bit positions.
11+
/// Note: the hasher must have been seeded with the key during construction.
12+
pub trait HashSeqBuilder {
13+
type Seq: HashSequence;
14+
15+
/// Returns a bit mask indicating which buckets have at least one hash.
16+
fn bit_mask(&self) -> u64;
17+
/// Return a HashSequence instance which is seeded with the given bit position
18+
/// and the seed of this builder.
19+
fn hash_seq(&self, bit: u64) -> Self::Seq;
20+
}
21+
22+
/// A trait for building multiple independent hash builders
23+
/// Note: the hasher must have been seeded with the key during construction.
24+
pub trait ManySeqBuilder {
25+
type Builder: HashSeqBuilder;
26+
27+
/// Returns the i-th independent hash builder.
28+
fn seq_builder(&self, i: usize) -> Self::Builder;
29+
}
30+
31+
impl<H: Hasher> HashSequence for H {
32+
fn next(&mut self) -> u64 {
33+
54387634019u64.hash(self);
34+
self.finish()
35+
}
36+
}
37+
38+
impl<H: Hasher + Clone> HashSeqBuilder for H {
39+
type Seq = H;
40+
41+
fn bit_mask(&self) -> u64 {
42+
self.finish()
43+
}
44+
45+
fn hash_seq(&self, bit: u64) -> Self::Seq {
46+
let mut hasher = self.clone();
47+
bit.hash(&mut hasher);
48+
hasher
49+
}
50+
}
51+
52+
impl<H: Hasher + Clone> ManySeqBuilder for H {
53+
type Builder = H;
54+
55+
fn seq_builder(&self, i: usize) -> Self::Builder {
56+
let mut hasher = self.clone();
57+
i.hash(&mut hasher);
58+
hasher
59+
}
60+
}
61+
62+
/// One building block for the consistent hashing algorithm is a consistent
63+
/// hash iterator which enumerates all the hashes for a specific bucket.
64+
/// A bucket covers the range `(1<<bit)..(2<<bit)`.
65+
#[derive(Default)]
66+
struct BucketIterator<H: HashSequence> {
67+
hasher: H,
68+
n: usize, // Upper bound for the hash values within the bucket.
69+
is_first: bool,
70+
bit: u64, // A bitmask with a single bit set.
71+
}
72+
73+
impl<H: HashSequence> BucketIterator<H> {
74+
fn new(n: usize, bit: u64, hasher: H) -> Self {
75+
Self {
76+
hasher,
77+
n,
78+
is_first: true,
79+
bit,
80+
}
81+
}
82+
}
83+
84+
impl<H: HashSequence> Iterator for BucketIterator<H> {
85+
type Item = usize;
86+
87+
fn next(&mut self) -> Option<Self::Item> {
88+
if self.bit == 0 {
89+
return None;
90+
}
91+
if self.is_first {
92+
let res = (self.hasher.next() & (self.bit - 1)) + self.bit;
93+
self.is_first = false;
94+
if res < self.n as u64 {
95+
self.n = res as usize;
96+
return Some(self.n);
97+
}
98+
}
99+
loop {
100+
let res = self.hasher.next() & (self.bit * 2 - 1);
101+
if res & self.bit == 0 {
102+
return None;
103+
}
104+
if res < self.n as u64 {
105+
self.n = res as usize;
106+
return Some(self.n);
107+
}
108+
}
109+
}
110+
}
111+
112+
/// An iterator which enumerates all the consistent hashes for a given key
113+
/// from largest to smallest in the range `0..n`.
114+
pub struct ConsistentHashRevIterator<H: HashSeqBuilder> {
115+
builder: H,
116+
bits: u64, // Bitmask of unvisited buckets.
117+
n: usize, // Exclusive upper bound for the hash values.
118+
inner: Option<BucketIterator<H::Seq>>, // Iterator for the current bucket.
119+
}
120+
121+
impl<H: HashSeqBuilder> ConsistentHashRevIterator<H> {
122+
pub fn new(n: usize, builder: H) -> Self {
123+
Self {
124+
bits: builder.bit_mask() & (n.next_power_of_two() as u64 - 1),
125+
builder,
126+
n,
127+
inner: None,
128+
}
129+
}
130+
}
131+
132+
impl<H: HashSeqBuilder> Iterator for ConsistentHashRevIterator<H> {
133+
type Item = usize;
134+
135+
fn next(&mut self) -> Option<Self::Item> {
136+
if self.n == 0 {
137+
return None;
138+
}
139+
if let Some(res) = self.inner.as_mut().and_then(|inner| inner.next()) {
140+
return Some(res);
141+
}
142+
while self.bits > 0 {
143+
let bit = 1 << self.bits.ilog2();
144+
self.bits ^= bit;
145+
let seq = self.builder.hash_seq(bit);
146+
let mut iter = BucketIterator::new(self.n, bit, seq);
147+
if let Some(res) = iter.next() {
148+
self.inner = Some(iter);
149+
return Some(res);
150+
}
151+
}
152+
self.n = 0;
153+
Some(0)
154+
}
155+
}
156+
157+
/// Same as `ConsistentHashRevIterator`, but iterates from smallest to largest
158+
/// for the range `n..`.
159+
pub struct ConsistentHashIterator<H: HashSeqBuilder> {
160+
bits: u64, // Bitmasks of unvisited buckets.
161+
n: usize, // Inclusive lower bound for the hash values.
162+
builder: H,
163+
stack: Vec<usize>, // Stack of hashes in the current bucket.
164+
}
165+
166+
impl<H: HashSeqBuilder> ConsistentHashIterator<H> {
167+
pub fn new(n: usize, builder: H) -> Self {
168+
Self {
169+
bits: builder.bit_mask() & !((n + 2).next_power_of_two() as u64 / 2 - 1),
170+
stack: if n == 0 { vec![0] } else { vec![] },
171+
builder,
172+
n,
173+
}
174+
}
175+
}
176+
177+
impl<H: HashSeqBuilder> Iterator for ConsistentHashIterator<H> {
178+
type Item = usize;
179+
180+
fn next(&mut self) -> Option<Self::Item> {
181+
if let Some(res) = self.stack.pop() {
182+
return Some(res);
183+
}
184+
while self.bits > 0 {
185+
let bit = self.bits & !(self.bits - 1);
186+
self.bits &= self.bits - 1;
187+
let inner = BucketIterator::new(bit as usize * 2, bit, self.builder.hash_seq(bit));
188+
self.stack = inner.take_while(|x| *x >= self.n).collect();
189+
if let Some(res) = self.stack.pop() {
190+
return Some(res);
191+
}
192+
}
193+
None
194+
}
195+
}
196+
197+
/// Wrapper around `ConsistentHashIterator` and `ConsistentHashRevIterator` to compute
198+
/// the next or previous consistent hash for a given key for a given number of nodes `n`.
199+
pub struct ConsistentHasher<H: HashSeqBuilder> {
200+
builder: H,
201+
}
202+
203+
impl<H: HashSeqBuilder> ConsistentHasher<H> {
204+
/// Construct a new ConsistentHasher with the given builder for a specific key.
205+
pub fn new(builder: H) -> Self {
206+
Self { builder }
207+
}
208+
209+
/// Return the largest consistent hash smaller than `n`.
210+
pub fn prev(&self, n: usize) -> Option<usize>
211+
where
212+
H: Clone,
213+
{
214+
let mut sampler = ConsistentHashRevIterator::new(n, self.builder.clone());
215+
sampler.next()
216+
}
217+
218+
/// Return the smallest consistent hash greater than or equal to `n`.
219+
pub fn next(&self, n: usize) -> Option<usize>
220+
where
221+
H: Clone,
222+
{
223+
let mut sampler = ConsistentHashIterator::new(n, self.builder.clone());
224+
sampler.next()
225+
}
226+
227+
/// Return the largest consistent hash smaller than `n`, consuming the hasher.
228+
pub fn into_prev(self, n: usize) -> Option<usize> {
229+
ConsistentHashRevIterator::new(n, self.builder).next()
230+
}
231+
}
232+
233+
#[cfg(test)]
234+
mod tests {
235+
use std::hash::DefaultHasher;
236+
237+
use super::*;
238+
239+
fn hasher_for_key(key: u64) -> DefaultHasher {
240+
let mut hasher = DefaultHasher::default();
241+
key.hash(&mut hasher);
242+
hasher
243+
}
244+
245+
#[test]
246+
fn test_uniform_1() {
247+
for k in 0..100 {
248+
let hasher = hasher_for_key(k);
249+
let sampler = ConsistentHasher::new(hasher.clone());
250+
for n in 0..1000 {
251+
assert!(sampler.prev(n + 1) <= sampler.prev(n + 2));
252+
let next = sampler.next(n).unwrap();
253+
assert_eq!(next, sampler.prev(next + 1).unwrap());
254+
}
255+
let mut iter_rev: Vec<_> = ConsistentHashIterator::new(0, hasher.clone())
256+
.take_while(|x| *x < 1000)
257+
.collect();
258+
iter_rev.reverse();
259+
let iter: Vec<_> = ConsistentHashRevIterator::new(1000, hasher).collect();
260+
assert_eq!(iter, iter_rev);
261+
}
262+
let mut stats = vec![0; 13];
263+
for i in 0..100000 {
264+
let hasher = hasher_for_key(i);
265+
let sampler = ConsistentHasher::new(hasher);
266+
let x = sampler.prev(stats.len()).unwrap();
267+
stats[x] += 1;
268+
}
269+
assert_eq!(
270+
stats,
271+
vec![7577, 7541, 7538, 7822, 7763, 7687, 7718, 7723, 7846, 7723, 7688, 7716, 7658]
272+
);
273+
}
274+
}

0 commit comments

Comments
 (0)