如何从文件中获取随机行?

时间:2018-06-10 21:01:19

标签: file vector io rust

我试图从文件中获取随机行:

extern crate rand;

use rand::Rng;
use std::{
    fs::File,
    io::{prelude::*, BufReader},
};

const FILENAME: &str = "/etc/hosts";

fn find_word() -> String {
    let f = File::open(FILENAME).expect(&format!("(;_;) file not found: {}", FILENAME));
    let f = BufReader::new(f);

    let lines: Vec<_> = f.lines().collect();

    let n = rand::thread_rng().gen_range(0, lines.len());
    let line = lines
        .get(n)
        .expect(&format!("(;_;) Couldn't get {}th line", n))
        .unwrap_or(String::from(""));

    line
}

此代码无效:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:18:16
   |
18 |       let line = lines
   |  ________________^
19 | |         .get(n)
20 | |         .expect(&format!("(;_;) Couldn't get {}th line", n))
   | |____________________________________________________________^ cannot move out of borrowed content

我尝试在.clone()之前和.expect(...)之前添加.unwrap_or(...),但它也犯了同样的错误。

有没有更好的方法从文件中获取随机行并不涉及在Vec中收集整个文件?

2 个答案:

答案 0 :(得分:2)

使用sample_iter使用reservoir sampling从迭代器中随机抽样。这将扫描整个文件一次,为每一行创建String,但不会为每一行创建一个巨大的向量:

fn find_word() -> String {
    let f = File::open(FILENAME)
        .unwrap_or_else(|e| panic!("(;_;) file not found: {}: {}", FILENAME, e));
    let f = BufReader::new(f);

    let lines = f.lines().map(|l| l.expect("Couldn't read line"));

    match rand::seq::sample_iter(&mut rand::thread_rng(), lines, 1) {
        Ok(mut v) => v.pop().unwrap(),
        Err(_) => panic!("File had no lines"),
    }
}
expect(&format!("..."))

不要这样做,无条件地分配内存。当没有失败时,就会浪费这种分配。如图所示使用unwrap_or_else

你原来的问题是:

  1. slice::get返回向量中的可选引用。
  2. 您可以克隆此值或获取值的所有权:

    let line = lines[n].cloned()
    
    let line = lines.swap_remove(n)
    

    如果n超出范围,这两种情况都会出现恐慌,这在你知道自己已经在界限时是合理的。

    1. BufRead::lines返回io::Result<String>,因此您必须处理该错误案例。

答案 1 :(得分:1)

  

有没有更好的方法从文件中获取随机行,而不涉及在Vec中收集整个文件?

如果只知道行数,您将始终需要读取整个文件。但是,您不需要将所有内容存储在内存中,您可以逐个读取行并将其丢弃,以便最后只保留一行。这是怎么回事:

  • 阅读并存储第一行;
  • 阅读第二行,随机选择并选择:
    • 保持第一行的概率为50%,
    • 或丢弃第一行并以50%的概率存储第二行
  • 继续阅读文件中的行和行号n,随机选择并:
    • 保持当前存储的行的概率为(n-1)/n
    • 或用当前行替换当前存储的行,概率为1/n

请注意,这或多或少是sample_iter所做的,除了sample_iter更通用,因为它可以在任何迭代器上工作,并且它可以选择任何大小的样本(例如,它可以选择{ {1}}项随机)。