#string #rope #editing #processing #trace #offset #byte

jumprope

基于跳表构建的简单、快速绳子(花哨字符串)库

13个版本 (4个稳定版)

1.1.2 2023年5月15日
1.1.1 2022年11月21日
1.1.0 2022年9月14日
1.0.0 2022年4月26日
0.4.0 2021年10月11日

#2 in #rope

Download history 5/week @ 2024-05-09 5/week @ 2024-05-16 12/week @ 2024-05-23 13/week @ 2024-05-30 22/week @ 2024-06-06 18/week @ 2024-06-13 24/week @ 2024-06-20 371/week @ 2024-06-27 842/week @ 2024-07-04 851/week @ 2024-07-11 1236/week @ 2024-07-18 1408/week @ 2024-07-25 1443/week @ 2024-08-01

5,009 每月下载量
用于 2 crates

ISC OR Apache-2.0

130KB
2K SLoC

JumpRope

因为字符串插入应该很快。

绳子是一种数据结构,用于高效编辑大字符串或处理编辑跟踪。

据我所知,JumpRope是世界最快的绳子实现。

与传统的字符串不同,JumpRope允许您

  • 高效地在文档的任何位置插入或删除任意按键。使用现实世界的编辑跟踪,jumprope每秒可以处理约3500万至4000万次编辑。
  • 使用Unicode字符偏移量或wchar偏移量(如你在JS和其他语言中找到的)进行索引。Jumprope可以有效地在这些格式之间转换。

JumpRope针对大字符串进行了优化,如源代码文件和文本文档。如果你的字符串非常小(小于100字节),你可能只需使用Rust内置的std String或类似SmartString的小字符串优化库。

JumpRope与ropey类似。Ropey支持一些更多功能(如转换行/列位置)。然而,jumprope在处理真实的编辑操作时比ropey快约3倍(见下文),并且jumprope编译后的wasm包更小。(Ropey brotli压缩后为30kb,而jumprope为18kb)。

API文档

Jumprope在crates.io上

将以下内容添加到Cargo.toml中以使用

jumprope = "1.0.0"

使用方法

JumpRope不是字符串的直接替代品,但它支持许多类似的方法。最重要的新增功能是insertremovereplace方法 - 这些方法允许您在(通常是)相对于现有文档大小的log(n)时间内原地编辑字符串。

use jumprope::JumpRope;

fn main() {
    let mut rope = JumpRope::from("Some large text document");
    rope.insert(5, "really "); // "Some really large text document"
    rope.replace(0..4, "My rad");  // "My rad really large text document"
    assert_eq!(rope, "My rad really large text document");

    // Extract to a string
    let s: String = rope.to_string();
    assert_eq!(s, "My rad really large text document");
}

您可以通过以下方式从绳子中读取内容

  • 使用 rope.to_string() 将绳索转换为字符串(需要分配)
  • 使用 rope.chars() 遍历字符
  • (最快)使用 rope.substrings() 遍历 &str 块。这返回文档中包含的 &str 项的迭代器。

如果您想读取绳索的子部分,可以使用 rope.slice_substrings(10..20) 读取绳索中给定范围内的所有内容。例如

fn main() {
    let rope = JumpRope::from("xxxGreetings!xxx");

    let string = rope.slice_substrings(3..13).collect::<String>();
    assert_eq!(string, "Greetings!");
}

有关更多详细信息,请参阅 JumpRope API 文档

宽字符转换

在某些语言中(尤其是 JavaScript、Java 和 C#),字符串的长度是通过使用 UTF16 编码字符串所需的 2 字节“字符”数量来衡量的。

这是因为很难有效地在 Unicode 字符偏移量(由 jumprope、菱形类型和其他编辑器使用)和这些编辑位置之间进行转换。简单的方法是 O(n) 操作。

Jumprope 支持通过向跳表添加额外的索引信息,以 O(log n) 的时间复杂度进行此转换。此功能默认禁用,因为额外的记录会增加 jumprope 的运行时间约 15%。

要使用此功能,启用 wchar_conversion 特性标志

jumprope = { version = "1.0.0", features = ["wchar_conversion"] }

此特性标志启用了与文档交互的许多额外宽字符相关方法

  • rope.len_wchars() -> usize:返回字符串的宽字符长度。
  • rope.chars_to_wchars(chars: usize) -> usize:将 char 偏移量转换为 wchar 偏移量
  • rope.wchars_to_chars(wchars: usize) -> usize:将 wchar 索引转换回 Unicode 字符计数
  • rope.insert_at_wchar(pos_wchar: usize, content: &str):在指定的 wchar 偏移量处插入 content
  • rope.remove_at_wchar(range: Range<usize>):使用宽字符偏移量指定的范围删除指定范围
  • rope.replace_at_wchar(range: Range<usize>, content: &str):用 content 替换指定的范围

有关这些方法的更多信息,请参阅docs.rs上的文档

缓冲字符串

JumpRope还提供了一个API用于缓冲编辑。通常当人类编辑字符串时,他们会插入或删除一系列字符。如果您在应用这些编辑之前将这些编辑操作合并在一起,JumpRope的速度会再快约10倍。

JumpRope提供了一个包装API,以JumpRopeBuf的形式进行透明处理。JumpRopeBuf会在刷新(写入)包含的JumpRope对象之前尽可能地合并传入的写入操作。

此API可能缺少在JumpRope上找到的一些方法。您通常可以通过调用rope.borrow()rope.as_mut()来清除挂起的更改并访问底层rope的指针来绕过任何缺失的方法。但请报告您发现的任何缺失函数,因为添加直接实现通常会带来更好的性能。

有关使用说明,请参阅JumpRopeBuf模块文档

历史/动机

此代码基于我几年前为了练习跳表而编写的较旧的基于跳表的C rope库。它有几个显著的不同之处

  • JumpRope不是简单地实现为一个跳表,而是一个跳表,其中每个叶节点包含一个Gap Buffer
  • JumpRope更快。(见下表)

基准测试

运行来自crdt-benchmarks的编辑跟踪,JumpRope比我所知的任何其他cargo库都要快

在Ryzen 5800X的单个核心上运行

数据集 原始字符串 XiRope Ropey librope(C) Jumprope
automerge-paper 3908.13 ms 518.75 ms 25.16 ms 16.28 ms 6.66 ms
rustcode 569.44 ms DNF 4.71 ms 3.93 ms 1.66 ms
sveltecomponent 41.05 ms 24.83 ms 2.31 ms 1.59 ms 0.59 ms
seph-blog1 1238.44 ms DNF 13.04 ms 10.01 ms 3.81 ms

完整的criterion报告在这里

我也试了AnRope,但在处理这些数据集时它崩溃了。

许可证

根据ISC许可证授权

版权所有 2018 Joseph Gentle

在此授予使用、复制、修改和/或以任何目的(无论是否付费)分发此软件的许可,前提是上述版权声明和本许可声明出现在所有副本中。

软件按“原样”提供,且作者放弃与此软件有关的任何保证,包括所有默示的适销性和适用性保证。在任何情况下,作者均不对任何特殊、直接、间接或后果性损害或任何损害承担责任,无论是否因合同、疏忽或其他侵权行为引起的,无论此类损害是否与使用或性能此软件有关。

依赖项

~390KB