#trie #key-value #key #radix #value #map #byte-slice

no-std qp-trie

一个纯Rust编写的典型且快速的QP-trie实现,注重安全性

19个版本

使用旧的Rust 2015

0.8.2 2023年10月3日
0.8.1 2023年2月11日
0.8.0 2022年4月9日
0.7.7 2021年8月21日
0.6.1 2017年7月10日

#54 in 数据结构

Download history 1323/week @ 2024-04-07 1282/week @ 2024-04-14 1568/week @ 2024-04-21 1818/week @ 2024-04-28 1873/week @ 2024-05-05 1954/week @ 2024-05-12 1788/week @ 2024-05-19 1398/week @ 2024-05-26 1496/week @ 2024-06-02 1581/week @ 2024-06-09 1056/week @ 2024-06-16 1013/week @ 2024-06-23 1111/week @ 2024-06-30 1035/week @ 2024-07-07 837/week @ 2024-07-14 879/week @ 2024-07-21

3,987每月下载量
用于 8 个crate(6个直接使用)

MPL-2.0 许可证

75KB
1.5K SLoC

Build Status Docs Status On crates.io

qp-trie-rs:纯Rust实现的QP-trie

QP-trie(“Quelques-bits Popcount trie”或“Quad-bit Popcount trie”)是键的基数 trie,可以解释为 nybble(nybble是半个字节,或四位)的字符串。QP-tries本质上是一种 Patricia trie,它以 nybble 而不是单个位进行分支;因此,QP-trie 的分支因子(和基数)为16。

通过Serde进行序列化/反序列化

可选的,qp_trie::Trie 类型支持通过 serde 进行(反)序列化。启用 serde 功能将启用对 DeserializeSerialize 实现的编译。

何时应该使用QP-trie?

此crate中实现的QP-tries是任何实现了 Borrow<[u8]> 的键的键值映射。当需要像 HashMapBTreeMap 那样的操作,但需要稍微更快一些(QP-tries与Rust的默认哈希器的 HashMap 一样快或更快)以及/或能够有效地查询具有给定前缀的元素集合时,它们非常有用。

QP-tries支持高效地查找/插入/删除单个元素,以及查找/删除具有给定前缀键的值集合。

示例

键可以是任何实现了 Borrow<[u8]> 的类型。遗憾的是,目前这排除了 String 类型——尽管这个 trie 仍然可以用来存储字符串,但必须手动将它们转换为字节切片和 Vec<u8> 用于键。以下是一个简单的例子,将 9 个 2 元素的字节数组放入 trie 中,然后删除所有以 "1" 开头的字节数组

use qp_trie::Trie;

let mut trie = Trie::new();

for i in 0u8..3 {
    for j in 0u8..3 {
        trie.insert([i, j], i + j);
    }
}

for i in 0u8..3 {
    trie.remove([1, i]);
}

assert!(trie.iter().all(|(&key, _)| key[0] != 1));

这是一个稍微不那么天真但效率更高的方法

use qp_trie::Trie;

let mut trie = Trie::new();

for i in 0u8..3 {
    trie.extend((0u8..3).map(|j| ([i, j], i + j)));
}

trie.remove_prefix([1]);

assert!(trie.iter().all(|(&key, _)| key[0] != 1));

尽管 extend 真的并没有更高效(在 trie 中为 n 个元素预分配空间是很困难的),但我们保证 trie.remove_prefix([1]) 只会真正删除 trie 中的一个节点——具有给定前缀的所有节点的父节点。QP-tries,就像所有的基数 trie 一样,在处理与前缀相关的事情时非常高效。这包括对前缀的迭代

use qp_trie::Trie;

let mut trie = Trie::new();

for i in 0u8..3 {
    trie.extend((0u8..3).map(|k| ([i, j], i + j)));
}

let mut iter = trie.iter_prefix([1]);

assert_eq!(iter.next(), Some((&[1, 0], &1)));
assert_eq!(iter.next(), Some((&[1, 1], &2)));
assert_eq!(iter.next(), Some((&[1, 2], &3)));
assert_eq!(iter.next(), None);

与 qptrie crate 的差异

这个 crate 最初是 qptrie 的一个分支;然而,我发现代码难以工作,充满了不安全的指针操作,我认为可以避免。为了避免一个基本上重写整个库的 pull request,我决定自己写一个。

提供了一些在 qptrie crate 中缺失的显著的惯用特性

  • 为 trie 的键/值对提供 .iter().iter_mut(),用于不可变和可变迭代
  • qp_trie::Trie 实现 ExtendIntoIterator
  • qp_trie::Trie 实现 IndexIndexMut
  • qp_trie::Trie 提供了一个“条目 API”,其类型签名几乎与 std::collectionsBTreeMapHashMap 相同。

除了使用更安全的代码(在启用调试断言的情况下,会引发 panic 的失败将导致未定义的行为)外,qp_trie::Trie 比基于 qptrie 仓库中显示的基准测试略快。

基准测试

基准测试是对 qptrie crate,Rust stdlib 的 BTreeMap 和 stdlib 的 HashMap(默认和 FNV 哈希)进行的。根据这些基准测试,qp_trie::Trie 一致地优于 std::collectionsBTreeMapHashMap,以及 qptrie crate 的实现。

名为 exotrie 的基准测试使用 qptrie::Trie 实现。

test bench_btreemap_get      ... bench: 111,468,098 ns/iter (+/- 10,103,247)
test bench_btreemap_insert   ... bench: 112,124,846 ns/iter (+/- 14,296,195)
test bench_exotrie_get       ... bench:  46,195,582 ns/iter (+/- 16,943,561)
test bench_exotrie_insert    ... bench:  52,886,847 ns/iter (+/- 15,574,538)
test bench_fnvhashmap_get    ... bench:   9,530,109 ns/iter (+/- 820,763)
test bench_fnvhashmap_insert ... bench:  21,281,107 ns/iter (+/- 7,254,084)
test bench_hashmap_get       ... bench:  49,653,426 ns/iter (+/- 7,004,051)
test bench_hashmap_insert    ... bench:  47,771,824 ns/iter (+/- 4,979,606)
test bench_trie_get          ... bench:  40,898,914 ns/iter (+/- 13,400,062)
test bench_trie_insert       ... bench:  50,966,392 ns/iter (+/- 18,077,240)

未来工作

  • Stringstr 添加包装类型,以便更容易地处理字符串。

许可证

qp-trie-rs 库采用 MPL v2.0 许可。

依赖项

~185KB