Sas*_*nov 18 string unicode slice rust
我有一段文字,字符长度不同.
let text = "Hello ??????";
Run Code Online (Sandbox Code Playgroud)
给定开始(包含)和结束(排除)字符索引时,我需要获取一小段字符串.我试过这个
let slice = &text[start..end];
Run Code Online (Sandbox Code Playgroud)
并得到以下错误
thread 'main' panicked at 'byte index 7 is not a char boundary; it is inside '?' (bytes 6..8) of `Hello ??????`'
Run Code Online (Sandbox Code Playgroud)
我想这是因为西里尔字母是多字节的,并且[..]符号使用字节索引来表示字符.如果我想使用字符索引切片,我可以使用什么,就像我在Python中做的那样:
slice = text[start:end] ?
我知道我可以使用chars()迭代器并手动遍历所需的子字符串,但是有更简洁的方法吗?
Luk*_*odt 24
我知道我可以使用
chars()迭代器并手动遍历所需的子字符串,但是有更简洁的方法吗?
如果您知道确切的字节索引,则可以对字符串进行切片:
let text = "Hello ??????";
println!("{}", &text[2..10]);
Run Code Online (Sandbox Code Playgroud)
这打印"lloпр".所以问题是找出确切的字节位置.你可以用做很容易char_indices()迭代器(或者你可以使用chars()同char::len_utf8()):
let text = "Hello ??????";
let end = text.char_indices().map(|(i, _)| i).nth(8).unwrap();
println!("{}", &text[2..idx]);
Run Code Online (Sandbox Code Playgroud)
作为另一种选择,您可以先将字符串收集到Vec<char>.然后,索引很简单,但要将其作为字符串打印,您必须再次收集它或编写自己的函数来完成它.
let text = "Hello ??????";
let text_vec = text.chars().collect::<Vec<_>>();
println!("{}", text_vec[2..8].iter().cloned().collect::<String>());
Run Code Online (Sandbox Code Playgroud)
如您所见,这些解决方案都不是那么好.这是故意的,原因有两个:
由于str是简单的UTF8缓冲区,因此通过unicode代码点进行索引是一种O(n)操作.通常,人们期望[]操作员是O(1)操作.Rust使这个运行时复杂性显而易见,并不试图隐藏它.在上面的两个解决方案中,您可以清楚地看到它不是O(1).
但更重要的原因是:
Python所做的(以及你认为你想要的)并不是那么有用.这一切都归结为语言的复杂性以及unicode的复杂性.Python切片Unicode 代码点.这就是Rust所char代表的.它是32位大(少了一些就足够了,但我们的功能为2).
但你真正想要做的是切割用户感知的角色.但这是一个明确松散定义的术语.不同的文化和语言将不同的东西视为"一个字".最接近的是"字形簇".这样的集群可以包含一个或多个unicode代码点.考虑这个Python 3代码:
>>> s = "Ju?rgen"
>>> s[0:2]
'Ju'
Run Code Online (Sandbox Code Playgroud)
令人惊讶,对吗?这是因为上面的字符串是:
0x004A 拉丁文大写字母J0x0075 拉丁文小写字母U.0x0308 结合DIAERESIS这是作为前一个字符的一部分呈现的组合字符的示例.Python切片在这里做了"错误"的事情.
另一个例子:
>>> s = "?re"
>>> s[0:2]
'?r'
Run Code Online (Sandbox Code Playgroud)
也不是你所期望的.这一次,fi实际上是连字?,这是一个代码点.
有更多的例子,Unicode以令人惊讶的方式表现.有关更多信息和示例,请参阅底部的链接.
因此,如果您想使用应该能够在任何地方工作的国际字符串,请不要进行代码点切片!如果您确实需要在语义上将字符串视为一系列字符,请使用字形集群.要做到这一点,箱子unicode-segmentation是非常有用的.
关于这个主题的更多资源:
UTF-8编码的字符串可能包含由多个字节组成的字符.在您的情况下,?从索引6(包括)开始,到第8位(不包括)结束,因此索引7不是字符的开头.这就是您的错误发生的原因.
你可以用它str::char_indices来解决这个问题(记住,在UTF-8中找到一个位置是O(n)):
fn get_utf8_slice(string: &str, start: usize, end: usize) -> Option<&str> {
assert!(end >= start);
string.char_indices().nth(start).and_then(|(start_pos, _)| {
string[start_pos..]
.char_indices()
.nth(end - start + 1)
.map(|(end_pos, _)| &string[start_pos..end_pos])
})
}
Run Code Online (Sandbox Code Playgroud)
你可以使用,str::chars()如果你没有得到String:
let string: String = text.chars().take(end).skip(start).collect();
Run Code Online (Sandbox Code Playgroud)