Как получить случайную строку из файла? - PullRequest
0 голосов
/ 11 июня 2018

Я пытаюсь получить случайную строку из файла:

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 голосов
/ 11 июня 2018

Есть ли лучший способ получить случайную строку из файла, которая не включает сбор всего файла в Vec?

Вам всегда нужно будет прочитатьЦелый файл, если только знать количество строк.Тем не менее, вам не нужно хранить все в памяти, вы можете читать строки одну за другой и отбрасывать их по ходу работы, чтобы в конце оставалась только одна строка.Вот как это происходит:

  • Чтение и сохранение первой строки;
  • Чтение второй строки, выбор случайного выбора и либо:
    • сохранение первой строкис вероятностью 50%,
    • или отбросить первую строку и сохранить вторую строку с вероятностью 50%,
  • Продолжить чтение строк из файла и дляномер строки n, нарисуйте случайный выбор и:
    • сохраните текущую сохраненную строку с вероятностью (n-1)/n,
    • или замените текущую сохраненную строку текущей строкой с вероятностьюиз 1/n.

Обратите внимание, что это более или менее то, что делает sample_iter, за исключением того, что sample_iter является более универсальным, поскольку можетработать на любом итераторе, и он может выбирать выборки любого размера (например, он может выбирать k элементов случайным образом).

0 голосов
/ 11 июня 2018

Используйте sample_iter для случайной выборки из итератора с использованием выборка из резервуара .Он будет сканировать весь файл один раз, создавая 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 возвращает необязательную ссылку на вектор.

Вы можете либо клонировать это, либо стать владельцем значения:

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

Обе эти паники, если n выходит за пределы, чторазумно здесь, поскольку вы знаете, что вы находитесь в пределах.

BufRead::lines возвращает io::Result<String>, поэтому вы должны обработать этот случай ошибки.
...