Skip to content
Merged
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
23276869c057900df6b14d4db2d97fcc01fd47d0
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)
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "trie-hard"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/cloudflare/trie-hard"
Expand All @@ -11,13 +11,13 @@ Fast implementation of a trie data structure
"""

[dev-dependencies]
criterion = "0.5.1"
criterion = "0.8.1"
divan = "0.1.14"
once_cell = "1.19.0"
paste = "1.0.15"
phf = { version = "0.11.2", default-features = false }
radix_trie = "0.2.1"
rstest = "0.22.0"
phf = { version = "0.13.1", default-features = false }
radix_trie = "0.3.0"
rstest = "0.26.1"

[[bench]]
name = "divan_bench"
Expand All @@ -28,4 +28,4 @@ name = "criterion_bench"
harness = false

[build-dependencies]
phf_codegen = "0.11.2"
phf_codegen = "0.13.1"
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
5 changes: 2 additions & 3 deletions benches/criterion_bench.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::collections::HashSet;
use std::{collections::HashSet, hint::black_box};

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use criterion::{criterion_group, criterion_main, Criterion};
use once_cell::sync::Lazy;
use trie_hard::TrieHard;

/// This is a rip off of the benchmark suite for for
/// [`radix_trie`](https://github.com/michaelsproul/rust_radix_trie/blob/master/Cargo.toml)

const OW_1984: &str = include_str!("../data/1984.txt");
const SUN_RISING: &str = include_str!("../data/sun-rising.txt");
const RANDOM: &str = include_str!("../data/random.txt");
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