Skip to content

Commit

Permalink
refactor(sequence): use patricia_tree (#995)
Browse files Browse the repository at this point in the history
The `patricia_tree` crate is more maintained than the currently-used
`radix_trie` crate and uses less memory because the radix_trie crate has
radix 16 while the patricia tree has radix 2. Computation cost
comparison unknown.. but reducing memory usage is a good thing!

- https://crates.io/crates/patricia_tree
- https://crates.io/crates/radix_trie

Comparison of memory usage:

```
(defseq woa (O-(a b c d e f g h i j)))

patricia_tree:  200 MB
radix_tree:    1700 MB
```

Since overlapped-key sequences generate a sequence for all permutations
of the key combinations, having 10 simultaneous keys generates `10! =
3.6M` sequences. I should probably limit this to some reasonable number
like 6...
  • Loading branch information
jtroo committed May 4, 2024
1 parent 097cfd4 commit f775a2a
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 27 deletions.
18 changes: 17 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ log = { version = "0.4.8", default_features = false }
anyhow = "1"
parking_lot = "0.12"
once_cell = "1"
radix_trie = "0.2"
patricia_tree = "0.8"
rustc-hash = "1.1.0"
miette = { version = "5.7.0", features = ["fancy"] }
thiserror = "1.0.38"
Expand All @@ -26,6 +26,7 @@ thiserror = "1.0.38"
# Otherwise any changes to the local files will not reflect in the compiled
# binary.
kanata-keyberon = { path = "../keyberon" }
bytemuck = "1.15.0"

[features]
cmd = []
Expand Down
49 changes: 24 additions & 25 deletions parser/src/trie.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Wrapper around a trie type for (hopefully) easier swapping of libraries if desired.
use radix_trie::TrieCommon;
use bytemuck::cast_slice;
use patricia_tree::map::PatriciaMap;

pub type TrieKey = Vec<u16>;
pub type TrieKeyElement = u16;
pub type TrieKey = Vec<TrieKeyElement>;
pub type TrieVal = (u8, u16);

#[derive(Debug, Clone)]
pub struct Trie {
inner: radix_trie::Trie<TrieKey, TrieVal>,
inner: patricia_tree::map::PatriciaMap<TrieVal>,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
Expand All @@ -25,45 +27,42 @@ impl Default for Trie {
}
}

fn key_len(k: &TrieKey) -> usize {
debug_assert!(std::mem::size_of::<TrieKeyElement>() == 2 * std::mem::size_of::<u8>());
k.len() * 2
}

impl Trie {
pub fn new() -> Self {
Self {
inner: radix_trie::Trie::new(),
inner: PatriciaMap::new(),
}
}

pub fn ancestor_exists(&self, key: &TrieKey) -> bool {
self.inner.get_ancestor(key).is_some()
self.inner
.get_longest_common_prefix(cast_slice(key))
.is_some()
}

pub fn descendant_exists(&self, key: &TrieKey) -> bool {
self.inner.get_raw_descendant(key).is_some()
// Length of the [u8] interpretation of the [u16] key is doubled.
self.inner.longest_common_prefix_len(cast_slice(key)) == key_len(key)
}

pub fn insert(&mut self, key: TrieKey, val: TrieVal) {
self.inner.insert(key, val);
self.inner.insert(cast_slice(&key), val);
}

pub fn get_or_descendant_exists(&self, key: &TrieKey) -> GetOrDescendentExistsResult {
let descendant = self.inner.get_raw_descendant(key);
match descendant {
let mut descendants = self.inner.iter_prefix(cast_slice(key));
match descendants.next() {
None => NotInTrie,
Some(subtrie) => {
// If the key exists in this subtrie, returns the value. Otherwise returns
// KeyInTrie.
match subtrie.key() {
Some(stkey) => {
if key == stkey {
HasValue(*subtrie.value().expect("node has value"))
} else {
InTrie
}
}
None => {
// Note: None happens if there are multiple children. The sequence is still
// in the trie.
InTrie
}
Some(descendant) => {
if descendant.0.len() == key_len(key) {
HasValue(*descendant.1)
} else {
InTrie
}
}
}
Expand Down

0 comments on commit f775a2a

Please sign in to comment.