2016-07-13 4 views
0

Ich versuche, ein neues Merkmal für einen String zu implementieren, der eine Funktion hat, die den ersten Buchstaben jedes Strings groß schreibt und den Rest nicht groß schreibt. Ich basiere die Schnittstelle der Funktion auf to_uppercase() und to_lowercase() in der Rust Standard Library.Wie sammle ich mehrere Iterator-Typen?

use std::io; 

trait ToCapitalized { 
    fn to_capitalized(&self) -> String; 
} 

impl ToCapitalized for String { 
    fn to_capitalized(&self) -> String { 
     self.chars().enumerate().map(|(i, c)| { 
      match i { 
       0 => c.to_uppercase(), 
       _ => c.to_lowercase(), 
      } 
     }).collect() 
    } 
} 

fn main() { 
    let mut buffer = String::new(); 
    io::stdin().read_line(&mut buffer).ok().expect("Unable to read from stdin."); 

    println!("{}", buffer.to_capitalized()); 
} 

Dieser Code basiert auf einem Vorschlag gegeben here, aber der Code ist veraltet und verursacht mehrere Kompilierungsfehlern. Das einzige Problem, das ich jetzt mit meiner Implementierung habe die folgende Fehlermeldung:

src/main.rs:10:13: 13:14 error: match arms have incompatible types [E0308] 
src/main.rs:10    match i { 
         ^
src/main.rs:10:13: 13:14 help: run `rustc --explain E0308` to see a detailed explanation 
src/main.rs:10:13: 13:14 note: expected type `std::char::ToUppercase` 
src/main.rs:10:13: 13:14 note: found type `std::char::ToLowercase` 
src/main.rs:12:22: 12:38 note: match arm with an incompatible type 
src/main.rs:12     _ => c.to_lowercase(), 

Also kurz gesagt, die Rückgabewerte von fn to_uppercase(&self) -> ToUppercase und fn to_lowercase(&self) -> ToLowercase können nicht zusammen gesammelt werden, da die Karte nun mehrere Rückgabetypen hat.

Ich habe versucht, sie zu einem anderen gemeinsamen Iterator Typ wie Bytes und Chars zu werfen, aber diese Iterator-Typen können nicht gesammelt werden, um eine Zeichenfolge zu bilden. Irgendwelche Vorschläge?

Antwort

2

Nachdem ich mir die Implementierung für pub fn to_uppercase(&self) -> Stringhere angesehen habe, habe ich eine Lösung entwickelt, die ein bisschen wie ein Hybrid zwischen den Lösungen von Dogbert und DK. Und der Implementierung in der Standardbibliothek ist. Es funktioniert sogar mit Unicode!

fn to_capitalized(&self) -> String { 
    match self.len() { 
     0 => String::new(), 
     _ => { 
      let mut s = String::with_capacity(self.len()); 
      s.extend(self.chars().next().unwrap().to_uppercase()); 
      s.extend(self.chars().skip(1).flat_map(|c| c.to_lowercase())); 
      return s; 
     } 
    } 
} 

Working Rust Playground Example

Edit: Für bessere Sichtbarkeit, Shepmaster die vereinfachte und optimierte Lösung:

fn to_capitalized(&self) -> String { 
    let mut s = String::with_capacity(self.len()); 
    let mut chars = self.chars(); 

    s.extend(chars.by_ref().take(1).flat_map(|c| c.to_uppercase())); 
    s.extend(chars.flat_map(|c| c.to_lowercase())); 

    s 
} 
+1

[Kann vereinfacht und leicht optimiert werden] (https://play.rust-lang.org/?gist=dfa85b1d5e07d8e5e29785c5e530ee76). – Shepmaster

1

Hier ist, wie ich es tun würde:

trait ToCapitalized { 
    fn to_capitalized(&self) -> String; 
} 

impl ToCapitalized for String { 
    fn to_capitalized(&self) -> String { 
     match self.chars().next() { 
      Some(c) => { 
       c.to_uppercase() 
        .chain(self.chars().skip(1).flat_map(|c| c.to_lowercase())) 
        .collect() 
      } 
      None => String::new(), 
     } 
    } 
} 

fn main() { 
    println!("{}", "fOoBaR".to_string().to_capitalized()); 
} 

Dies wird ein wenig langsamer als die ideale Lösung sein, da sie decodiert die ersten Zeichen zweimal, aber es ist ziemlich IMO lesbar.

Ausgang:

Foobar 
+0

@Jared wird es nicht ein Speicherleck (die Boxen würden, sobald 'collect' ausgeplant bekommen ist mit ihnen gemacht), aber es wäre schrecklich ineffizient. Bitte sehen Sie die bearbeitete Antwort. – Dogbert

2

Casting ist selten ein guter Ansatz, um Typ-Probleme in Rust zu lösen. Die richtige Lösung wäre hier, einen Typ zu schreiben (oder eine Kiste zu finden, der definiert), der verschiedene Iterator-Typen vereinheitlicht. Aber das würde erfordern Aufwand, so ist es einfacher, nur collect aus dem Fenster zu werfen:

trait ToCapitalized { 
    fn to_capitalized(&self) -> String; 
} 

impl ToCapitalized for String { 
    fn to_capitalized(&self) -> String { 
     let mut r = String::with_capacity(self.len()); 
     for (i, c) in self.chars().enumerate() { 
      match i { 
       0 => r.extend(c.to_uppercase()), 
       _ => r.extend(c.to_lowercase()), 
      } 
     } 
     r 
    } 
} 

fn main() { 
    let buffer = String::from("canberra"); 
    println!("{}", buffer.to_capitalized()); 
} 

Dies wird, mehr oder weniger, was collect würde tun sowieso wenn Sie irgendeine Art haben, um „entweder ToUppercase oder ToLowercase ". In der Mehrzahl der Fälle führt dies auch nur eine einzige Zuweisung durch.

+0

Da ist wahrscheinlich irgendwo in der stdlib ein Iterator-Typ, aber ich bezweifle, dass irgendjemand von uns in der Lage sein wird, ihn zu finden. Ehrlich, ich fühle, dass es zu viele sind, aber ich schweife ab. Das sieht nach der besten Lösung aus, weil es einfach auf den Sammelaufruf für eine ähnliche for-Schleife verzichtet, was meiner Meinung nach der intelligenteste Ansatz wäre. Danke für deinen Beitrag. –

Verwandte Themen