Как я могу иметь несколько итераторов для одних и тех же данных, относящихся к файлу? - PullRequest
0 голосов
/ 11 февраля 2020

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

use std::io::{self, BufRead};

fn main() {
    let cursor = io::Cursor::new(b"pillow\nbrick\r\nphone");

    let lines = cursor.lines().map(|l| l.unwrap());

    let soft_count = lines.filter(|line| line.contains("pillow")).count();

    let hard_count = lines.filter(|line| !line.contains("pillow")).count();
}

Однако, проверка заимствования выдает мне ошибку:

error[E0382]: use of moved value: `lines`
  --> src/main.rs:14:22
   |
8  |     let lines = cursor.lines().map(|l| l.unwrap());
   |         ----- move occurs because `lines` has type `std::iter::Map<std::io::Lines<std::io::Cursor<&[u8; 19]>>, [closure@src/main.rs:8:36: 8:50]>`, which does not implement the `Copy` trait
9  |     
10 |     let soft_count = lines
   |                      ----- value moved here
...
14 |     let hard_count = lines
   |                      ^^^^^ value used here after move

Я пытался обойти это, используя ссылку считая, чтобы разрешить множественное владение:

use std::io::{self, BufRead};
use std::rc::Rc;

fn main() {
    let cursor = io::Cursor::new(b"pillow\nbrick\r\nphone");

    let lines = Rc::new(cursor.lines().map(|l| l.unwrap()));

    let soft_count = Rc::clone(&lines)
        .filter(|line| line.contains("pillow"))
        .count();

    let hard_count = Rc::clone(&lines)
        .filter(|line| !line.contains("pillow"))
        .count();
}

Я получаю похожее сообщение об ошибке:

error[E0507]: cannot move out of an `Rc`
  --> src/main.rs:11:22
   |
11 |     let soft_count = Rc::clone(&lines)
   |                      ^^^^^^^^^^^^^^^^^ move occurs because value has type `std::iter::Map<std::io::Lines<std::io::Cursor<&[u8; 19]>>, [closure@src/main.rs:9:44: 9:58]>`, which does not implement the `Copy` trait

error[E0507]: cannot move out of an `Rc`
  --> src/main.rs:15:22
   |
15 |     let hard_count = Rc::clone(&lines)
   |                      ^^^^^^^^^^^^^^^^^ move occurs because value has type `std::iter::Map<std::io::Lines<std::io::Cursor<&[u8; 19]>>, [closure@src/main.rs:9:44: 9:58]>`, which does not implement the `Copy` trait

1 Ответ

1 голос
/ 11 февраля 2020

Вы не можете. Вместо этого вам нужно будет клонировать итератор или какой-то его строительный блок. В этом случае самое высокое, что вы можете клонировать, это Cursor:

use std::io::{self, BufRead};

fn main() {
    let cursor = io::Cursor::new(b"pillow\nbrick\r\nphone");

    let lines = cursor.clone().lines().map(|l| l.unwrap());
    let lines2 = cursor.lines().map(|l| l.unwrap());

    let soft_count = lines.filter(|line| line.contains("pillow")).count();

    let hard_count = lines2.filter(|line| !line.contains("pillow")).count();
}

Для фактического File вам нужно будет использовать try_clone, так как он может потерпеть неудачу , В любом случае вы будете ссылаться на одни и те же данные дважды, и будет храниться только информация итератора.


Для вашего конкретного случая c вам это не нужно. Фактически, повторение данных дважды неэффективно. Самая простая встроенная вещь, которую вы можете сделать, это partition итератор:

let (softs, hards): (Vec<_>, Vec<_>) = lines.partition(|line| line.contains("pillow"));

let soft_count = softs.len();
let hard_count = hards.len();

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

#[derive(Debug, Default)]
struct Count(usize);

impl<T> std::iter::Extend<T> for Count {
    fn extend<I>(&mut self, iter: I)
    where
        I: IntoIterator,
    {
        self.0 += iter.into_iter().count();
    }
}
let (softs, hards): (Count, Count) = lines.partition(|line| line.contains("pillow"));

let soft_count = softs.0;
let hard_count = hards.0;

Вы также можете просто использовать for l oop или построить что-то поверх fold :

let (soft_count, hard_count) = lines.fold((0, 0), |mut state, line| {
    if line.contains("pillow") {
        state.0 += 1;
    } else {
        state.1 += 1;
    }
    state
});
...