Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bleep
Original file line number Diff line number Diff line change
@@ -1 +1 @@
d3eea0d95bb3d98b7624e6dbc706b117216aa493
d5e93b09c79b14282a4900ada8d6c485ed8d6577
13 changes: 9 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
trie-hard:
strategy:
matrix:
toolchain: [nightly, 1.74, 1.80.0]
toolchain: [nightly, 1.74, 1.91.0]
runs-on: ubuntu-latest
# Only run on "pull_request" event for external PRs. This is to avoid
# duplicate builds for PRs created from internal branches.
Expand Down Expand Up @@ -36,18 +36,23 @@ jobs:
- name: Run cargo fmt
run: cargo fmt --all -- --check

- name: Run cargo build
run: cargo build

- name: Run cargo test
run: cargo test --verbose --lib --bins --tests --no-fail-fast
run: |
[[ ${{ matrix.toolchain }} != 1.74 ]] || (cargo test --verbose --lib --bins --tests --no-fail-fast)

# Need to run doc tests separately.
# (https://github.com/rust-lang/cargo/issues/6669)
- name: Run cargo doc test
run: cargo test --verbose --doc
run: |
[[ ${{ matrix.toolchain }} != 1.74 ]] || (cargo test --verbose --doc)

- name: Run cargo clippy
run: |
[[ ${{ matrix.toolchain }} == nightly ]] || cargo clippy --all-targets --all -- --allow=unknown-lints --deny=warnings

- name: Run cargo audit
run: |
[[ ${{ matrix.toolchain }} != 1.80.0 ]] || (cargo install cargo-audit && cargo audit)
[[ ${{ matrix.toolchain }} == 1.91.0 ]] || (cargo install cargo-audit && cargo audit)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ child_index = (( 0b00010 - 1) & 0b00011 ).count_ones() // = 1

The next node is the one at **index = 1** in the current node's child array.

The reason this works may actually easier to understand with a larger example. Consider this node from a larger trie (unrelated to our original example).
The reason this works may actually be easier to understand with a larger example. Consider this node from a larger trie (unrelated to our original example).

```rust
TrieNode {
Expand Down
134 changes: 131 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@
unsafe_code
)]
#![warn(rust_2018_idioms)]
#![no_std]

extern crate alloc;

#[cfg(test)]
extern crate std;

mod u256;

use std::{
use alloc::{
collections::{BTreeMap, BTreeSet, VecDeque},
ops::RangeFrom,
vec,
vec::Vec,
};
use core::ops::RangeFrom;

use u256::U256;
use self::u256::U256;

#[derive(Debug, Clone)]
#[repr(transparent)]
Expand Down Expand Up @@ -319,6 +327,35 @@ where
TrieHard::U256(trie) => TrieIter::U256(trie.prefix_search(prefix)),
}
}

/// Find the closest ancestor to the given key, where an ancestor is defined as the longest
/// string present in the trie that appears as a prefix of the given key.
///
/// ```
/// # use trie_hard::TrieHard;
/// let trie = ["dad", "ant", "and", "dot", "do"]
/// .into_iter()
/// .collect::<TrieHard<'_, _>>();
///
/// assert_eq!(
/// trie.ancestor("dada").map(|(_, v)| v),
/// Some("dad")
/// );
/// assert_eq!(
/// trie.ancestor("an").map(|(_, v)| v),
/// None
/// );
/// ```
pub fn ancestor<K: AsRef<[u8]>>(&self, key: K) -> Option<(&[u8], T)> {
match self {
TrieHard::U8(trie) => trie.ancestor(key),
TrieHard::U16(trie) => trie.ancestor(key),
TrieHard::U32(trie) => trie.ancestor(key),
TrieHard::U64(trie) => trie.ancestor(key),
TrieHard::U128(trie) => trie.ancestor(key),
TrieHard::U256(trie) => trie.ancestor(key),
}
}
}

/// Structure used for iterative over the contents of trie
Expand Down Expand Up @@ -583,6 +620,71 @@ macro_rules! trie_impls {

TrieIterSized::new(self, node_index)
}

/// Find the closest ancestor to the given key, where an ancestor is defined as the
/// longest string present in the trie that appears as a prefix of the given key.
///
/// ```
/// # use trie_hard::TrieHard;
/// let trie = ["dad", "ant", "and", "dot", "do"]
/// .into_iter()
/// .collect::<TrieHard<'_, _>>();
///
/// let TrieHard::U8(sized_trie) = trie else {
/// unreachable!()
/// };
///
/// assert_eq!(
/// sized_trie.ancestor("dada").map(|(_, v)| v),
/// Some("dad")
/// );
/// assert_eq!(
/// sized_trie.ancestor("an").map(|(_, v)| v),
/// None
/// );
/// ```
pub fn ancestor<K: AsRef<[u8]>>(
&self,
key: K,
) -> Option<(&[u8], T)> {
self.ancestor_recurse(0, key.as_ref(), self.nodes.get(0)?)
}

fn ancestor_recurse(
&self,
i: usize,
key: &[u8],
state: &TrieState<'a, T, $int_type>,
) -> Option<(&[u8], T)> {
match state {
TrieState::Leaf(k, value) => {
(
k.len() <= key.len()
&& k[i..] == key[i..k.len()]
).then_some((k, *value))
}
TrieState::Search(search) => {
let c = key.get(i)?;
let next_state_index = search.evaluate(*c, self)?;
self.ancestor_recurse(i + 1, key, &self.nodes[next_state_index])
}
TrieState::SearchOrLeaf(k, value, search) => {
// lambda to enable using `?` operator
let search = || {
let c = key.get(i)?;
let next_state_index = search.evaluate(*c, self)?;
self.ancestor_recurse(i + 1, key, &self.nodes[next_state_index])
};

search().or_else(|| {
(
k.len() <= key.len()
&& k[i..] == key[i..k.len()]
).then_some((k, *value))
})
}
}
}
}

impl<'a, T> TrieHardSized<'a, T, $int_type> where T: 'a + Copy {
Expand Down Expand Up @@ -924,4 +1026,30 @@ mod tests {
.collect::<Vec<_>>();
assert_eq!(emitted, output);
}

#[rstest]
#[case(&[], "", None)]
#[case(&[""], "", Some(""))]
#[case(&["aaa", "a", ""], "", Some(""))]
#[case(&["aaa", "a", ""], "a", Some("a"))]
#[case(&["aaa", "a", ""], "aa", Some("a"))]
#[case(&["aaa", "a", ""], "aab", Some("a"))]
#[case(&["aaa", "a", ""], "aaa", Some("aaa"))]
#[case(&["aaa", "a", ""], "b", Some(""))]
#[case(&["dad", "ant", "and", "dot", "do"], "d", None)]
#[case(&["dad", "ant", "and", "dot", "do"], "dad", Some("dad"))]
#[case(&["dad", "ant", "and", "dot", "do"], "dada", Some("dad"))]
#[case(&["dad", "ant", "and", "dot", "do"], "do", Some("do"))]
#[case(&["dad", "ant", "and", "dot", "do"], "dot", Some("dot"))]
#[case(&["dad", "ant", "and", "dot", "do"], "dob", Some("do"))]
#[case(&["dad", "ant", "and", "dot", "do"], "doto", Some("dot"))]
fn test_ancestor(
#[case] input: &[&str],
#[case] key: &str,
#[case] output: Option<&str>,
) {
let trie = input.iter().copied().collect::<TrieHard<'_, _>>();
let emitted = trie.ancestor(key).map(|(_, v)| v);
assert_eq!(emitted, output);
}
}
2 changes: 1 addition & 1 deletion src/u256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{
use core::{
cmp::Ordering,
ops::{
Add, AddAssign, BitAnd, BitOrAssign, Shl, ShlAssign, Sub, SubAssign,
Expand Down
Loading