Warum ist das Großschreiben des ersten Buchstabens einer Zeichenfolge in Rust so verwickelt?

81

Ich möchte den ersten Buchstaben von a groß schreiben &str. Es ist ein einfaches Problem und ich hoffe auf eine einfache Lösung. Die Intuition sagt mir, dass ich so etwas tun soll:

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

Aber &strs kann nicht so indiziert werden. Der einzige Weg, auf dem ich es geschafft habe, scheint übermäßig kompliziert zu sein. Ich konvertiere den &strin einen Iterator, konvertiere den Iterator in einen Vektor, in Großbuchstaben das erste Element im Vektor, wodurch ein Iterator erstellt wird, in den ich indiziere, und einen Option, den ich auspacke, um mir den Großbuchstaben zu geben. Dann konvertiere ich den Vektor in einen Iterator, den ich in einen konvertiere, den ich in einen Stringkonvertiere &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;

Gibt es einen einfacheren Weg als diesen und wenn ja, welchen? Wenn nicht, warum ist Rust so konzipiert?

Ähnliche Frage

Marshallm
quelle
43
Es ist ein einfaches Problem - nein, das ist es nicht. Bitte groß schreiben, ßwenn es als Deutsch interpretiert wird. Hinweis: Es ist kein einzelnes Zeichen. Auch die Problemstellung kann kompliziert sein. Zum Beispiel wäre es unangemessen, das erste Zeichen des Nachnamens groß zu schreiben von Hagen. Dies alles ist ein Aspekt des Lebens in einer globalen Welt, in der es seit Tausenden von Jahren unterschiedliche Kulturen mit unterschiedlichen Praktiken gibt, und wir versuchen, all diese in 8 Bit und 2 Codezeilen zusammenzufassen.
Shepmaster
3
Was Sie darstellen, scheint ein Zeichencodierungsproblem zu sein, kein Datentypproblem. Ich gehe davon aus, dass char :: to_uppercase Unicode bereits ordnungsgemäß verarbeitet. Meine Frage ist, warum alle Datentypkonvertierungen erforderlich sind. Es scheint, dass die Indizierung ein Multi-Byte-Unicode-Zeichen zurückgeben könnte (kein einzelnes Byte-Zeichen, das nur ASCII annehmen würde), und to_uppercase könnte ein Großbuchstaben in jeder Sprache zurückgeben, in der es verfügbar ist, sofern eines in dieser Sprache verfügbar ist.
Marshallm
3
@marshallm behandelt char::to_uppercasezwar dieses Problem, aber Sie werfen seine Bemühungen weg, indem Sie nur den ersten Codepunkt ( nth(0)) anstelle aller Codepunkte nehmen, aus denen die Großschreibung besteht
Die Zeichenkodierung ist kein einfacher Vorgang, wie Joel in Software: Unicode ausgeführt hat .
Nathan
@ Shepmaster, im Allgemeinen bist du richtig. Es ist ein einfaches Problem in Englisch (die De-facto-Standardbasis für Programmiersprachen und Datenformate). Ja, es gibt Skripte, bei denen "Großschreibung" nicht einmal ein Konzept ist, und andere, bei denen es sehr kompliziert ist.
Paul Draper

Antworten:

98

Warum ist es so verworren?

Lassen Sie es uns Zeile für Zeile aufschlüsseln

let s1 = "foobar";

Wir haben eine Literalzeichenfolge erstellt, die in UTF-8 codiert ist . Mit UTF-8 können wir die 1.114.112 Codepunkte von Unicode auf eine ziemlich kompakte Weise codieren, wenn Sie aus einer Region der Welt stammen, in der hauptsächlich Zeichen eingegeben werden, die in ASCII , einem 1963 erstellten Standard, gefunden wurden. UTF-8 ist eine variable Länge Codierung, was bedeutet, dass ein einzelner Codepunkt 1 bis 4 Bytes benötigen kann . Die kürzeren Codierungen sind für ASCII reserviert, aber viele Kanji benötigen in UTF-8 3 Bytes .

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

Dies erzeugt einen Vektor von charAkteuren. Ein Zeichen ist eine 32-Bit-Zahl, die direkt einem Codepunkt zugeordnet ist. Wenn wir mit Nur-ASCII-Text begonnen haben, haben wir unseren Speicherbedarf vervierfacht. Wenn wir eine Reihe von Charakteren aus der Astralebene hätten , hätten wir vielleicht nicht viel mehr verwendet.

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

Dadurch wird der erste Codepunkt erfasst und die Konvertierung in eine Großbuchstabenvariante angefordert. Leider gibt es für diejenigen von uns, die mit Englisch aufgewachsen sind, nicht immer eine einfache Eins-zu-Eins-Zuordnung eines "kleinen Buchstabens" zu einem "großen Buchstaben" . Randnotiz: Wir nennen sie Groß- und Kleinbuchstaben, weil sich früher eine Buchstabenschachtel über der anderen Buchstabenschachtel befand .

Dieser Code gerät in Panik, wenn ein Codepunkt keine entsprechende Großbuchstabenvariante hat. Ich bin mir nicht sicher, ob diese tatsächlich existieren. Es kann auch semantisch fehlschlagen, wenn ein Codepunkt eine Großbuchstabenvariante mit mehreren Zeichen enthält, z. B. Deutsch ß. Beachten Sie, dass ß in der realen Welt möglicherweise nie groß geschrieben wird. Dies ist das einzige Beispiel, an das ich mich immer erinnern und nach dem ich suchen kann. Ab dem 29.06.2017 wurden die offiziellen Regeln der deutschen Rechtschreibung aktualisiert, sodass sowohl "ẞ" als auch "SS" gültige Großschreibung sind !

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

Hier konvertieren wir die Zeichen zurück in UTF-8 und benötigen eine neue Zuordnung, um sie zu speichern, da die ursprüngliche Variable im konstanten Speicher gespeichert wurde, um zur Laufzeit keinen Speicher zu belegen.

let s3 = &s2;

Und jetzt nehmen wir einen Hinweis darauf String.

Es ist ein einfaches Problem

Dies ist leider nicht wahr. Vielleicht sollten wir uns bemühen, die Welt zum Esperanto zu konvertieren ?

Ich gehe davon aus char::to_uppercase, dass Unicode bereits richtig gehandhabt wird.

Ja, das hoffe ich sehr. Leider reicht Unicode nicht in allen Fällen aus. Vielen Dank an huon für den Hinweis auf das türkische I , bei dem sowohl die Groß- ( İ ) als auch die Kleinbuchstaben ( i ) einen Punkt haben. Das heißt, es nicht ist eine angemessene Kapitalisierung des Briefes i; Dies hängt auch vom Gebietsschema des Quelltextes ab.

Warum müssen alle Datentypkonvertierungen durchgeführt werden?

Weil die Datentypen, mit denen Sie arbeiten, wichtig sind, wenn Sie sich um Korrektheit und Leistung sorgen. A charist 32-Bit und eine Zeichenfolge ist UTF-8-codiert. Sie sind verschiedene Dinge.

Die Indizierung kann ein Multi-Byte-Unicode-Zeichen zurückgeben

Möglicherweise gibt es hier eine nicht übereinstimmende Terminologie. A char ist ein Multi-Byte-Unicode-Zeichen.

Das Schneiden einer Zeichenfolge ist möglich, wenn Sie Byte für Byte arbeiten. Die Standardbibliothek gerät jedoch in Panik, wenn Sie sich nicht an einer Zeichengrenze befinden.

Einer der Gründe, warum die Indizierung einer Zeichenfolge zum Abrufen eines Zeichens nie implementiert wurde, ist, dass so viele Benutzer Zeichenfolgen als Arrays von ASCII-Zeichen missbrauchen. Das Indizieren einer Zeichenfolge zum Festlegen eines Zeichens könnte niemals effizient sein - Sie müssten in der Lage sein, 1-4 Bytes durch einen Wert zu ersetzen, der ebenfalls 1-4 Bytes beträgt, was dazu führt, dass der Rest der Zeichenfolge ziemlich viel herumspringt.

to_uppercase könnte ein Großbuchstaben zurückgeben

Wie oben erwähnt, ßhandelt es sich um ein einzelnes Zeichen, das bei Großschreibung zu zwei Zeichen wird .

Lösungen

Siehe auch die Antwort von trentcl, bei der nur ASCII-Zeichen in Großbuchstaben geschrieben werden.

Original

Wenn ich den Code schreiben müsste, würde er so aussehen:

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("ß"));
}

Aber ich würde wahrscheinlich auf crates.io nach Großbuchstaben oder Unicode suchen und jemanden schlauer als mich damit umgehen lassen.

Verbessert

Veedrac spricht von "jemandem, der schlauer ist als ich" und weist darauf hin, dass es wahrscheinlich effizienter ist, den Iterator nach dem Zugriff auf die ersten Großbuchstaben wieder in ein Slice umzuwandeln. Dies ermöglicht einen memcpyRest der Bytes.

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(),
    }
}
Shepmaster
quelle
34
Nachdem ich viel darüber nachgedacht habe, verstehe ich diese Designentscheidungen besser. Die Standardbibliothek sollte die vielseitigsten, leistungsfähigsten und sichersten Kompromisse wählen, die möglich sind. Andernfalls werden Entwickler gezwungen, Kompromisse einzugehen, die für ihre Anwendung, Architektur oder ihr Gebietsschema möglicherweise nicht geeignet sind. Oder es könnte zu Mehrdeutigkeiten und Missverständnissen führen. Wenn ich andere Kompromisse bevorzuge, kann ich eine Bibliothek eines Drittanbieters auswählen oder selbst schreiben.
Marshallm
13
@ Marshallsh, das ist wirklich toll zu hören! Ich befürchte, dass viele Rust-Neulinge die von den Rust-Designern getroffenen Entscheidungen falsch verstehen und sie einfach als zu kompliziert ohne Nutzen abschreiben. Durch das Stellen und Beantworten von Fragen hier habe ich eine Wertschätzung für die Sorgfalt gewonnen, die erforderlich ist, um in solche Entwürfe einzusteigen und hoffentlich ein besserer Programmierer zu werden. Offen zu sein und bereit zu sein, mehr zu lernen, ist eine großartige Eigenschaft, die man als Programmierer haben muss.
Shepmaster
6
Das "türkische i" ist ein Beispiel für die Abhängigkeit vom Gebietsschema, die für diese spezielle Frage direkter relevant ist als das Sortieren.
Huon
6
Ich bin überrascht, dass sie Großbuchstaben und Kleinbuchstaben haben, aber nicht Großbuchstaben. IIRC, einige Unicode-Zeichen haben tatsächlich eine spezielle Titelkoffer-Variante.
Tim
6
Übrigens ist möglicherweise nicht einmal ein einzelner Codepunkt die richtige Einheit zum Konvertieren. Was ist, wenn das erste Zeichen ein Graphemcluster ist, der im oberen Fall eine besondere Behandlung erhalten sollte? (Es kommt vor, dass zerlegte Umlaute funktionieren, wenn Sie nur den Grundcharakter in Großbuchstaben schreiben, aber ich weiß nicht, ob dies allgemein zutrifft.)
Sebastian Redl
21

Gibt es einen einfacheren Weg als diesen und wenn ja, welchen? Wenn nicht, warum ist Rust so konzipiert?

Ja und nein. Ihr Code ist, wie die andere Antwort hervorhob, nicht korrekt und gerät in Panik, wenn Sie ihm so etwas wie བོད་ སྐད་ ལ་ geben. Dies ist mit der Standardbibliothek von Rust noch schwieriger als ursprünglich angenommen.

Rust wurde jedoch entwickelt, um die Wiederverwendung von Code zu fördern und das Einbringen von Bibliotheken zu vereinfachen. Die idiomatische Art, einen String groß zu schreiben, ist also eigentlich ziemlich schmackhaft:

extern crate inflector;
use inflector::Inflector;

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

quelle
4
Die Frage des Benutzers klingt eher so, als würde er es wollen .to_sentence_case().
Christopher Oezbek
1
Leider hilft es nicht, Dinge zu benennen ... Dies ist eine großartige Bibliothek, die ich noch nie gesehen habe, aber der Name ist (für mich) schwer zu merken und hat Funktionen, die kaum etwas mit der tatsächlichen Beugung zu tun haben, eine davon dein Beispiel sein.
Sahsahae
11

Es ist nicht besonders kompliziert, wenn Sie Ihre Eingabe auf reine ASCII-Zeichenfolgen beschränken können.

Da Rust 1.23 streine make_ascii_uppercaseMethode hat (in älteren Rust-Versionen war sie über das AsciiExtMerkmal verfügbar ). Dies bedeutet, dass Sie relativ einfach nur ASCII-Zeichenfolgen in Großbuchstaben schreiben können:

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

Dies wird sich "taylor"in "Taylor", aber es lässt sich nicht einschalten "édouard"zu "Édouard". ( Spielplatz )

Mit Vorsicht verwenden.

trentcl
quelle
2
Helfen Sie einem Rust-Neuling, warum ist rveränderlich? Ich sehe das sist veränderlich str. Ohhhh ok: Ich habe die Antwort auf meine eigene Frage: get_mut(hier mit einem Bereich genannt) kehrt explizit zurück Option<&mut>.
Steven Lu
0

Auf diese Weise habe ich dieses Problem gelöst. Beachten Sie, dass ich überprüfen musste, ob self nicht ascii ist, bevor ich in Großbuchstaben umwandelte.

trait TitleCase {
    fn title(&self) -> String;
}

impl TitleCase for &str {
    fn title(&self) -> String {
        if !self.is_ascii() || self.is_empty() {
            return String::from(*self);
        }
        let (head, tail) = self.split_at(1);
        head.to_uppercase() + tail
    }
}

pub fn main() {
    println!("{}", "bruno".title());
    println!("{}", "b".title());
    println!("{}", "🦀".title());
    println!("{}", "ß".title());
    println!("{}", "".title());
    println!("{}", "བོད་སྐད་ལ".title());
}

Ausgabe

Bruno
B
🦀
ß

བོད་སྐད་ལ 
Bruno Rocha - Rochacbruno
quelle
-1

Hier ist eine Version, die etwas langsamer ist als die verbesserte Version von @ Shepmaster, aber auch idiomatischer :

fn capitalize_first(s: &str) -> String {
    let mut chars = s.chars();
    chars
        .next()
        .map(|first_letter| first_letter.to_uppercase())
        .into_iter()
        .flatten()
        .chain(chars)
        .collect()
}
yuyoyuppe
quelle
-1

Ich habe es so gemacht:

fn str_cap(s: &str) -> String {
  format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..])
}

Wenn es sich nicht um eine ASCII-Zeichenfolge handelt:

fn str_cap(s: &str) -> String {
  format!("{}{}", s.chars().next().unwrap().to_uppercase(), 
  s.chars().skip(1).collect::<String>())
}
Nikolai Lasunov
quelle