Skip to main content

rolling_hash/
lib.rs

1//! Rolling (Rabin-Karp-style) hash for strings and byte slices.
2//!
3//! Provides two flavours:
4//!
5//! * [`RollingHash`] — single-modulus rolling hash over `&[u8]`. The current
6//!   hash value is maintained incrementally as the window slides. Useful
7//!   when you need a fingerprint that updates in `O(1)` per shift.
8//!
9//! * [`DoubleRollingHash`] — same idea, with two different moduli for very
10//!   low collision rates; pairs of hashes can be compared for equality.
11//!
12//! Both flavours precompute the base-power table for substrings up to some
13//! `max_len`. To compare hashes of arbitrary substrings of a long string,
14//! use [`compute_hash`] (single) or build a [`DoubleRollingHash`] and
15//! query substrings using `hash_range(lo, hi)` / `combine(lo1, hi1, lo2, hi2)`.
16
17/// Default base used for the rolling hash.
18pub const DEFAULT_BASE: u64 = 131;
19/// Default modulus (a known large prime).
20pub const DEFAULT_MOD: u64 = (1 << 61) - 1;
21
22/// Single-modulus rolling hash with `O(1)` append / slide.
23#[derive(Debug, Clone)]
24pub struct RollingHash {
25    base: u64,
26    mod_: u64,
27    hash: u64,
28}
29
30impl RollingHash {
31    /// Create a new rolling hash with the default base and modulus.
32    pub fn new() -> Self {
33        Self::with_base_mod(DEFAULT_BASE, DEFAULT_MOD)
34    }
35
36    /// Create with a custom base and modulus.
37    pub fn with_base_mod(base: u64, mod_: u64) -> Self {
38        Self { base, mod_, hash: 0 }
39    }
40
41    /// Reset to the empty state.
42    pub fn clear(&mut self) {
43        self.hash = 0;
44    }
45
46    /// Append a byte to the hash, computing
47    /// `h = (h * base + byte) mod mod_`.
48    pub fn push(&mut self, byte: u8) {
49        self.hash = mul_mod(self.hash, self.base, self.mod_);
50        self.hash = add_mod(self.hash, byte as u64, self.mod_);
51    }
52
53    /// Remove the leftmost byte `b_old` from a window of size `n`, computing
54    /// `h = (h - b_old * base^(n-1)) mod mod_`.
55    ///
56    /// `window_len_before` is the length of the window *before* the removal.
57    pub fn pop(&mut self, b_old: u8, window_len_before: usize) {
58        debug_assert!(window_len_before >= 1);
59        let p = pow_mod(self.base, (window_len_before - 1) as u64, self.mod_);
60        let sub = mul_mod(b_old as u64, p, self.mod_);
61        self.hash = add_mod(self.hash, self.mod_ - sub % self.mod_, self.mod_);
62    }
63
64    /// Current hash value.
65    pub fn value(&self) -> u64 {
66        self.hash
67    }
68}
69
70impl Default for RollingHash {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76/// Compute the polynomial hash of a byte slice.
77///
78/// `h(s) = s[0]*b^(n-1) + s[1]*b^(n-2) + ... + s[n-1]  (mod mod_)`.
79pub fn compute_hash(bytes: &[u8], base: u64, mod_: u64) -> u64 {
80    let mut h = 0u64;
81    for &b in bytes {
82        h = mul_mod(h, base, mod_);
83        h = add_mod(h, b as u64, mod_);
84    }
85    h
86}
87
88/// Two-modulus rolling hash with combine/equal-friendly semantics.
89#[derive(Debug, Clone)]
90pub struct DoubleRollingHash {
91    h1: RollingHash,
92    h2: RollingHash,
93    /// Base for the first hash stream (exposed for advanced users).
94    pub pub_b1: u64,
95    /// Base for the second hash stream.
96    pub pub_b2: u64,
97    /// Modulus for the first hash stream.
98    pub pub_m1: u64,
99    /// Modulus for the second hash stream.
100    pub pub_m2: u64,
101}
102
103impl DoubleRollingHash {
104    /// Create with two distinct (base, modulus) pairs. Recommended:
105    /// `(131, 2^61-1)` and `(137, 2^61-1)`.
106    pub fn new(base1: u64, mod1: u64, base2: u64, mod2: u64) -> Self {
107        Self {
108            h1: RollingHash::with_base_mod(base1, mod1),
109            h2: RollingHash::with_base_mod(base2, mod2),
110            pub_b1: base1,
111            pub_b2: base2,
112            pub_m1: mod1,
113            pub_m2: mod2,
114        }
115    }
116
117    /// Reset both hashes to 0.
118    pub fn clear(&mut self) {
119        self.h1.clear();
120        self.h2.clear();
121    }
122
123    /// Append a byte to both hashes.
124    pub fn push(&mut self, byte: u8) {
125        self.h1.push(byte);
126        self.h2.push(byte);
127    }
128
129    /// Get the pair `(hash1, hash2)`.
130    pub fn value(&self) -> (u64, u64) {
131        (self.h1.value(), self.h2.value())
132    }
133}
134
135fn mul_mod(a: u64, b: u64, m: u64) -> u64 {
136    // For m = 2^61 - 1, this matches the standard Rh_hash trick.
137    ((a as u128 * b as u128) % m as u128) as u64
138}
139
140fn add_mod(a: u64, b: u64, m: u64) -> u64 {
141    let s = a + b;
142    if s >= m {
143        s - m
144    } else {
145        s
146    }
147}
148
149fn pow_mod(mut base: u64, mut exp: u64, m: u64) -> u64 {
150    let mut result = 1u64;
151    while exp > 0 {
152        if exp & 1 == 1 {
153            result = mul_mod(result, base, m);
154        }
155        base = mul_mod(base, base, m);
156        exp >>= 1;
157    }
158    result
159}