///|
/// Pair a source string with its stable pre-hash during construction.
priv struct StringHash {
key : String
hash : Int
}
///| Build an exact static string set. Duplicate source strings and distinct
///|
/// strings that collide in the 31-bit stable hash are both rejected, rather
/// than silently constructing an ambiguous table.
pub fn StaticStringSet::from_keys(
keys : Array[String],
) -> Result[StaticStringSet, MphfError] {
let hashes = match unique_string_hashes(keys) {
Ok(value) => value
Err(error) => return Err(error)
}
let mphf = match Mphf::build(hashes) {
Ok(value) => value
Err(error) => return Err(error)
}
let keys_by_slot = Array::make(keys.length(), "")
for key in keys {
let slot = mphf.slot_of_hash(stable_string_hash(key)).unwrap()
keys_by_slot[slot] = key
}
Ok({ mphf, keys_by_slot })
}
///| Test exact string membership; a stable-hash collision is ruled out by the
///|
/// slot-resident source string comparison.
pub fn StaticStringSet::contains(self : StaticStringSet, key : String) -> Bool {
match self.mphf.slot_of_hash(stable_string_hash(key)) {
Some(slot) => self.keys_by_slot[slot] == key
None => false
}
}
///|
/// Return the number of source strings in this immutable set.
pub fn StaticStringSet::len(self : StaticStringSet) -> Int {
self.keys_by_slot.length()
}
///|
/// Return construction details for observability.
pub fn StaticStringSet::stats(self : StaticStringSet) -> MphfStats {
self.mphf.stats()
}
///|
/// Build a checked immutable string-to-integer map.
pub fn StaticStringIntMap::from_entries(
entries : Array[StringIntEntry],
) -> Result[StaticStringIntMap, MphfError] {
let source_keys : Array[String] = []
for entry in entries {
source_keys.push(entry.key)
}
let hashes = match unique_string_hashes(source_keys) {
Ok(value) => value
Err(error) => return Err(error)
}
let mphf = match Mphf::build(hashes) {
Ok(value) => value
Err(error) => return Err(error)
}
let keys_by_slot = Array::make(entries.length(), "")
let values_by_slot = Array::make(entries.length(), 0)
for entry in entries {
let slot = mphf.slot_of_hash(stable_string_hash(entry.key)).unwrap()
keys_by_slot[slot] = entry.key
values_by_slot[slot] = entry.value
}
Ok({ mphf, keys_by_slot, values_by_slot })
}
///|
/// Retrieve a value only when both the MPHF slot and original string match.
pub fn StaticStringIntMap::get(self : StaticStringIntMap, key : String) -> Int? {
match self.mphf.slot_of_hash(stable_string_hash(key)) {
Some(slot) if self.keys_by_slot[slot] == key =>
Some(self.values_by_slot[slot])
_ => None
}
}
///|
/// Test exact membership without exposing a value.
pub fn StaticStringIntMap::contains_key(
self : StaticStringIntMap,
key : String,
) -> Bool {
self.get(key) is Some(_)
}
///|
/// Count entries in this immutable map.
pub fn StaticStringIntMap::len(self : StaticStringIntMap) -> Int {
self.keys_by_slot.length()
}
///| Sort hashes and reject both duplicate strings and distinct hash collisions
///|
/// before the integer MPHF builder sees its input.
fn unique_string_hashes(keys : Array[String]) -> Result[Array[Int], MphfError] {
if keys.length() == 0 {
return Err(EmptyInput)
}
let pairs : Array[StringHash] = []
for key in keys {
pairs.push({ key, hash: stable_string_hash(key) })
}
pairs.sort_by((left, right) => left.hash.compare(right.hash))
for index in 1..