代码点切片的可能解决方案
我知道可以使用 chars()
迭代器并手动遍历所需的子字符串,但是否有更简洁的方法?
如果你知道确切的字节索引,你可以对字符串进行切片:
let text = "Hello привет";
println!("{}", &text[2..10]);
这将打印出 "llo пр"。因此问题是找到确切的字节位置。您可以使用 char_indices()
迭代器轻松地完成这个任务(或者您也可以使用 chars()
与 char::len_utf8()
):
let text = "Hello привет";
let end = text.char_indices().map(|(i, _)| i).nth(8).unwrap();
println!("{}", &text[2..end]);
作为另一种选择,您可以先将字符串收集到Vec<char>
中。然后,索引很简单,但要将其打印为字符串,您必须再次收集它或编写自己的函数来执行此操作。
let text = "Hello привет";
let text_vec = text.chars().collect::<Vec<_>>();
println!("{}", text_vec[2..8].iter().cloned().collect::<String>());
为什么这不那么容易?
正如您所看到的,这两种解决方案都不太好。这是有意为之的,原因有两个:
由于str
是一个简单的UTF8缓冲区,按Unicode代码点进行索引是O(n)操作。通常,人们希望[]
运算符是O(1)操作。Rust使这个运行时复杂度明确,并且不试图隐藏它。在上面的两个解决方案中,您可以清楚地看到它不是O(1)。
但更重要的原因是:
Unicode代码点通常不是有用的单位
Python所做的(以及您认为需要的)并不是非常有用。这完全取决于语言的复杂性以及Unicode的复杂性。Python对Unicode 代码点进行切片。这就是Rust的char
所表示的。它有32位(几个较少的位就足够了,但我们向上舍入到2的幂次)。
但您实际想要做的是切割用户感知字符。但这是一个明确的松散定义的术语。不同的文化和语言将不同的事物视为“一个字符”。最接近的近似是“字形群集”。这样的聚集可以由一个或多个Unicode代码点组成。考虑以下Python 3代码:
>>> s = "Jürgen"
>>> s[0:2]
'Ju'
令人惊讶,不是吗?这是因为上面的字符串是:
0x004A
大写字母 J
0x0075
小写字母 u
0x0308
连接变音符
- ...
这是一个作为前一个字符的一部分呈现的连接字符的示例。在这里,Python切片做了“错误”的事情。
另一个例子:
>>> s = "fire"
>>> s[0:2]
'fir'
同样不是您预期的内容。这次,fi
实际上是连字号fi
,它是一个码位。
Unicode有更多令人惊讶的行为示例。有关更多信息和示例,请参见底部的链接。
因此,如果您想使用可在任何地方正常工作的国际字符串,请勿进行码位切片!如果您确实需要将字符串语义视为一系列字符,请使用字符簇。为此,crate unicode-segmentation
非常有用。
此主题的其他资源:
chars()
是这里的最佳选择:text.chars().take(end).skip(start)
。 - Tim DiekmannTake<Chars>
转换为&str
? - Sasha Tsukanovcollect()
。请参考这个问题:https://dev59.com/9VoU5IYBdhLWcg3wvIqp - ozkriffcollect()
方法的结果是String
类型,而不是&str
类型。这就是我没有将此标记为您链接的问题的重复原因。 - Tim Diekmann