web-dev-qa-db-ja.com

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がインデックス付けできない理由はありますか?データにアクセスするにはどうすればよいですか?

24
Sam Myers

Rustでこの種のことを行うための正しいアプローチは、インデックスではなくiterationです。ここでの主な問題はRustの文字列は、Unicode文字の可変長エンコードであるUTF-8でエンコードされます。長さが可変であるため、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)部分は、実行される作業量を半分にするためにあります。次に、1つのイテレータを他のイテレータと比較して、各ステップで最後のn番目とn番目のバイトが同等であることを確認します。存在する場合、trueを返します。そうでない場合はfalse。

25
Chris Morgan

はい、文字列へのインデックス付けは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 cluster で作業する必要があります。 nicode-segmentation crateを使用すると、書記素クラスターにもインデックスを付けることができます。

_use unicode_segmentation::UnicodeSegmentation

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

当然、書記素クラスタのインデックス付けには、コードポイントへのインデックス付けと同じ文字列全体の走査という要件があります。

22

探しているものがインデックスに似ている場合は、使用できます

.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"));
18
Angel Angel