繁体   English   中英

如何索引 Rust 中的字符串

[英]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; 如果不是,则为假。

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

.chars().nth()字符串。


.chars() -> 在字符串切片的char返回迭代器。

.nth() -> 在Option返回迭代器的第 n 个元素


现在您可以通过多种方式使用上述内容,例如:

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]);
}

这里有一个活生生的例子

不允许在 String 上建立索引,因为(请查看本书):

  • 不清楚索引值应该是什么:一个字节,一个字符,还是一个字素簇(我们常说的字母
  • 字符串是用 UTF-8 编码的字节 (u8) 向量,而 UTF-8 是可变长度编码,即每个字符可以采用不同数量的字节 - 从 1 到 4。因此,要通过索引获取字符或字素簇,需要遍历整个字符串(平均和最坏情况下为 O(n))从头开始确定字符或字素的有效字节边界。

因此,如果您输入的内容不包含变音符号(被视为单独的字符)并且可以用字符来近似字母,则可以使用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] 将指定第一个字节,它是第一个字符串的唯一一半。

  • 另一个原因是在 unicode 中表示一个词有 3 种相关方式: Bytesscalar values 、字素grapheme clusters 如果我们使用索引 rust 不知道我们会收到什么。 字节、标量值或字素簇。 所以我们必须使用更具体的方法。

如何访问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)
   }
  • 返回字形值:

为了保持 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM