我有一段文字,字符的字节长不同。
let text = "Hello привет";
我需要从给定的开始(包括)和结束(不包括)字符索引中提取字符串的一部分。我尝试过了
let slice = &text[start..end];
并出现以下错误
thread 'main' panicked at 'byte index 7 is not a char boundary; it is inside 'п' (bytes 6..8) of `Hello привет`'
我想发生这种情况是因为西里尔字母是多字节的,并且[..]
表示法使用 byte 索引接受字符。如果我想使用 character 索引进行切片,就像在Python中一样,该怎么办?
slice = text[start:end]
吗?
我知道我可以使用chars()
迭代器并手动遍历所需的子字符串,但是还有更简洁的方法吗?
答案 0 :(得分:24)
我知道我可以使用
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..idx]);
作为另一种选择,您可以首先将字符串收集到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)。
但更重要的原因:
Python的功能(以及您认为想要的功能)并不是那么有用。一切都归结为语言的复杂性,从而也归因于unicode的复杂性。 Python切片Unicode codepoints 。这就是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表现出令人惊讶的方式。有关更多信息和示例,请参见底部的链接。
因此,如果您想使用应该可以在任何地方使用的国际字符串,请不要进行代码点切片!如果您确实需要在语义上将字符串视为一系列字符,请使用字素簇。为此,板条箱unicode-segmentation
非常有用。
有关此主题的其他资源:
答案 1 :(得分:7)
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])
})
}
如果可以使用String
,则可以使用str::chars()
:
let string: String = text.chars().take(end).skip(start).collect();
答案 2 :(得分:0)
这是一个检索utf8切片的函数,具有以下优点:
coord_trans(y = "log10")