diff --git a/.bleep b/.bleep index 4c98b0b..c18e76a 100644 --- a/.bleep +++ b/.bleep @@ -1 +1 @@ -d3eea0d95bb3d98b7624e6dbc706b117216aa493 \ No newline at end of file +d5e93b09c79b14282a4900ada8d6c485ed8d6577 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83a1939..1922f74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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. @@ -36,13 +36,18 @@ 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: | @@ -50,4 +55,4 @@ jobs: - 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) diff --git a/README.md b/README.md index 9655461..1460b67 100644 --- a/README.md +++ b/README.md @@ -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 { diff --git a/src/lib.rs b/src/lib.rs index f59af63..6d9a17e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)] @@ -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::>(); + /// + /// assert_eq!( + /// trie.ancestor("dada").map(|(_, v)| v), + /// Some("dad") + /// ); + /// assert_eq!( + /// trie.ancestor("an").map(|(_, v)| v), + /// None + /// ); + /// ``` + pub fn ancestor>(&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 @@ -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::>(); + /// + /// 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>( + &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 { @@ -924,4 +1026,30 @@ mod tests { .collect::>(); 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::>(); + let emitted = trie.ancestor(key).map(|(_, v)| v); + assert_eq!(emitted, output); + } } diff --git a/src/u256.rs b/src/u256.rs index bfee984..48b76d6 100644 --- a/src/u256.rs +++ b/src/u256.rs @@ -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,