如何在 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
}

The error:

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不能被索引? 那么我如何访问数据?

103737 次浏览

是的,在 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()

如前所述,这将需要遍历整个迭代器直到 ith 代码元素。

最后,在许多文本处理的情况下,实际上有必要使用 grapheme clusters,而不是使用代码点或字节。在 Unicode 分段板条箱的帮助下,你也可以索引到字母簇:

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个字符(对字符进行迭代是更通用的解决方案,但效率稍低)。

下面是实现这一点的一些惯用代码(playground) :

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))字节; .take(half)部分用于将所完成的工作量减半。然后,我们将一个迭代器与另一个迭代器进行比较,以确保在每个步骤中 nth 和 nth 最后一个字节是等价的; 如果它们是等价的,则返回 true; 如果不是等价的,则返回 false。

如果您要查找的是类似于索引的内容,则可以使用

字符串上的 .chars() .nth()


.chars() -> Returns an iterator over the chars of a string slice.

返回迭代器的第 n 个元素,在 Option


现在你可以用以下几种方式来使用它们,例如:

let s: String = String::from("abc");
//If you are sure
println!("{}", s.chars().nth(x).unwrap());
//or if not
println!("{}", s.chars().nth(x).expect("message"));

您可以将 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]);
}


这里有一个活的 例子

无论如何,这并不适用于所有的用法,但是如果您只需要引用前一个字符(或者稍微修改一下,引用下一个字符) ,那么不需要遍历整个 str 就可以做到这一点。

这里的场景是在这个切片中找到了 str 切片、字符串和模式。我想知道模式之前的角色。

调用 prev _ char,如 prev_char(string.as_bytes(), pattern_index),其中模式索引是字符串中模式的第一个字节的索引。

utf-8 encoding is well defined and this works just by backing up until it finds one of the starting bytes (either high order bit 0 or bits 11) and then converting that 1-4 byte [u8] slice to a str.

这段代码只是打开它,因为这个模式是在一个有效的 utf-8 str 中找到的,所以不可能出现错误。如果您的数据尚未经过验证,最好返回一个结果而不是 Option。

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
}

不允许在字符串上建立索引,因为(请检查 那本书) :

  • 不清楚索引值应该是什么: 一个字节,一个 字符,或者一个字母集群(在常识中我们称之为 )
  • 字符串是用 UTF-8编码的字节(u8)向量,而 UTF-8是 变量长度编码,即每个 性格可以采用不同数量的字节——从1到4。因此,按索引获取字符或字符集需要从一开始就进行整个字符串遍历(平均为 O (n) ,最糟糕的情况是 O (n)) ,以确定字符或字符集的有效字节边界。

因此,如果你的输入不包含变音符(被认为是一个单独的 性格) ,并且可以用字符来近似字母,你可以使用 查尔斯()迭代器和 双尾迭代器 trait 作为两个指针的方法:

    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
}

索引在 Rust 中不起作用有两个原因:

  • 在锈中,字符串存储为 utf-8编码字节的集合。在内存中,字符串只是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个字节。如果希望获取 string 中的第一个字符,使用索引,hello [0]将指定第一个字节,它是第一个字符串的唯一一半。

  • 另一个原因是在 Unicode 中有3种相关的方式来表示一个单词: Bytesscalar valuesgrapheme clusters。如果我们使用索引生锈不知道我们会收到什么。字节、标量值或字形集群。所以我们得用更具体的方法。

How to access the characters in String

  • 返回字节

       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)
}
  • 返回图形值:

为了保持锈标准库的精简,在缺省情况下不包括在石墨烯簇上迭代的能力。我们需要进口一个箱子

// 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)
}