2016-06-09 14 views
0

Ich habe versucht, eine Datei auf eine Weise zu lesen, die für meinen Zweck performant genug ist. Ich habe eine Liste von Datei-IDs, Namen und Zeilenindizes (geordnet) und für jedes Paar (file_id, file_name, line_index) muss ich die Datei öffnen, finde die Zeile nach Index und drucke.Mutieren von zwei abhängigen Werten in Schleife

Um leistungsfähiger zu sein (ich weiß, die Eingabe ist bestellt) Ich möchte die BufReader, die Zeile für Zeile liest und die Datei offen bleiben lassen, wenn möglich.

fn main() { 
    // positions in file 
    // structure: (file_id, file_name, line_index_in_file) 
    let positions = &vec![ 
     (1, String::from("file1"), 1), 
     (1, String::from("file1"), 2), 
     (1, String::from("file1"), 20), 
     (2, String::from("file2"), 15)]; 

    print_lines_from_file(&positions); 
} 

fn print_lines_from_file(found: &Vec<(i32, String, i32)>) { 
    let mut last_file_id = -1; 

    //let mut last_file_name = None; 
    let mut open_file = None; 
    let mut open_reader = None; 

    for &(file_id, ref file_name, pos_in_file) in found { 
     println!("{} {}", file_id, pos_in_file); 

     if last_file_id < file_id { 
      last_file_id = file_id; 
      //last_file_name = file_ids.get(&file_id); 

      if let Some(to_close) = open_file { 
       drop(open_reader.unwrap()); 
       drop(to_close); 
      } 
      //let file = File::open(last_file_name.unwrap()).unwrap(); 
      let file = File::open(file_name).unwrap(); 
      open_file = Some(file); 
      open_reader = Some(BufReader::new(&file)); 
     } 

     // use reader to find the line in file and process 
    } 
} 

Ich bin mit diesem Problem konfrontiert:

main.rs:40:48: 40:52 error: `file` does not live long enough 
main.rs:40    open_reader = Some(BufReader::new(&file)); 

main.rs:40:48: 40:52 error: use of moved value: `file` [E0382] 
main.rs:40    open_reader = Some(BufReader::new(&file)); 

Es ist offensichtlich, (file ‚s Lebensdauer sehr kurz ist), aber ich weiß nicht, wie es zu umgehen. Die BufReader hängt von File ab, aber ich muss die File später in der Schleife schließen, wenn sich file_id ändert.

Auch fühle ich mich nicht sehr bequem drop Aufruf so in Schleife, wie es mir aussieht, wie ich versuche, den Compiler zu täuschen. Ist dieser Ansatz in Ordnung?

Bitte auch wenn Sie bessere Lösung kennen (z. B. wie Sie die Datei über BufReader schließen, würde ich mich über den allgemeinen Einblick freuen, wie Sie es lösen können).

Antwort

3

Sie können die File nach Wert an die BufReader übergeben. Auf diese Weise haben Sie nur eine einzige Variable, die ein Dateihandle besitzt. Sie können take auf der Option verwenden, um den inneren Wert zu verschieben und eine None hinter sich lassen. Damit stellen Sie sicher, dass die Datei-Handle freigegeben wird, bevor die nächste genommen wird (also, wenn Sie die gleiche Datei wieder öffnen es nicht Panik)

let mut open_reader = None; 

for &(file_id, ref file_name, pos_in_file) in found { 
    println!("{} {}", file_id, pos_in_file); 

    if last_file_id < file_id { 
     last_file_id = file_id; 
     //last_file_name = file_ids.get(&file_id); 

     // take the value out of the `open_reader` to make sure that 
     // the file is closed, so we don't panic if the next statement 
     // tries to open the same file again. 
     open_reader.take(); 
     //let file = File::open(last_file_name.unwrap()).unwrap(); 
     let file = File::open(file_name).unwrap(); 
     open_reader = Some(BufReader::new(file)); 
    } 

    // use reader to find the line in file and process 
} 
+0

Die explizite 'drop' ist nicht erforderlich, es scheint. – Shepmaster

+0

@Shempmaster: aber es ist expliziter, deshalb habe ich es aufgenommen. Alternativ könnte man einen Kommentar hinzufügen, der die Absicht angibt ... Was wahrscheinlich besser ist :) –

+0

Ich habe den Text bearbeitet, um einen Kommentar anstelle des 'drop' einzufügen. –

2

Sie Eigentum an der Datei an den BufReader geben (das ist offensichtlich, da es nach Wert übergeben wird), anstatt es zu verleihen - es ist jetzt BufReader 's Job, um die Datei zu schließen. Wenn es fallen gelassen wird, wird die File, die es besitzt, der Reihe nach fallengelassen; Sie können also open_file komplett verlieren.

Der Compiler hält Sie erfolgreich davon ab, die Datei unter den BufReader Füßen zu zerstören.

2

Ich möchte die BufReader zwischenspeichern, die Zeile für Zeile liest und die Datei offen lassen, wenn möglich.

Der einfachste Weg, dies zu tun ist, um die Daten vor der Zeit zur Gruppe:

use std::collections::HashMap; 

fn print_lines_from_file(found: &[(i32, String, i32)]) { 
    let mut index = HashMap::new(); 
    for line in found { 
     let name = &line.1; 
     index.entry(name).or_insert_with(Vec::new).push(line); 
    } 

    for (file_name, lines) in &index { 
     let file = File::open(file_name).unwrap(); 

     for &&(file_id, _, line_index) in lines { 
      // do something with `file` 
      println!("processing ID {} ({}) line {}", file_id, file_name, line_index); 
     } 
    } 
} 

Beachten Sie, dass dies befreit Sie von einem speziellen Sentinel-Wert für file_id müssen (was auch sein könnte getan mit einem Option). Auch wenn Sie sagen, dass die Daten sortiert sind, können Sie die Fälle behandeln, in denen die file_id s nicht sind. Sie könnten auch den Fall unsortierter line_index es behandeln, indem Sie den Vektor sortieren, nachdem er abgeschlossen ist.

Zusätzlich:

  1. Sie haben eine doppelte Referenz in main - Sie müssen nicht &vec![...] sagen müssen.
  2. Sie sollten eine &[T] statt einer &Vec<T> akzeptieren.

Eine noch schönere Lösung, IMHO, ist itertools zu verwenden, insbesondere group_by_lazy:

extern crate itertools; 

use itertools::Itertools; 
use std::fs::File; 
use std::io::BufReader; 

fn main() { 
    // structure: (file_id, file_name, line_index_in_file) 
    let positions = [ 
     (1, String::from("file1"), 1), 
     (1, String::from("file1"), 2), 
     (1, String::from("file1"), 20), 
     (2, String::from("file2"), 15) 
    ]; 

    print_lines_from_file(&positions); 
} 

fn print_lines_from_file(found: &[(i32, String, i32)]) { 
    for (filename, positions) in &found.iter().group_by_lazy(|pos| &pos.1) { 
     println!("Opening file {}", filename); 
     // let file = File::open(file_name).expect("Failed to open the file"); 
     // let file = BufReader::new(file); 

     for &(id, _, line) in positions { 
      println!("Processing ID {}, line {}", id, line); 
     } 
    } 
} 
+0

Das ist eine nette Idee. Es braucht mehr Speicher als nötig, aber es könnte nützlich sein. (es gibt mehr Daten als in der Probe, die ich über 'vec! [..]' gemacht habe - ich wollte die Probe einfach genug machen) – stej

+0

@stej neue Version, die ziemlich gut aussieht ;-) – Shepmaster

+0

Tolle Idee. Ich akzeptiere Kers Antwort, da sie mehr mit meiner Frage zu tun hat, aber deine Lösung ist nett. Ich würde es nach Möglichkeit zweimal aufheben;) – stej