web-dev-qa-db-fra.com

Comment indexer une chaîne dans Rust

J'essaie d'indexer une chaîne dans Rust, mais le compilateur renvoie une erreur. Mon code (problème Project Euler 4, aire de jeux ):

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
}

L'erreur:

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`

Y a-t-il une raison pour laquelle String ne peut pas être indexé? Comment puis-je accéder aux données?

24
Sam Myers

L'approche correcte pour faire ce genre de chose dans Rust n'est pas l'indexation mais l'itération . Le principal problème ici est que Les chaînes de Rust sont encodées en UTF-8, un encodage de longueur variable pour les caractères Unicode. Étant de longueur variable, la position de mémoire du nième caractère ne peut pas être déterminée sans regarder la chaîne. Cela signifie également que l'accès au nième caractère a un exécution de O (n)!

Dans ce cas particulier, vous pouvez parcourir les octets, car votre chaîne est connue pour ne contenir que les caractères 0 à 9 (l'itération sur les caractères est la solution la plus générale mais est un peu moins efficace).

Voici un code idiomatique pour y parvenir ( terrain de je ):

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

Nous parcourons les octets de la chaîne à la fois en avant (num_string.bytes().take(half)) et en arrière (num_string.bytes().rev().take(half)) simultanément; la partie .take(half) est là pour diviser par deux la quantité de travail effectuée. On compare ensuite simplement un itérateur à l'autre pour s'assurer à chaque étape que les nième et nième octets sont équivalents; s'ils le sont, cela renvoie vrai; sinon, faux.

25
Chris Morgan

Oui, l'indexation dans une chaîne n'est pas disponible dans Rust. La raison en est que Rust sont encodées en UTF-8 en interne, donc le concept d'indexation lui-même serait ambigu et les gens en abuseraient: l'indexation des octets est rapide, mais presque toujours incorrecte (lorsque votre texte contient des symboles non ASCII, l'indexation des octets peut vous laisser à l'intérieur d'un caractère, ce qui est vraiment mauvais si vous avez besoin d'un traitement de texte), tandis que l'indexation des caractères n'est pas gratuite car UTF-8 est un encodage de longueur variable, vous avez donc pour parcourir la chaîne entière pour trouver le point de code requis.

Si vous êtes certain que vos chaînes contiennent des caractères ASCII uniquement), vous pouvez utiliser la méthode as_bytes() sur &str Qui renvoie une tranche d'octets, puis l'indexer dans cette tranche:

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

Si vous avez besoin d'indexer des points de code, vous devez utiliser l'itérateur char():

num_string.chars().nth(i).unwrap()

Comme je l'ai dit ci-dessus, cela nécessiterait de parcourir l'itérateur entier jusqu'à l'élément de code ith.

Enfin, dans de nombreux cas de traitement de texte, il est en fait nécessaire de travailler avec grappes de graphèmes plutôt qu'avec des points de code ou des octets. À l'aide de la caisse segmentation unicode , vous pouvez également indexer en grappes de graphèmes:

use unicode_segmentation::UnicodeSegmentation

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

Naturellement, l'indexation de grappes grapheme a la même exigence de traverser la chaîne entière que l'indexation en points de code.

22
Vladimir Matveev

Si ce que vous recherchez est similaire à un index, vous pouvez utiliser

.chars() et .nth() sur une chaîne.


.chars() -> Renvoie un itérateur sur les chars d'une tranche de chaîne.

.nth() -> Renvoie le nième élément de l'itérateur, dans un Option


Vous pouvez maintenant utiliser ce qui précède de plusieurs manières, par exemple:

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