2016-02-13 13 views
10

Czy istnieje idiomatyczny sposób przetwarzania pliku po jednym znaku w Rust?Odczytuj plik znak po znaku w Rust

To wydaje się być mniej więcej co jestem po:

let mut f = io::BufReader::new(try!(fs::File::open("input.txt"))); 

for c in f.chars() { 
    println!("Character: {}", c.unwrap()); 
} 

Ale Read::chars jest nadal niestabilna jak od Rust v1.6.0.

Zastanawiam się nad użyciem Read::read_to_string, ale plik może być duży i nie chcę go wszystkie odczytać w pamięci.

+1

Dla niektórych typów plików tekstowych. 'F.lines() flat_map (| l | l.chars()) '... ale to nie jest dobre rozwiązanie. –

+1

Czy rozważałeś właśnie skopiowanie implementacji w międzyczasie? To tylko ~ 100 linii i oznacza, że ​​twój kod będzie banalny do uaktualnienia, jeśli 'chars' będzie stabilne. – Veedrac

Odpowiedz

3

Porównajmy 4 podejścia.

1. Read::chars

Można skopiować Read::chars wdrożenia, ale jest oznaczone niestabilna z

semantykę częściowego odczytu/zapisu, gdzie błędy zdarzyć obecnie niejasne i mogą ulec zmianie

dlatego należy zachować ostrożność. W każdym razie wydaje się to najlepszym podejściem.

2. flat_map

flat_map alternatywa nie kompiluje:

use std::io::{BufRead, BufReader}; 
use std::fs::File; 

pub fn main() { 
    let mut f = BufReader::new(File::open("input.txt").expect("open failed")); 

    for c in f.lines().flat_map(|l| l.expect("lines failed").chars()) { 
     println!("Character: {}", c); 
    } 
} 

problemów jest to, że chars pożycza od napisu, ale tylko l.expect("lines failed") mieszka wewnątrz zamknięcia, więc kompilator daje błąd borrowed value does not live long enough.

3. Zagnieżdżone dla

Ten kod

use std::io::{BufRead, BufReader}; 
use std::fs::File; 

pub fn main() { 
    let mut f = BufReader::new(File::open("input.txt").expect("open failed")); 

    for line in f.lines() { 
     for c in line.expect("lines failed").chars() { 
      println!("Character: {}", c); 
     } 
    } 
} 

działa, ale utrzymuje alokacji ciąg dla każdej linii. Poza tym, jeśli w pliku wejściowym nie ma linii podziału, cały plik będzie ładowany do pamięci.

4.BufRead::read_until

Pamięć wydajny alternatywą Podejście 3 jest użycie Read::read_until i korzystać z jednego łańcucha, aby przeczytać każdy wiersz:

use std::io::{BufRead, BufReader}; 
use std::fs::File; 

pub fn main() { 
    let mut f = BufReader::new(File::open("input.txt").expect("open failed")); 

    let mut buf = Vec::<u8>::new(); 
    while f.read_until(b'\n', &mut buf).expect("read_until failed") != 0 { 
     // this moves the ownership of the read data to s 
     // there is no allocation 
     let s = String::from_utf8(buf).expect("from_utf8 failed"); 
     for c in s.chars() { 
      println!("Character: {}", c); 
     } 
     // this returns the ownership of the read data to buf 
     // there is no allocation 
     buf = s.into_bytes(); 
     buf.clear(); 
    } 
} 
0

Są tu dwa rozwiązania, które mają sens.

Po pierwsze, można skopiować implementację Read::chars() i użyć jej; to całkowicie trywialnie przeniesie twój kod do standardowej implementacji biblioteki, jeśli/kiedy się ustabilizuje.

Z drugiej strony można po prostu powtórzyć linię po linii (używając f.lines()), a następnie użyć line.chars() w każdej linii, aby uzyskać znaki. To trochę bardziej hacky, ale na pewno zadziała.

Jeśli potrzebujesz tylko jednej pętli, możesz użyć flat_map() z lambda podobnym do |line| line.chars().

+0

Jako heads-up, ogólnie rzecz biorąc, dobrze jest przenosić przydatne komentarze na odpowiedzi, ale jeśli naprawdę nie dodajesz do treści, prawdopodobnie [bardziej akceptowalne jest oznaczenie odpowiedzi jako wiki społeczności] (http: //meta.stackoverflow .com/q/269913/155423). – Shepmaster

+0

Przepraszamy! Postaram się to zrobić następnym razem. –