我试图从文件中获取随机行:
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
中收集整个文件?
答案 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
。
你原来的问题是:
slice::get
返回向量中的可选引用。 您可以克隆此值或获取值的所有权:
let line = lines[n].cloned()
let line = lines.swap_remove(n)
如果n
超出范围,这两种情况都会出现恐慌,这在你知道自己已经在界限时是合理的。
BufRead::lines
返回io::Result<String>
,因此您必须处理该错误案例。答案 1 :(得分:1)
有没有更好的方法从文件中获取随机行,而不涉及在
Vec
中收集整个文件?
如果只知道行数,您将始终需要读取整个文件。但是,您不需要将所有内容存储在内存中,您可以逐个读取行并将其丢弃,以便最后只保留一行。这是怎么回事:
n
,随机选择并:
(n-1)/n
,1/n
。请注意,这或多或少是sample_iter
所做的,除了sample_iter
更通用,因为它可以在任何迭代器上工作,并且它可以选择任何大小的样本(例如,它可以选择{ {1}}项随机)。