#trie #louds #prefix-tree #succinct

trie-rs

基于 LOUDS 的内存高效 trie(前缀树)和映射库

6 个版本 (3 个重大变更)

0.4.2 2024 年 5 月 12 日
0.4.1 2024 年 5 月 12 日
0.4.0 2024 年 4 月 30 日
0.3.0 2024 年 4 月 15 日
0.1.1 2019 年 5 月 3 日

压缩 中排名 16

Download history 4327/week @ 2024-05-03 6714/week @ 2024-05-10 13560/week @ 2024-05-17 10543/week @ 2024-05-24 5872/week @ 2024-05-31 12352/week @ 2024-06-07 7224/week @ 2024-06-14 6837/week @ 2024-06-21 9718/week @ 2024-06-28 7157/week @ 2024-07-05 4611/week @ 2024-07-12 5776/week @ 2024-07-19 4795/week @ 2024-07-26 5187/week @ 2024-08-02 11046/week @ 2024-08-09 8160/week @ 2024-08-16

每月 30,296 次下载
37 包中使用 37 (直接使用 18 个)

MIT/Apache

1MB
1.5K SLoC

trie-rs

基于 LOUDS 的内存高效 trie(前缀树)和映射库。

主 API 文档 | 发布 API 文档 | 基准测试结果 | 变更日志

GitHub Actions Status Crates.io Version Crates.io Downloads Minimum rustc version License: MIT License: Apache 2.0

快速入门

要使用 trie-rs,请将以下内容添加到您的 Cargo.toml 文件中

[dependencies]
trie-rs = "0.4.2"

使用概述

use std::str;
use trie_rs::TrieBuilder;

let mut builder = TrieBuilder::new();  // Inferred `TrieBuilder<u8>` automatically
builder.push("すし");
builder.push("すしや");
builder.push("すしだね");
builder.push("すしづめ");
builder.push("すしめし");
builder.push("すしをにぎる");
builder.push("すし");  // Word `push`ed twice is just ignored.
builder.push("🍣");

let trie = builder.build();

// exact_match(): Find a word exactly match to query.
assert_eq!(trie.exact_match("すし"), true);
assert_eq!(trie.exact_match("🍣"), true);
assert_eq!(trie.exact_match("🍜"), false);

// predictive_search(): Find words which include `query` as their prefix.
let results_in_u8s: Vec<Vec<u8>> = trie.predictive_search("すし").collect();
let results_in_str: Vec<String> = trie.predictive_search("すし").collect();
assert_eq!(
    results_in_str,
    vec![
        "すし",
        "すしだね",
        "すしづめ",
        "すしめし",
        "すしや",
        "すしをにぎる"
    ]  // Sorted by `Vec<u8>`'s order
);

// common_prefix_search(): Find words which is included in `query`'s prefix.
let results_in_u8s: Vec<Vec<u8>> = trie.common_prefix_search("すしや").collect();
let results_in_str: Vec<String> = trie.common_prefix_search("すしや").collect();
assert_eq!(
    results_in_str,
    vec![
        "すし",
        "すしや",
    ]  // Sorted by `Vec<u8>`'s order
);

与各种数据类型的结合使用

TrieBuilder 使用泛型类型实现,如下所示

impl<Label: Ord> TrieBuilder<Label> {
    ...
    pub fn push<Arr: AsRef<[Label]>>(&mut self, word: Arr) where Label: Clone { ... }
    ...
}

在上面的 使用概述 示例中,我们使用了 Label=u8, Arr=&str。如果 Label 没有实现 Clone,则使用 insert()

这里展示了其他 LabelArr 类型示例。

标签=&str,Arr=Vec<&str>

假设 Label 是英文单词,而 Arr 是英文短语。

use trie_rs::TrieBuilder;

let mut builder = TrieBuilder::new();
builder.push(vec!["a", "woman"]);
builder.push(vec!["a", "woman", "on", "the", "beach"]);
builder.push(vec!["a", "woman", "on", "the", "run"]);

let trie = builder.build();

assert_eq!(
    trie.exact_match(vec!["a", "woman", "on", "the", "beach"]),
    true
);
let r: Vec<Vec<&str>> = trie.predictive_search(vec!["a", "woman", "on"]).collect();
assert_eq!(
    r,
    vec![
        ["a", "woman", "on", "the", "beach"],
        ["a", "woman", "on", "the", "run"],
    ],
);
let s: Vec<Vec<&str>> = trie.common_prefix_search(vec!["a", "woman", "on", "the", "beach"]).collect();
assert_eq!(
    s,
    vec![vec!["a", "woman"], vec!["a", "woman", "on", "the", "beach"]],
);

标签=u8,Arr=[u8;n]

假设 Label 是 π(= 3.14...)中的数字,而 Arr 是将 π 的数字每 10 个分开的窗口。

use trie_rs::TrieBuilder;

let mut builder = TrieBuilder::<u8>::new(); // Pi = 3.14...

builder.push([1, 4, 1, 5, 9, 2, 6, 5, 3, 5]);
builder.push([8, 9, 7, 9, 3, 2, 3, 8, 4, 6]);
builder.push([2, 6, 4, 3, 3, 8, 3, 2, 7, 9]);
builder.push([6, 9, 3, 9, 9, 3, 7, 5, 1, 0]);
builder.push([5, 8, 2, 0, 9, 7, 4, 9, 4, 4]);
builder.push([5, 9, 2, 3, 0, 7, 8, 1, 6, 4]);
builder.push([0, 6, 2, 8, 6, 2, 0, 8, 9, 9]);
builder.push([8, 6, 2, 8, 0, 3, 4, 8, 2, 5]);
builder.push([3, 4, 2, 1, 1, 7, 0, 6, 7, 9]);
builder.push([8, 2, 1, 4, 8, 0, 8, 6, 5, 1]);
builder.push([3, 2, 8, 2, 3, 0, 6, 6, 4, 7]);
builder.push([0, 9, 3, 8, 4, 4, 6, 0, 9, 5]);
builder.push([5, 0, 5, 8, 2, 2, 3, 1, 7, 2]);
builder.push([5, 3, 5, 9, 4, 0, 8, 1, 2, 8]);

let trie = builder.build();

assert_eq!(trie.exact_match([5, 3, 5, 9, 4, 0, 8, 1, 2, 8]), true);

let t: Vec<Vec<u8>> = trie.predictive_search([3]).collect();
assert_eq!(
    t,
    vec![
        [3, 2, 8, 2, 3, 0, 6, 6, 4, 7],
        [3, 4, 2, 1, 1, 7, 0, 6, 7, 9],
    ],
);
let u: Vec<Vec<u8>> = trie.common_prefix_search([1, 4, 1, 5, 9, 2, 6, 5, 3, 5]).collect();
assert_eq!(
    u,
    vec![[1, 4, 1, 5, 9, 2, 6, 5, 3, 5]],
);

trie 映射使用

要存储每个单词的值,请使用 trie_rs::map::{Trie, TrieBuilder}

use std::str;
use trie_rs::map::TrieBuilder;

let mut builder = TrieBuilder::new();  // Inferred `TrieBuilder<u8, u8>` automatically
builder.push("すし", 0);
builder.push("すしや", 1);
builder.push("すしだね", 2);
builder.push("すしづめ", 3);
builder.push("すしめし", 4);
builder.push("すしをにぎる", 5);
builder.push("すし", 6);  // Word `push`ed twice uses last value.
builder.push("🍣", 7);

let mut trie = builder.build();

// exact_match(): Find a word exactly match to query.
assert_eq!(trie.exact_match("すし"), Some(&6));
assert_eq!(trie.exact_match("🍣"), Some(&7));
assert_eq!(trie.exact_match("🍜"), None);

// Values can be modified.
let v = trie.exact_match_mut("🍣").unwrap();
*v = 8;
assert_eq!(trie.exact_match("🍣"), Some(&8));

对于交互式应用,可以使用增量搜索以获得最佳性能。请参阅IncSearch

use std::str;
use trie_rs::{TrieBuilder, inc_search::Answer};

let mut builder = TrieBuilder::new();  // Inferred `TrieBuilder<u8, u8>` automatically
builder.push("ab");
builder.push("すし");
builder.push("すしや");
builder.push("すしだね");
builder.push("すしづめ");
builder.push("すしめし");
builder.push("すしをにぎる");
let trie = builder.build();
let mut search = trie.inc_search();

// Query by the byte.
assert_eq!(search.query(&b'a'), Some(Answer::Prefix));
assert_eq!(search.query(&b'c'), None);
assert_eq!(search.query(&b'b'), Some(Answer::Match));

// Reset the query to go again.
search.reset();

// For unicode its easier to use .query_until().
assert_eq!(search.query_until(""), Ok(Answer::Prefix));
assert_eq!(search.query_until(""), Ok(Answer::PrefixAndMatch));
assert_eq!(search.query_until(""), Ok(Answer::Match));
assert_eq!(search.query(&b'a'), None);
assert_eq!(search.query_until("a"), Err(0));

search.reset();
assert_eq!(search.query_until("ab-NO-MATCH-"), Err(2)); // No match on byte at index 2.

功能

  • 泛型类型支持:如上述示例所示,trie-rs不仅可以用于搜索UTF-8字符串,还可以用于其他数据类型。
  • 基于louds-rs,该库快速、并行化且内存高效。
  • 最新基准测试结果始终可访问:trie-rs使用Criterion.rs在Travis CI上持续进行基准测试。图形基准测试结果发布在此
  • map::Trie将每个条目与一个Value相关联。
  • Value不需要任何特质。
  • Label: Clone不是创建Trie<Label>所必需的,但对于许多实现具体化搜索操作(如predictive_search())非常有用。
  • 许多搜索操作通过迭代器实现,这些迭代器是懒加载的、占用更少的内存,并且可以短路。
  • 增量搜索适用于“在线”应用,即一次搜索一个Label

Cargo功能

  • "rayon"

启用rayon数据并行库。

  • "mem_dbg"

可以确定嵌套数据结构(如trie本身)的字节数。

  • "serde"

可以序列化和反序列化trie。

致谢

edict.furigana用于基准测试。此文件按以下步骤构建

  1. EDICT下载edict.gz
  2. 将其从原始EUC转换为UTF-8。
  3. 使用edict-to-csv将其转换为CSV文件。
  4. 提取字段$1用于平假名/片假名单词,以及字段$3用于其他(如汉字)单词。
  5. 使用kana2hira将片假名转换为平假名。

非常感谢这些字典和工具。

版本

trie-rs使用语义版本控制

由于当前主版本是0,次版本更新可能会涉及破坏公共API更改(尽管会小心避免)。

Rust版本支持

trie-rs使用github CI对这些Rust版本进行持续测试

  • 1.75.0带有所有功能
  • 1.67.0不带功能
  • 最新稳定版本

因此,预计它可以与Rust 1.75.0及其任何新版本一起使用。

较旧版本也可能可以工作,但未进行测试或保证。

更早的Rust版本支持

如果需要支持1.67.0之前的Rust版本,trie-rs 0.2.0支持Rust 1.33.0及更高版本。

贡献

欢迎任何类型的pull请求。

许可

MIT OR Apache-2.0

依赖项

~1–27MB
~393K SLoC