web-dev-qa-db-fra.com

Pourquoi capitaliser la première lettre d'une chaîne si compliquée dans Rust?

Je voudrais mettre en majuscule la première lettre d'un &str. C'est un problème simple et j'espère une solution simple. L'intuition me dit de faire quelque chose comme ça:

let mut s = "foobar";
s[0] = s[0].to_uppercase();

Mais &strs ne peut pas être indexé comme ceci. La seule façon dont j'ai pu le faire semble trop compliquée. Je convertis le &str en itérateur, convertissez l'itérateur en vecteur, en majuscule le premier élément du vecteur, ce qui crée un itérateur, dans lequel j'indexe, créant un Option, que je déballe pour me donner le haut- première lettre encadrée. Ensuite, je convertis le vecteur en un itérateur, que je convertis en String, que je convertis en &str.

let s1 = "foobar";
let mut v: Vec<char> = s1.chars().collect();
v[0] = v[0].to_uppercase().nth(0).unwrap();
let s2: String = v.into_iter().collect();
let s3 = &s2;

Existe-t-il un moyen plus simple que cela, et si oui, quoi? Sinon, pourquoi est Rust conçu de cette façon?

Question similaire

51
marshallm

Pourquoi est-il si alambiqué?

Décomposons-le, ligne par ligne

let s1 = "foobar";

Nous avons créé une chaîne littérale codée en TF-8 . UTF-8 nous permet d'encoder les 1114112 points de code de nicode d'une manière assez compacte si vous venez d'une région du monde qui tape principalement des caractères trouvés dans - ASCII , un standard créé en 1963. UTF-8 est un codage de longueur variable, ce qui signifie qu'un seul point de code peut prendre de 1 à 4 octets . Les encodages plus courts sont réservés pour ASCII, mais de nombreux kanji prennent 3 octets en UTF-8 .

let mut v: Vec<char> = s1.chars().collect();

Cela crée un vecteur de char caractères. Un caractère est un nombre de 32 bits qui correspond directement à un point de code. Si nous avons commencé avec du texte ASCII uniquement, nous avons quadruplé nos besoins en mémoire. Si nous avions un tas de personnages de le plan astral , alors peut-être que nous n'en avons pas utilisé beaucoup plus.

v[0] = v[0].to_uppercase().nth(0).unwrap();

Cela saisit le premier point de code et demande qu'il soit converti en une variante majuscule. Malheureusement pour ceux d'entre nous qui ont grandi en anglais, il y a pas toujours une simple correspondance biunivoque d'une "petite lettre" à une "grande lettre" . Remarque: nous les appelons majuscules et minuscules car une boîte de lettres était au-dessus de l'autre boîte de lettres dans la journée .

Ce code panique lorsqu'un point de code n'a pas de variante majuscule correspondante. Je ne sais pas si elles existent, en fait. Il peut également sémantiquement échouer lorsqu'un point de code a une variante en majuscule qui a plusieurs caractères, comme l'allemand ß. Notez que ß peut ne jamais être capitalisé dans le monde réel, c'est le juste exemple dont je me souviens et que je recherche toujours. En date du 29/06/2017, en fait, les règles officielles de l'orthographe allemande ont été mises à jour pour que les deux "ẞ" et " SS "sont des majuscules valides !

let s2: String = v.into_iter().collect();

Ici, nous reconvertissons les caractères en UTF-8 et demandons une nouvelle allocation pour les stocker, car la variable d'origine était stockée en mémoire constante afin de ne pas prendre de mémoire au moment de l'exécution.

let s3 = &s2;

Et maintenant, nous prenons une référence à ce String.

C'est un problème simple

Malheureusement, ce n'est pas vrai. Peut-être devrions-nous nous efforcer de convertir le monde en espéranto ?

Je suppose char::to_uppercase gère déjà correctement Unicode.

Oui, j'espère bien. Malheureusement, Unicode n'est pas suffisant dans tous les cas. Merci à huon pour avoir souligné le turc I , où la partie supérieure ( © ) et les minuscules ( i ) les versions ont un point. Autrement dit, il n'y a pas un capitalisation correcte de la lettre i; cela dépend aussi de locale du texte source.

pourquoi la nécessité de toutes les conversions de types de données?

Parce que les types de données avec lesquels vous travaillez sont importants lorsque vous vous inquiétez de l'exactitude et des performances. Un char est de 32 bits et une chaîne est encodée en UTF-8. Ce sont des choses différentes.

l'indexation pourrait renvoyer un caractère Unicode sur plusieurs octets

Il peut y avoir une terminologie non concordante ici. Un char est un caractère Unicode multi-octets.

Slicing une chaîne est possible si vous passez octet par octet, mais la bibliothèque standard paniquera si vous n'êtes pas sur une limite de caractère.

L'une des raisons pour lesquelles l'indexation d'une chaîne pour obtenir un caractère n'a jamais été implémentée est que tant de personnes utilisent mal les chaînes comme des tableaux de ASCII caractères. Indexer une chaîne pour set = un caractère ne pourrait jamais être efficace - il faudrait pouvoir remplacer 1-4 octets par une valeur qui est également 1-4 octets, ce qui fait rebondir le reste de la chaîne.

to_uppercase pourrait renvoyer un caractère majuscule

Comme mentionné ci-dessus, ß est un caractère unique qui, une fois mis en majuscule, devient deux caractères .

Solutions

Voir aussi réponse de trentcl qui ne met en majuscule que ASCII caractères.

Original

Si je devais écrire le code, ça ressemblerait à:

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().chain(c).collect(),
    }
}

fn main() {
    println!("{}", some_kind_of_uppercase_first_letter("joe"));
    println!("{}", some_kind_of_uppercase_first_letter("jill"));
    println!("{}", some_kind_of_uppercase_first_letter("von Hagen"));
    println!("{}", some_kind_of_uppercase_first_letter("ß"));
}

Mais je rechercherais probablement majuscule ou nicode sur crates.io et laisserais quelqu'un plus intelligent que moi le gérer.

Amélioré

En parlant de "quelqu'un de plus intelligent que moi", souligne Veedrac qu'il est probablement plus efficace de reconvertir l'itérateur en tranche après avoir accédé aux premiers points de code majuscules. Cela permet un memcpy du reste des octets.

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}
74
Shepmaster

Existe-t-il un moyen plus simple que cela, et si oui, quoi? Sinon, pourquoi est Rust conçu de cette façon?

Eh bien, oui et non. Comme l'indique l'autre réponse, votre code n'est pas correct et paniquera si vous lui donnez quelque chose comme བོད་ སྐད་ ལ་. Donc, faire cela avec la bibliothèque standard de Rust est encore plus difficile que vous ne le pensiez au départ.

Cependant, Rust est conçu pour encourager la réutilisation du code et faciliter l'introduction de bibliothèques. Ainsi, la manière idiomatique de capitaliser une chaîne est en fait assez acceptable:

extern crate inflector;
use inflector::Inflector;

let capitalized = "some string".to_title_case();
17
user8174234

Ce n'est pas particulièrement compliqué si vous pouvez limiter votre entrée aux chaînes ASCII uniquement.

Puisque Rust 1.23, str a un make_ascii_uppercase méthode (dans les anciennes versions de Rust, elle était disponible via le trait AsciiExt). Cela signifie que vous pouvez mettre en majuscule des tranches de chaîne uniquement ASCII avec une relative facilité:

fn make_ascii_titlecase(s: &mut str) {
    if let Some(r) = s.get_mut(0..1) {
        r.make_ascii_uppercase();
    }
}

Cela transformera "taylor" en "Taylor", mais ça ne tournera pas "édouard" en "Édouard". ( aire de jeux )

Utiliser avec précaution.

6
trentcl