【问题标题】:How to use the same iterator twice, once for counting and once for iteration?如何使用相同的迭代器两次,一次用于计数,一次用于迭代?
【发布时间】:2018-06-10 18:02:41
【问题描述】:

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

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

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 方法,但内部游标似乎在计数后位于迭代器的末尾。是否必须通过再次调用log_content.lines() 来创建新的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);
    }
}

【问题讨论】:

    标签: iterator rust reset


    【解决方案1】:

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

    您可以通过使用by_ref 来阻止使用迭代器,但迭代器仍然被驱动完成(by_ref 实际上只是返回对迭代器的可变引用,并且Iterator 也为可变引用实现: impl<'a, I> Iterator for &'a mut I)。

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

    您可以简单地尝试分叉迭代器(如果它们没有副作用,它们通常会实现 Clone),尽管在这种情况下重新创建它同样好(大多数时候创建迭代器很便宜;真正的通常只有在您通过直接或间接调用next 来驾驶它时才能完成工作。

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

    【讨论】:

    • 感谢您的回答。我虽然想克隆迭代器,但我认为在这种情况下它相当于创建一个新迭代器。我知道迭代是有成本的(在forcount 中),但只有在我迭代时才会有成本,而且只有在需要时才进行迭代。但如果无法再次迭代,我将创建一个新的迭代器。
    【解决方案2】:

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

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

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

    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);
        }
    }
    

    【讨论】:

      【解决方案3】:

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

      有些Iterators 的迭代成本更高,所以你真的不想重复两次。

      许多迭代器可以很容易地重新创建(这里调用str::lines 第二次)或cloned。无论您以哪种方式重新创建迭代器,这两个迭代器通常是完全独立的,因此迭代意味着您将付出两次代价。

      在您的特定情况下,将字符串迭代两次可能很好,因为适合内存的字符串不应该太长,以至于仅计算行数将是一项非常昂贵的操作。如果您认为是这种情况,首先对其进行基准测试,其次,编写您自己的算法,因为Lines::count 可能没有尽可能优化,因为Lines 的主要目标是迭代行。

      【讨论】:

      • 感谢您的回答。我知道迭代器的迭代是有代价的,但正如我对 Stefan 所说的那样,我只在需要时才迭代,所以在这种情况下,我选择迭代。但我想这是为了阻止我们在不必要的时候通过迭代来浪费性能。因此,如果这是我应该采用的方式,我将创建一个非常新的迭代器。
      猜你喜欢
      • 2010-11-25
      • 2017-11-07
      • 2021-10-05
      • 2014-06-30
      • 2013-05-26
      • 2017-09-21
      • 2019-10-01
      • 1970-01-01
      相关资源
      最近更新 更多