【问题标题】:Emulating Python's `index(separator, start_index)` in Rust在 Rust 中模拟 Python 的 `index(separator, start_index)`
【发布时间】:2015-11-17 20:20:28
【问题描述】:

我目前正在从 Rust 移植一个 Python 库,但发现一行我无法找到正确的“翻译”:

right = s.index(sep, left)

其中right 是在字符串s 中找到的第一个sep 实例的索引,该索引位于索引left 之后。

这里有一个简单的例子:

Python 3

>>> s = "Hello, my name is erip and my favorite color is green."
>>> right = s.index("my", 10) # Find index of first instance of 'my' after index 10
>>> print right
27
>>> print s[27:]
my favorite color is green.

我在 Rust 中的尝试是:

// s: &str, sep: &str, left: usize
let right = s[left..].find(sep).unwrap() + left;

这将在left 之后的字节中搜索sep。这seems to work 使用 ASCII 字符时。不过,使用 Unicode 时似乎有问题:

Python 3

>>> s = "Hello, mÿ name is erip and mÿ favorite color is green."
>>> right = s.index("mÿ", 10)
>>> print(right)
27

Rust

fn main() {
    let sep: &str = "mÿ";
    let left: usize = 10;
    let s: &str = "Hello, mÿ name is erip and mÿ favorite color is green.";
    let right = s[left..].find(sep).unwrap() + left;
    println!("{}", right); //prints 28
}

我意识到 Python 2 也会给出 28,因为它本身不支持 Unicode,但我想模仿 Python 3 的结果。

问题在于 Rust 中的 usize 指的是字符串中 bytes 的数量,因为“mÿ”实际上需要 3 个字节来编码。如何在 Rust 中获得这种期望的行为?

我正在使用rustc 1.4.0

【问题讨论】:

  • 因为usize [...] 指的是字符串中的字节数——在这种情况下确实如此,但并非普遍如此。 usize 是机器大小的无符号整数,适用于计算受机器内存限制的事物的数量。
  • 在风格上,没有理由在 main 中使用任何类型声明。都可以推断出来。
  • 我怀疑是 X/Y 问题:该索引之后可能被其他东西使用,因此索引本身的单位(以及值)并不重要(无论是字形、代码点还是字节)只要该单元由消费者共享 => 你真的想在代码点中有一个索引吗?还是字形?或者,只要消费者也可以适应字节,字节会起作用吗?
  • @MatthieuM。我在标记器中使用它 - 我想要字素中的索引。即,我不需要担心字符串中的 bytes 的数量。而是字符的位置,与编码方案无关。
  • @Shepmaster:视情况而定,有时您可能需要字素中的索引;例如,当在下一行用“~~~”为文本的一部分加下划线时,您不需要根据终端中的字节或代码点进行推理,而是根据字素(假设一个等宽字体,常见于终端)。对于分词器,我不会对同时需要字节索引和字素索引感到惊讶。

标签: unicode rust


【解决方案1】:

让我们稍微重申一下这个问题,因为目前还不清楚index 的单位应该是什么。人类相信弦乐很简单,因为我们一生中大部分时间都在使用它们。然而,事情远没有我们想的那么简单。

Rust 认为字符串(&strString)是 UTF-8 编码的字节序列。使用字节偏移量跳转到一个字符串是 O(1),你真的希望这种级别的性能保证来构建更复杂的东西。

我不知道 Python 认为该索引是什么。一旦你超越了简单的编码方案,如一个字符是一个字节的 ASCII,它就会变得hard。根据您的需要,有多种方法可以对 Unicode 字符串进行分块。两个明显的是 Unicode 代码点和字形。

由于代码点可以使用 char 在 Rust 中表示,这就是我假设你想要的。然而,你是唯一能弄清楚这一点的人。

此外,由于您要求结果为28,因此它必须是字符串中的字节数。跳过 N 个代码点但返回字节有点奇怪,但它就是这样。


现在我们知道我们在做什么了...让我们试着去做吧。 (请参阅下一个解决方案,我可以更好地阅读所需的结果)。

您需要使用的关键是char_indices。这是一个 O(n) 操作,遍历字符串并为您提供每个代码点及其对应的字节偏移量。

然后,只需将它们放在一起并正确处理脱离字符串末端的情况即可。 Rust 的强类型让这一点显而易见,万岁!

// `index` is the number of Unicode codepoints to skip
// The result is the number of **bytes** inside the haystack
// that the needle can be found.
fn python_index(haystack: &str, needle: &str, index: usize) -> Option<usize> {
    haystack.char_indices().nth(index).and_then(|(byte_idx, _)| {
        let leftover = &haystack[byte_idx..];
        leftover.find(needle).map(|inner_idx| inner_idx + byte_idx)
    })
}

fn main() {
    let right = python_index("Hello, mÿ name is erip and mÿ favorite color is green.", "mÿ", 10);
    println!("{:?}", right); // prints Some(28)
}

我们执行与上述相同的高级概念,但一旦找到needle,我们就会重新设置并再次遍历代码点。当我们找到子字符串的相同字节偏移量时,我们终止。

那么就只需要数我们看到的字符并加上我们已经跳过的数字。

// `index` is the number of Unicode codepoints to skip
// The result is the number of codepoints inside the haystack
// that the needle can be found.
fn python_index(haystack: &str, needle: &str, index: usize) -> Option<usize> {
    haystack.char_indices().nth(index).and_then(|(byte_idx, _)| {
        let leftover = &haystack[byte_idx..];

        leftover.find(needle).map(|inner_offset| {
            leftover.char_indices().take_while(|&(inner_inner_offset, _)| {
                inner_inner_offset != inner_offset
            }).count() + index
        })
    })
}

fn main() {
    let right = python_index("Hello, mÿ name is erip and mÿ favorite color is green.", "mÿ", 10);
    println!("{:?}", right); // prints Some(27)
}

这当然感觉不是超级高效;您需要进行基准测试以了解它的表现。但是,find 的实现非常优化,所以我宁愿使用它,然后直接通过字符并信任缓存和预取来帮助我^_^。

【讨论】:

  • 我实际上希望结果是27。我的结果已经在打印28
  • 我想在字符串s 中找到sep 的第一个实例的索引,该索引位于索引left 之后。我想按字符而不是按字节查找索引。我认为char_indices 仍然是我想要的。
  • @erip 已更新。我不太明白这样做的目的,因为一旦您开始包含组合字符,它就会中断......
  • 这就是问题的重点。我想要一种高效而不是杂乱无章的方法。
  • @erip 你从来没有在你的问题中提到过“性能”或“效率”;您只是要求提供等效功能。也许您应该问另一个问题,例如modifying a question dramatically is frowned upon。或者您可以对此投反对票并等待其他人提供更理想的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-03
  • 2021-07-01
  • 2021-02-23
  • 1970-01-01
  • 1970-01-01
  • 2013-05-27
相关资源
最近更新 更多