use super::config::{
TrieStrategy,
ZiporaTrieConfig,
};
use super::storage::{CritBitNode, PatriciaNode, TrieStorage};
use crate::StateId;
use crate::containers::FastVec;
use crate::containers::specialized::UintVector;
use crate::error::Result;
use crate::fsa::traits::{
FiniteStateAutomaton, PrefixIterable, Trie, TrieStats,
};
use crate::memory::SecureMemoryPool;
use crate::memory::cache_layout::{CacheLayoutConfig, CacheOptimizedAllocator};
use crate::succinct::RankSelectOps;
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
#[derive(Debug)]
pub struct ZiporaTrie<R = crate::succinct::RankSelectInterleaved256>
where
R: RankSelectOps,
{
pub(super) config: ZiporaTrieConfig,
pub(super) storage: TrieStorage<R>,
pub(super) stats: TrieStats,
pub(super) stats_dirty: bool,
pub(super) cache_allocator: Option<CacheOptimizedAllocator>,
pub(super) _memory_pool: Option<Arc<SecureMemoryPool>>,
pub(super) root_state: StateId,
}
impl<R> ZiporaTrie<R>
where
R: RankSelectOps + Default,
{
pub fn new() -> Self {
Self::with_config(ZiporaTrieConfig::default())
}
pub fn with_config(config: ZiporaTrieConfig) -> Self {
let cache_allocator = if config.cache_optimization {
Some(CacheOptimizedAllocator::new(CacheLayoutConfig::default()))
} else {
None
};
let storage = Self::create_storage(&config);
Self {
config,
storage,
stats: TrieStats::new(),
stats_dirty: false,
cache_allocator,
_memory_pool: None,
root_state: 0,
}
}
fn create_storage(config: &ZiporaTrieConfig) -> TrieStorage<R> {
match &config.trie_strategy {
TrieStrategy::Patricia { .. } => TrieStorage::Patricia {
nodes: FastVec::new(),
edge_data: FastVec::new(),
compressed_paths: HashMap::new(),
},
TrieStrategy::CriticalBit { .. } => TrieStorage::CriticalBit {
nodes: FastVec::new(),
keys: FastVec::new(),
critical_cache: HashMap::new(),
},
TrieStrategy::DoubleArray {
initial_capacity, ..
} => {
let mut base = match FastVec::with_capacity(*initial_capacity) {
Ok(vec) => vec,
Err(_) => {
FastVec::with_capacity(1).unwrap_or_else(|_| FastVec::new())
}
};
let mut check = match FastVec::with_capacity(*initial_capacity) {
Ok(vec) => vec,
Err(_) => {
FastVec::with_capacity(1).unwrap_or_else(|_| FastVec::new())
}
};
let _ = base.push(1); let _ = check.push(0);
TrieStorage::DoubleArray {
base,
check,
free_list: VecDeque::new(),
state_count: 1, }
}
TrieStrategy::Louds { .. } => TrieStorage::Louds {
louds: R::default(),
is_link: R::default(),
next_link: UintVector::new(),
label_data: FastVec::new(),
core_data: FastVec::new(),
next_trie: None,
},
TrieStrategy::CompressedSparse { .. } => {
TrieStorage::CompressedSparse(crate::fsa::cspp_trie::CsppTrie::new(4))
}
}
}
#[inline]
pub fn root(&self) -> StateId {
self.root_state
}
pub fn stats(&self) -> TrieStats {
let mut stats = self.stats.clone();
stats.memory_usage = self.memory_usage();
if stats.num_keys > 0 {
stats.bits_per_key = (stats.memory_usage as f64 * 8.0) / stats.num_keys as f64;
} else {
stats.bits_per_key = 0.0;
}
stats.num_states = if stats.num_keys == 0 {
0
} else {
match &self.storage {
TrieStorage::Patricia { nodes, .. } => nodes.len(),
TrieStorage::CriticalBit { nodes, .. } => nodes.len(),
TrieStorage::DoubleArray { check, .. } => {
1 + check.iter().skip(1).filter(|&&c| c != 0).count()
}
TrieStorage::Louds { .. } => 1, TrieStorage::CompressedSparse(cspp) => cspp.total_states(),
}
};
stats.num_transitions = match &self.storage {
TrieStorage::Patricia { nodes, .. } => nodes.iter().map(|n| n.children.len()).sum(),
TrieStorage::CriticalBit { .. } => 0, TrieStorage::DoubleArray { base, check, .. } => {
const STATE_MASK: u32 = 0x3FFF_FFFF;
const TERMINAL_FLAG: u32 = 0x4000_0000;
let mut transition_count = 0;
for i in 1..check.len() {
let check_val = check[i];
if check_val != 0 {
if (check_val & STATE_MASK) == 0 {
if (check_val & TERMINAL_FLAG) != 0 || (i < base.len() && base[i] != 0)
{
transition_count += 1;
}
} else {
transition_count += 1;
}
}
}
transition_count
}
TrieStorage::Louds { .. } => 0, TrieStorage::CompressedSparse(_cspp) => 0,
};
stats
}
pub fn config(&self) -> &ZiporaTrieConfig {
&self.config
}
pub fn is_cache_optimized(&self) -> bool {
self.cache_allocator.is_some()
}
pub fn state_count(&self) -> usize {
match &self.storage {
TrieStorage::Patricia { nodes, .. } => nodes.len(),
TrieStorage::CriticalBit { nodes, .. } => nodes.len(),
TrieStorage::DoubleArray { state_count, .. } => *state_count,
TrieStorage::Louds { label_data, .. } => label_data.len(),
TrieStorage::CompressedSparse(cspp) => cspp.total_states(),
}
}
pub fn memory_usage(&self) -> usize {
if self.stats.num_keys == 0 {
return 0;
}
match &self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => {
nodes.capacity() * std::mem::size_of::<PatriciaNode>()
+ edge_data.capacity()
+ compressed_paths.capacity() * 64 }
TrieStorage::CriticalBit {
nodes,
keys,
critical_cache,
} => {
nodes.capacity() * std::mem::size_of::<CritBitNode>()
+ keys.capacity() * 32 + critical_cache.capacity() * 9 }
TrieStorage::DoubleArray { base, check, .. } => {
base.len() * 4 + check.len() * 4
}
TrieStorage::Louds {
label_data,
core_data,
..
} => {
label_data.capacity() + core_data.capacity() + 1024 }
TrieStorage::CompressedSparse(cspp) => cspp.total_states() * 4,
}
}
pub fn insert(&mut self, key: &[u8]) -> Result<()> {
let _state_id = <Self as Trie>::insert(self, key)?;
self.stats_dirty = true;
Ok(())
}
#[inline]
pub fn contains(&self, key: &[u8]) -> bool {
<Self as Trie>::contains(self, key)
}
pub fn remove(&mut self, key: &[u8]) -> Result<bool> {
match &mut self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => {
let removed =
Self::remove_patricia_actual(nodes, edge_data, compressed_paths, key)?;
if removed {
self.stats.num_keys = self.stats.num_keys.saturating_sub(1);
self.stats_dirty = true;
}
Ok(removed)
}
TrieStorage::DoubleArray { base, check, .. } => {
let state = Self::lookup_node_id_double_array(base, check, key);
if let Some(state_id) = state {
const TERMINAL_BIT: u32 = 0x8000_0000;
base[state_id as usize] &= !TERMINAL_BIT;
self.stats.num_keys = self.stats.num_keys.saturating_sub(1);
self.stats_dirty = true;
Ok(true)
} else {
Ok(false)
}
}
_ => Ok(false),
}
}
#[inline]
pub fn len(&self) -> usize {
self.stats.num_keys
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn keys(&self) -> Vec<Vec<u8>> {
match &self.storage {
TrieStorage::Patricia {
nodes,
edge_data: _,
compressed_paths,
} => Self::keys_patricia_actual(nodes, compressed_paths),
TrieStorage::Louds { label_data, .. } => Self::keys_louds_actual(label_data),
TrieStorage::DoubleArray { base, check, .. } => {
Self::keys_double_array_actual(base, check)
}
TrieStorage::CompressedSparse(_cspp) => Vec::new(), _ => {
Vec::new()
}
}
}
pub fn keys_with_prefix(&self, prefix: &[u8]) -> Vec<Vec<u8>> {
match &self.storage {
TrieStorage::Patricia {
nodes,
edge_data: _,
compressed_paths,
} => Self::keys_with_prefix_patricia_actual(nodes, compressed_paths, prefix),
TrieStorage::Louds { label_data, .. } => {
Self::keys_with_prefix_louds_actual(label_data, prefix)
}
TrieStorage::DoubleArray { base, check, .. } => {
Self::keys_with_prefix_double_array_actual(base, check, prefix)
}
TrieStorage::CompressedSparse(_cspp) => Vec::new(), _ => {
Vec::new()
}
}
}
pub fn iter_all(&self) -> TrieIterator {
let keys = self.keys();
TrieIterator::with_keys(keys)
}
pub fn iter_prefix(&self, prefix: &[u8]) -> TrieIterator {
let keys = self.keys_with_prefix(prefix);
TrieIterator::with_keys(keys)
}
pub fn capacity(&self) -> usize {
match &self.storage {
TrieStorage::Patricia { nodes, .. } => {
nodes.capacity().max(nodes.len() * 2)
}
TrieStorage::CriticalBit { nodes, .. } => nodes.capacity().max(nodes.len() * 2),
TrieStorage::DoubleArray { base, .. } => {
base.capacity().max(base.len())
}
TrieStorage::Louds { label_data, .. } => {
label_data.capacity().max(label_data.len() * 2)
}
TrieStorage::CompressedSparse(cspp) => cspp.total_states() * 4,
}
}
pub fn memory_stats(&self) -> (usize, usize, usize) {
match &self.storage {
TrieStorage::DoubleArray { base, check, .. } => {
let base_memory = base.capacity() * std::mem::size_of::<u32>();
let check_memory = check.capacity() * std::mem::size_of::<u32>();
(base_memory, check_memory, 0)
}
_ => {
let total_memory = self.memory_usage();
(total_memory / 2, total_memory / 2, 0)
}
}
}
pub fn insert_and_get_node_id(&mut self, key: &[u8]) -> Result<StateId> {
match &mut self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => {
let node_id = Self::insert_patricia_actual(
nodes,
edge_data,
compressed_paths,
key,
&mut self.stats.num_keys,
)?;
Ok(node_id)
}
TrieStorage::Louds {
louds,
is_link,
next_link,
label_data,
core_data,
next_trie,
} => {
let node_id = Self::insert_louds(
louds, is_link, next_link, label_data, core_data, next_trie, key,
)?;
self.stats.num_keys += 1;
Ok(node_id)
}
TrieStorage::DoubleArray {
base,
check,
free_list,
state_count,
} => {
let node_id = Self::insert_double_array(
base,
check,
free_list,
state_count,
key,
&mut self.stats.num_keys,
)?;
self.stats_dirty = true;
Ok(node_id)
}
_ => {
self.stats.num_keys += 1;
Ok(0)
}
}
}
pub fn lookup_node_id(&self, key: &[u8]) -> Option<StateId> {
match &self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => Self::lookup_node_id_patricia_actual(nodes, edge_data, compressed_paths, key),
TrieStorage::Louds { .. } => None,
TrieStorage::DoubleArray { base, check, .. } => {
Self::lookup_node_id_double_array(base, check, key)
}
_ => None,
}
}
fn lookup_node_id_double_array(
base: &FastVec<u32>,
check: &FastVec<u32>,
key: &[u8],
) -> Option<StateId> {
const TERMINAL_BIT: u32 = 0x8000_0000;
const VALUE_MASK: u32 = 0x7FFF_FFFF;
const FREE_BIT: u32 = 0x8000_0000;
if base.is_empty() {
return None;
}
let mut current_state = 0u32;
if key.is_empty() {
let base_val = base[0];
return if (base_val & TERMINAL_BIT) != 0 {
Some(0)
} else {
None
};
}
for &symbol in key {
let base_value = base[current_state as usize] & VALUE_MASK;
let next_state = base_value.saturating_add(symbol as u32);
if next_state as usize >= check.len() {
return None;
}
let check_val = check[next_state as usize];
let is_free = (check_val & FREE_BIT) != 0;
if is_free || check_val != current_state {
return None;
}
current_state = next_state;
}
let base_val = base[current_state as usize];
if (base_val & TERMINAL_BIT) != 0 {
Some(current_state)
} else {
None
}
}
pub fn restore_string(&self, state_id: StateId) -> Option<Vec<u8>> {
match &self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => Self::restore_string_patricia_actual(nodes, edge_data, compressed_paths, state_id),
TrieStorage::Louds { label_data, .. } => {
Self::restore_string_louds(label_data, state_id)
}
TrieStorage::DoubleArray { base, check, .. } => {
Self::restore_string_double_array(base, check, state_id)
}
_ => None,
}
}
fn restore_string_double_array(
base: &FastVec<u32>,
check: &FastVec<u32>,
state_id: StateId,
) -> Option<Vec<u8>> {
const VALUE_MASK: u32 = 0x7FFF_FFFF;
const FREE_BIT: u32 = 0x8000_0000;
if state_id as usize >= check.len() {
return None;
}
let mut symbols = Vec::new();
let mut current = state_id;
while current != 0 {
let check_val = check[current as usize];
if (check_val & FREE_BIT) != 0 {
return None; }
let parent = check_val; let parent_base = base[parent as usize] & VALUE_MASK;
if current < parent_base {
return None; }
let symbol = (current - parent_base) as u8;
symbols.push(symbol);
current = parent;
}
symbols.reverse();
Some(symbols)
}
pub fn is_free_double_array(&self, state: StateId) -> bool {
match &self.storage {
TrieStorage::DoubleArray { check, .. } => {
const FREE_BIT: u32 = 0x8000_0000;
if state == 0 {
return false;
}
if (state as usize) >= check.len() {
return true; }
(check[state as usize] & FREE_BIT) != 0
}
_ => false,
}
}
pub fn get_parent_double_array(&self, state: StateId) -> StateId {
match &self.storage {
TrieStorage::DoubleArray { check, .. } => {
const VALUE_MASK: u32 = 0x7FFF_FFFF; if (state as usize) < check.len() {
check[state as usize] & VALUE_MASK
} else {
0 }
}
_ => 0,
}
}
pub fn get_base_double_array(&self, state: StateId) -> u32 {
match &self.storage {
TrieStorage::DoubleArray { base, .. } => {
const VALUE_MASK: u32 = 0x7FFF_FFFF; if (state as usize) < base.len() {
base[state as usize] & VALUE_MASK
} else {
0
}
}
_ => 0,
}
}
pub fn get_check_double_array(&self, state: StateId) -> u32 {
match &self.storage {
TrieStorage::DoubleArray { check, .. } => {
const VALUE_MASK: u32 = 0x7FFF_FFFF; if (state as usize) < check.len() {
check[state as usize] & VALUE_MASK
} else {
0
}
}
_ => 0,
}
}
pub fn shrink_to_fit(&mut self) {
if let TrieStorage::DoubleArray { base, check, .. } = &mut self.storage {
let mut actual_len = base.len();
while actual_len > 1 {
let idx = actual_len - 1;
if check[idx] != 0 || base[idx] != 0 {
break;
}
actual_len -= 1;
}
const NIL_STATE: u32 = 0x7FFF_FFFF;
const VALUE_MASK: u32 = 0x7FFF_FFFF;
for i in 0..actual_len {
let base_val = base[i] & VALUE_MASK;
if base_val == NIL_STATE {
base[i] = (base[i] & !VALUE_MASK) | 1; }
}
if actual_len < base.len() {
let _ = base.resize(actual_len, 0).ok();
let _ = check.resize(actual_len, 0).ok();
}
let _ = base.shrink_to_fit();
let _ = check.shrink_to_fit();
}
}
fn restore_string_louds(label_data: &FastVec<u8>, state_id: StateId) -> Option<Vec<u8>> {
let start_pos = state_id as usize;
if start_pos >= label_data.len() {
return None;
}
let mut key = Vec::new();
for i in start_pos..label_data.len() {
if label_data[i] == 0 {
break;
}
key.push(label_data[i]);
}
if key.is_empty() { None } else { Some(key) }
}
}
pub struct TrieIterator {
keys: Vec<Vec<u8>>,
index: usize,
}
impl Default for TrieIterator {
fn default() -> Self {
Self::new()
}
}
impl TrieIterator {
pub fn new() -> Self {
TrieIterator {
keys: Vec::new(),
index: 0,
}
}
pub fn with_keys(keys: Vec<Vec<u8>>) -> Self {
TrieIterator { keys, index: 0 }
}
}
impl Iterator for TrieIterator {
type Item = Vec<u8>;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.keys.len() {
let key = self.keys[self.index].clone();
self.index += 1;
Some(key)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub total_bytes: usize,
pub allocated_bytes: usize,
pub peak_bytes: usize,
}
impl<R> Clone for ZiporaTrie<R>
where
R: RankSelectOps + Default + Clone,
{
fn clone(&self) -> Self {
let mut new_trie = Self::with_config(self.config.clone());
let keys = self.keys();
for key in keys {
let _ = new_trie.insert(&key);
}
new_trie.stats = self.stats.clone();
new_trie
}
}
impl<R> Trie for ZiporaTrie<R>
where
R: RankSelectOps + Default,
{
fn insert(&mut self, key: &[u8]) -> Result<StateId> {
let result = match &mut self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => Self::insert_patricia(
nodes,
edge_data,
compressed_paths,
key,
&mut self.stats.num_keys,
),
TrieStorage::CriticalBit {
nodes,
keys,
critical_cache,
} => Self::insert_critical_bit(nodes, keys, critical_cache, key),
TrieStorage::DoubleArray {
base,
check,
free_list,
state_count,
} => Self::insert_double_array(
base,
check,
free_list,
state_count,
key,
&mut self.stats.num_keys,
),
TrieStorage::Louds {
louds,
is_link,
next_link,
label_data,
core_data,
next_trie,
} => Self::insert_louds(
louds, is_link, next_link, label_data, core_data, next_trie, key,
),
TrieStorage::CompressedSparse(cspp) => {
let (is_new, _) = cspp.insert(key);
if is_new {
self.stats.num_keys += 1;
}
Ok(0)
}
}?;
Ok(result)
}
fn contains(&self, key: &[u8]) -> bool {
match &self.storage {
TrieStorage::Patricia {
nodes,
edge_data,
compressed_paths,
} => self.contains_patricia(nodes, edge_data, compressed_paths, key),
TrieStorage::CriticalBit {
nodes,
keys,
critical_cache,
} => self.contains_critical_bit(nodes, keys, critical_cache, key),
TrieStorage::DoubleArray { base, check, .. } => {
self.contains_double_array(base, check, key)
}
TrieStorage::Louds {
louds,
is_link,
next_link,
label_data,
core_data,
next_trie,
} => self.contains_louds(
louds, is_link, next_link, label_data, core_data, next_trie, key,
),
TrieStorage::CompressedSparse(cspp) => cspp.contains(key),
}
}
fn len(&self) -> usize {
self.stats.num_keys
}
fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<R> FiniteStateAutomaton for ZiporaTrie<R>
where
R: RankSelectOps + Default,
{
fn root(&self) -> StateId {
self.root_state
}
fn is_final(&self, state: StateId) -> bool {
match &self.storage {
TrieStorage::Patricia { nodes, .. } => nodes
.get(state as usize)
.map(|n| n.is_final)
.unwrap_or(false),
TrieStorage::CriticalBit { nodes, .. } => nodes
.get(state as usize)
.map(|n| n.is_final)
.unwrap_or(false),
TrieStorage::DoubleArray { base, .. } => {
const TERMINAL_BIT: u32 = 0x8000_0000;
base.get(state as usize)
.map(|b| (b & TERMINAL_BIT) != 0)
.unwrap_or(false)
}
TrieStorage::Louds { .. } => {
false
}
TrieStorage::CompressedSparse(_cspp) => false, }
}
fn transition(&self, state: StateId, symbol: u8) -> Option<StateId> {
match &self.storage {
TrieStorage::Patricia { nodes, .. } => {
let node = nodes.get(state as usize)?;
node.children
.binary_search_by_key(&symbol, |(s, _)| *s)
.ok()
.map(|idx| node.children[idx].1)
}
TrieStorage::CriticalBit { nodes: _, .. } => {
None
}
TrieStorage::DoubleArray { base, check, .. } => {
const VALUE_MASK: u32 = 0x7FFF_FFFF;
let base_value = base.get(state as usize)? & VALUE_MASK;
let next_state = base_value.saturating_add(symbol as u32);
if let Some(check_value) = check.get(next_state as usize) {
if *check_value == state {
Some(next_state)
} else {
None
}
} else {
None
}
}
TrieStorage::Louds { .. } => {
None
}
TrieStorage::CompressedSparse(_cspp) => None, }
}
fn transitions(&self, state: StateId) -> Vec<(u8, StateId)> {
match &self.storage {
TrieStorage::Patricia { nodes, .. } => {
if let Some(node) = nodes.get(state as usize) {
node.children.clone()
} else {
Vec::new()
}
}
TrieStorage::DoubleArray { base, check, .. } => {
let Some(&base_val) = base.get(state as usize) else {
return Vec::new();
};
if base_val == 0 {
return Vec::new();
}
const STATE_MASK: u32 = 0x3FFF_FFFF;
const TERMINAL_FLAG: u32 = 0x4000_0000;
(0u8..=255u8)
.filter_map(|symbol| {
let next_state = base_val.saturating_add(symbol as u32);
if (next_state as usize) >= check.len() {
return None;
}
let check_val = check[next_state as usize];
let is_valid_child = if state == 0 {
(check_val & STATE_MASK) == 0
&& ((check_val & TERMINAL_FLAG) != 0
|| ((next_state as usize) < base.len()
&& base[next_state as usize] != 0))
} else {
check_val != 0 && (check_val & STATE_MASK) == state
};
if is_valid_child {
Some((symbol, next_state))
} else {
None
}
})
.collect()
}
_ => Vec::new(),
}
}
}
impl<R> PrefixIterable for ZiporaTrie<R>
where
R: RankSelectOps + Default,
{
fn iter_prefix(&self, prefix: &[u8]) -> Box<dyn Iterator<Item = Vec<u8>> + '_> {
Box::new(self.iter_prefix(prefix))
}
fn iter_all(&self) -> Box<dyn Iterator<Item = Vec<u8>> + '_> {
Box::new(self.iter_all())
}
}
impl<R> Default for ZiporaTrie<R>
where
R: RankSelectOps + Default,
{
fn default() -> Self {
Self::new()
}
}