如何使用同一个迭代器两次,一次用于计数,一次用于迭代?

问题描述 投票:0回答:3

计数的时候好像消耗了一个迭代器。如何使用同一个迭代器进行计数然后对其进行迭代?

我正在尝试计算文件中的行数,然后打印它们。我能够读取文件内容,我能够计算行数,但随后我无法再迭代这些行,就好像内部光标位于迭代器的末尾一样。

use std::fs::File;
use std::io::prelude::*;

fn main() {
    let log_file_name = "/home/myuser/test.log";
    let mut log_file = File::open(log_file_name).unwrap();
    let mut log_content: String = String::from("");
    //Reads the log file.
    log_file.read_to_string(&mut log_content).unwrap();
    //Gets all the lines in a Lines struct.
    let mut lines = log_content.lines();
    //Uses by_ref() in order to not take ownership
    let count = lines.by_ref().count();
    println!("{} lines", count); //Prints the count
                                 //Doesn't enter in the loop
    for value in lines {
        println!("{}", value);
    }
}

Iterator
没有
reset
方法,但似乎内部游标在计数后位于迭代器的末尾。是否必须再次调用 Lines
 来创建新的 
log_content.lines()
 或者我可以重置内部光标吗?

目前,我找到的解决方法是创建一个新的迭代器:

use std::fs::File;
use std::io::prelude::*;

fn main() {
    let log_file_name = "/home/myuser/test.log";
    let mut log_file = File::open(log_file_name).unwrap();
    let mut log_content: String = String::from("");
    //Reads the log file.
    log_file.read_to_string(&mut log_content).unwrap();
    //Counts all and consume the iterator
    let count = log_content.lines().count();
    println!("{} lines", count);
    //Creates a pretty new iterator
    let lines = log_content.lines();
    for value in lines {
        println!("{}", value);
    }
}
rust iterator
3个回答
26
投票

调用

count()
会消耗迭代器,因为它实际上会迭代直到完成(即
next()
返回
None
)。

您可以使用

by_ref()
来防止消耗迭代器,但迭代器仍然会被驱动完成(
by_ref()
实际上只是返回对迭代器的可变引用,并且
Iterator
也实现了可变引用:
impl<'a, I> Iterator for &'a mut I
)。

如果迭代器包含完成后您想要重用的其他状态,这仍然很有用,但在这种情况下则不然。

您可以简单地尝试分叉迭代器(如果没有副作用,它们通常会实现

Clone
),尽管在这种情况下重新创建它也同样好(大多数时候创建迭代器很便宜;真正的通常只有当您通过直接或间接调用
next
来驱动它时,工作才会完成。

所以不,(在这种情况下)您无法重置它,是的,您需要创建一个新的(或在使用它之前克隆它)。


11
投票

其他答案已经很好地解释了,您可以重新创建迭代器或克隆它。

如果迭代行为过于昂贵或者不可能多次执行(例如从网络套接字读取),则另一种解决方案是创建迭代器值的集合,以便您获取长度和值。

这确实需要存储迭代器中的每个值; 天下没有免费的午餐

use std::fs;

fn main() {
    let log_content = fs::read_to_string("/home/myuser/test.log").unwrap();
    let lines: Vec<_> = log_content.lines().collect();

    println!("{} lines", lines.len());
    for value in lines {
        println!("{}", value);
    }
}

5
投票

迭代器通常不能迭代两次,因为迭代可能会产生成本。在

str::lines
的情况下,每次迭代都需要找到下一个行尾,这意味着扫描字符串,这会产生一些成本。 您可能会说迭代器可以保存这些位置以供以后重用,但存储它们的成本会更大。

有些

Iterator
的迭代成本甚至更高,所以你真的不想重复两次。

许多迭代器可以轻松地重新创建(此处再次调用

str::lines
)或被
clone
d。无论您以哪种方式重新创建迭代器,这两个迭代器通常都是完全独立的,因此迭代意味着您将付出两次代价。

在您的具体情况下,只需将字符串迭代两次可能就可以了,因为适合内存的字符串不应该太长,以至于仅仅计算行数将是一项非常昂贵的操作。如果您认为是这种情况,请首先对其进行基准测试,然后编写自己的算法,因为

Lines::count
可能没有得到尽可能多的优化,因为
Lines
的主要目标是迭代行。

© www.soinside.com 2019 - 2024. All rights reserved.