Rope 数据结构和 Lines

问题描述

我正在使用 Rope 来存储大量 (GB) 的文本。文本长度可达数千万行。

绳索本身在任何位置插入都非常快,并且在特定位置也能快速获取字符。

但是,我如何获得特定行(本例中为 \n)的起始位置?例如,我如何获得第 15 行的起始位置?我可以看到几个选项。

  1. 没有任何额外的数据。每当你想说第 15 行时,你就遍历 Rope 中的所有字符,找到换行符,当你到达第 15 行换行符时,就停止。
  2. 将每行的 startlength 存储在一个向量中。因此,您将拥有包含所有字符的 Rope 数据结构,然后是单独的 std::vector<line>line 结构将只包含 2 个字段; startlength。 Start 表示行在 Rope 内的起始位置,length 是行的长度。要找到第 15 行的开始位置,只需执行 lines[14].start

问题

#1 是一种可怕的方法。它非常慢,因为您必须遍历所有字符。

#2 也不好。尽管获取一行开始的位置非常快(O(1)),但是每次插入一行时,您都必须将所有行移到它前面,这就是O(N)。此外,存储这意味着对于您拥有的每一行,它会占用额外的 16 字节数据。 (假设 startlength 各为 8 个字节)。这意味着如果您有 13,000,000 行,它将占用 200MB 的额外内存。您可以使用链表,但它只会使访问速度变慢。

是否有更好、更有效的方法来存储行位置以便快速访问和插入?(最好使用 O(log(n)) 来插入和访问行)

我正在考虑使用 BST,更具体地说是 RB-Tree,但我不完全确定如何使用它。我看到 VSCode 执行 this 但改为 PieceTable

任何帮助将不胜感激。

编辑

@interjay 提供的答案似乎不错,但是如果 CR 和 LF 被拆分为 2 个叶节点,我将如何处理 CRLF?

我还注意到 ropey,它是 Rope 的 Rust 库。我想知道是否有类似的东西,但对于 C++

解决方法

在每个rope节点(叶子节点和内部节点)中,除了保存该子树中的字符数外,还可以放置子树中包含的换行总数。

然后查找特定的换行符与查找包含特定字符索引的节点的工作方式完全相同。您会查看“换行数”字段而不是“字符数”字段。

所有绳索操作的工作原理大致相同。创建新的内部节点时,您只需要添加其子节点的换行数。所有操作的复杂度都是一样的。