【问题标题】:How to index a String in Rust如何在 Rust 中索引字符串
【发布时间】:2014-08-23 21:26:34
【问题描述】:

我试图在 Rust 中索引一个字符串,但编译器抛出一个错误。我的代码(Project Euler 问题4,playground):

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 不能被索引是有原因的吗?那我该如何访问数据呢?

【问题讨论】:

标签: string indexing rust


【解决方案1】:

下面的代码工作正常,不确定性能和 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
}

【讨论】:

    【解决方案2】:

    无论如何,这并不适合所有用途,但如果您只需要引用前一个字符(或者,稍作修改,下一个字符),那么可以在不遍历整个 str 的情况下这样做。

    这里的场景是有一个str切片,字符串,在切片中找到pattern。我想知道模式之前的字符。

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

    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
    }
    

    【讨论】:

    • 这个函数可以写成,签名稍有不同,如string[..index].chars().next_back() (playground)
    • 谢谢。我对 rust 很陌生,似乎每天都在学习新东西。
    【解决方案3】:

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

    这里有直播example

    【讨论】:

    • 性能怎么样?我认为字符串字节被复制了。
    【解决方案4】:

    是的,Rust 中不支持对字符串进行索引。原因是 Rust 字符串在内部是用 UTF-8 编码的,所以索引本身的概念会很模糊,人们会误用它:字节索引很快,但几乎总是不正确(当你的文本包含非 ASCII 符号时) , 字节索引可能会把你留在一个字符内,如果你需要文本处理,这真的很糟糕),而 char 索引不是免费的,因为 UTF-8 是可变长度编码,所以你必须遍历整个字符串才能找到所需的代码点。

    如果您确定您的字符串仅包含 ASCII 字符,您可以在 &amp;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-segmentation crate 的帮助下,您也可以索引到字素簇:

    use unicode_segmentation::UnicodeSegmentation
    
    let string: String = ...;
    UnicodeSegmentation::graphemes(&string, true).nth(i).unwrap()
    

    自然,字素簇索引与对代码点的索引一样,需要遍历整个字符串。

    【讨论】:

    • FWIW, String 永远无法被索引。索引删除仅适用于 &amp;str
    • 我想现在,char_at() 也被删除了... (rustc 1.23.0-nightly (79cfce3d3 2017-11-12))
    【解决方案5】:

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

    .chars().nth() 在一个字符串上。


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

    .nth() -> 返回迭代器的第 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"));
    

    【讨论】:

    • 重要的是要注意Chars::nth(n) 消耗 n 个字符,而不仅仅是简单的索引。如文档所述,在同一个迭代器上多次调用 nth(0) 将返回不同的元素。
    • 如果您确实不确定第 N 个字符是否存在,使用 expect()unwrap()不会防止恐慌。无论如何,代码都会恐慌,但期望会提供一个自定义的恐慌消息。另见:stackoverflow.com/questions/61301581/…
    【解决方案6】:

    在 Rust 中做这种事情的正确方法不是索引,而是迭代。这里的主要问题是 Rust 的字符串是用 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) 部分用于将完成的工作量减半。然后,我们只需将一个迭代器与另一个迭代器进行比较,以确保在每一步中第 n 个和第 n 个最后一个字节是等价的;如果是,则返回 true;如果不是,则为假。

    【讨论】:

    • FWIW,String 有一个直接的as_bytes。此外,您可以使用std::iter::order::equals。而不是allequals(iter.take(n), iter.rev().take(n))
    • 顺便说一句,约定意味着导入std::iter::order 并调用order::equals(..., ...)(我只是在评论中没有这样做,因为它会很吵)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-08
    • 2014-02-16
    • 2014-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多