[英]How to index a String in Rust
我试图在 Rust 中索引一个字符串,但编译器抛出错误。 我的代码(欧拉计划问题 4, 游乐场):
fn is_palindrome(num: u64) -> bool {
let num_string = num.to_string();
let num_length = num_string.len();
for i in 0 .. num_length / 2 {
if num_string[i] != num_string[(num_length - 1) - i] {
return false;
}
}
true
}
错误:
error[E0277]: the trait bound `std::string::String: std::ops::Index<usize>` is not satisfied
--> <anon>:7:12
|
7 | if num_string[i] != num_string[(num_length - 1) - i] {
| ^^^^^^^^^^^^^
|
= note: the type `std::string::String` cannot be indexed by `usize`
String
不能被索引是有原因的吗? 那我怎样才能访问数据呢?
是的,在 Rust 中无法对字符串进行索引。 这样做的原因是 Rust 字符串在内部以 UTF-8 编码,因此索引本身的概念会很模糊,人们会误用它:字节索引很快,但几乎总是不正确(当您的文本包含非 ASCII 符号时) ,字节索引可能会让你留在一个字符中,如果你需要文本处理,这真的很糟糕),而字符索引不是免费的,因为 UTF-8 是可变长度编码,所以你必须遍历整个字符串才能找到所需的代码点。
如果你确定你的字符串只包含 ASCII 字符,你可以在&str
上使用as_bytes()
方法,它返回一个字节片,然后索引到这个片中:
let num_string = num.to_string();
// ...
let b: u8 = num_string.as_bytes()[i];
let c: char = b as char; // if you need to get the character as a unicode code point
如果确实需要索引代码点,则必须使用char()
迭代器:
num_string.chars().nth(i).unwrap()
正如我上面所说,这需要遍历整个迭代器直到第i
个代码元素。
最后,在文本处理的许多情况下,实际上需要使用字素簇而不是代码点或字节。 在unicode-segmentation crate 的帮助下,您也可以索引到字形簇中:
use unicode_segmentation::UnicodeSegmentation
let string: String = ...;
UnicodeSegmentation::graphemes(&string, true).nth(i).unwrap()
自然地,字素簇索引具有与索引到代码点相同的遍历整个字符串的要求。
在 Rust 中做这种事情的正确方法不是索引而是迭代。 这里的主要问题是 Rust 的字符串是用 UTF-8 编码的,UTF-8 是 Unicode 字符的可变长度编码。 由于长度可变,不查看字符串就无法确定第 n 个字符的内存位置。 这也意味着访问第 n 个字符的运行时间为 O(n)!
在这种特殊情况下,您可以遍历字节,因为已知您的字符串仅包含字符 0-9(遍历字符是更通用的解决方案,但效率稍低)。
这是一些惯用的代码来实现这一点( 操场):
fn is_palindrome(num: u64) -> bool {
let num_string = num.to_string();
let half = num_string.len() / 2;
num_string.bytes().take(half).eq(num_string.bytes().rev().take(half))
}
我们同时向前( num_string.bytes().take(half)
)和向后( num_string.bytes().rev().take(half)
) num_string.bytes().take(half)
字符串中的字节; .take(half)
部分用于将完成的工作量减半。 然后我们简单地将一个迭代器与另一个迭代器进行比较,以确保在每个步骤中第 n 个和第 n 个最后一个字节是等效的; 如果是,则返回 true; 如果不是,则为假。
您可以将String
或&str
转换为字符的vec
,然后索引该vec
。
例如:
fn main() {
let s = "Hello world!";
let my_vec: Vec<char> = s.chars().collect();
println!("my_vec[0]: {}", my_vec[0]);
println!("my_vec[1]: {}", my_vec[1]);
}
这里有一个活生生的例子
不允许在 String 上建立索引,因为(请查看本书):
因此,如果您输入的内容不包含变音符号(被视为单独的字符)并且可以用字符来近似字母,则可以使用chars()迭代器和DoubleEndedIterator特性来实现两个指针方法:
fn is_palindrome(num: u64) -> bool {
let s = num.to_string();
let mut iterator = s.chars();
loop {
let ch = iterator.next();
let ch_end = iterator.next_back();
if ch.is_none() || ch_end.is_none() {
break;
}
if ch.unwrap() != ch_end.unwrap() {
return false
}
}
true
}
这无论如何都不适用于所有用途,但如果您只需要引用前一个字符(或者,稍作修改,下一个字符),那么可以在不遍历整个 str 的情况下这样做。
这里的场景是在切片中找到了一个 str 切片、字符串和模式。 我想知道模式之前的字符。
像prev_char(string.as_bytes(), pattern_index)
一样调用 prev_char prev_char(string.as_bytes(), pattern_index)
其中模式索引是字符串中模式第一个字节的索引。
utf-8 编码定义良好,这只是通过备份直到找到起始字节之一(高位位 0 或位 11)然后将该 1-4 字节 [u8] 切片转换为 str 来工作。
这段代码只是解开它,因为该模式是在有效的 utf-8 str 中找到的,因此不可能出现错误。 如果您的数据尚未经过验证,最好返回结果而不是选项。
enum PrevCharStates {
Start,
InEncoding,
}
fn prev_char(bytes: &[u8], starting_index: usize) -> Option<&str> {
let mut ix = starting_index;
let mut state = PrevCharStates::Start;
while ix > 0 {
ix -= 1;
let byte = bytes[ix];
match state {
PrevCharStates::Start => {
if byte & 0b10000000 == 0 {
return Some(std::str::from_utf8(&bytes[ix..starting_index]).unwrap());
} else if byte & 0b11000000 == 0b10000000 {
state = PrevCharStates::InEncoding;
}
},
PrevCharStates::InEncoding => {
if byte & 0b11000000 == 0b11000000 {
return Some(std::str::from_utf8(&bytes[ix..starting_index]).unwrap());
} else if byte & 0b11000000 != 0b10000000 {
return None;
}
}
}
}
None
}
波纹管代码工作正常,不确定性能和 O 复杂性,希望有人可以添加有关此解决方案的更多信息。
fn is_palindrome(num: u64) -> bool {
let num_string = String::from(num.to_string());
let num_length = num_string.len();
for i in 0..num_length / 2 {
let left = &num_string[i..i + 1];
let right = &num_string[((num_length - 1) - i)..num_length - i];
if left != right {
return false;
}
}
true
}
索引在 Rust 中不起作用的原因有两个:
在 rust 中,字符串被存储为utf-8
编码字节的集合。 在 memory 中,字符串只是 collections 的 1 和 0。 程序需要能够解释那些 1 和 0 并打印出正确的字符。 这就是编码发挥作用的地方。
fn main(){ let sample:String=String::from("2bytesPerChar") // we could this in higher programming languages. in rust we get error. cannot be indexed by an integer let c:char=sample[0] }
字符串是字节的集合。 那么我们的“2bytesPerChar”的长度是多少? 因为有些字符可以是 1 到 4 个字节长。 假设第一个字符有 2 个字节。 如果你想获取字符串中的第一个字符,使用索引,hello[0] 将指定第一个字节,它是第一个字符串的唯一一半。
Bytes
、 scalar values
、字素grapheme clusters
。 如果我们使用索引 rust 不知道我们会收到什么。 字节、标量值或字素簇。 所以我们必须使用更具体的方法。返回字节
for b in "dsfsd".bytes(){ // bytes method returns a collection of bytes and here we are iterating over every byte and printing it out println,("{}",b) }
返回标量值:
// we could iterate over scalar values using char methods
for c in "kjdskj".chars(){
println!("{}",c)
}
为了保持 rust 标准库精简,默认情况下不包括迭代石墨烯簇的能力。 我们需要导入一个箱子
// in cargo.toml
[dependencies]
unicode-segmentation="1.7.1"
然后:
use unicode_segmentation::UnicodeSegmentation;
// we pass true to get extended grapheme clusters
for g in "dada"graphemes(true){
println!("{}",g)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.