2

I need to create two iterators for stdin. However, I cannot figure out how to implement this.

Here is a simple example:

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

fn main() {
    let reader1 = BufReader::new(io::stdin()).lines();
    let reader2 = BufReader::new(io::stdin()).lines();

    for line in reader1 {
        println!("reader1: {:?}", line);
    }

    for line in reader2 {
        println!("reader2: {:?}", line);
    }
}

output:

$ printf '1\n2\n3\n4\n' | cargo run
reader1: Ok("1")
reader1: Ok("2")
reader1: Ok("3")
reader1: Ok("4")

The iterator reader2 never gets implemented.

The full example of what I am trying to accomplish is a little more complex...

use itertools::multipeek;
use std::io::{self, BufRead, BufReader, Result};

fn main() {
    let reader = BufReader::new(io::stdin()).lines();
    let mut mp = multipeek(BufReader::new(io::stdin()).lines());

    for line in reader {
        mp.next();
        match line {
            Ok(l) => {
                println!("line: {}", l);
                println!("peek: {:?}", mp.peek());
                println!("peek: {:?}", mp.peek());
            }
            Err(e) => println!("error parsing line: {:?}", e),
        }
    }
}

output:

$ printf '1\n2\n3\n4\n' | cargo run
line: 1
peek: None
peek: None
line: 2
peek: None
peek: None
line: 3
peek: None
peek: None
line: 4
peek: None
peek: None

from what I can tell though, it has something to do with using stdin because it works fine when reading from a file.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user5359531
  • 3,217
  • 6
  • 30
  • 55
  • See also [How to use the same iterator twice, once for counting and once for iteration?](https://stackoverflow.com/q/48037128/155423); [Using the same iterator multiple times in Rust](https://stackoverflow.com/q/23969191/155423). – Shepmaster Aug 14 '19 at 01:52
  • well that the very first design of stdin. You should have understand it since you claim to have do this since years. data must be stock somewhere there is no magic bullet. – Stargateur Aug 14 '19 at 09:11

3 Answers3

3

I am not familiar with Rust enough to offer a concrete solution, but you are failing because on lines 6-8 you process stdin (into lines), then on lines 10-12 you try to process it again. But there is nothing left to process a second time; you processed it already.

Remember, reader1 and reader2 are readers, not buffers. They do not duplicate the input stream.

If you wish to review any part of prior input, you must read it into some kind of storage object. Then you can create a BufReader or otherwise prepare access to that data.

From what you appear to be doing, an easy way would be simply to create a single BufReader object to populate an array of strings with the input. Then just iterate over that array as many times as you want.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
  • "They do not duplicate the input stream" then how do you duplicate the input stream? I have been Googling all day and cannot find any mention of ways to do this. Also I already have buffer structs implemented to cache prior results, but I need a way to look ahead in the iteration the way that `itertools::multipeek` allows. I do not want to read all of the input stream into memory, I want to only hold `n` lines at a time, where `n` is the current line + previous/next input lines. – user5359531 Aug 14 '19 at 01:33
  • 2
    @user5359531 “then how do you duplicate the input stream”; “I do not want to read all of the input stream into memory” — Then you are stuck. If you want to see a stream multiple times, you _**must**_ have a copy of that stream somewhere. – Dúthomhas Aug 14 '19 at 03:16
1

Alternative solution to the second problem that avoids duplicating the stdin iterator and allows for look-ahead with itertools::multipeek

use std::io::{self, BufRead, BufReader, Result};
use itertools::multipeek;

fn main(){
    let num_peeks = 2;
    let mut mp = multipeek(BufReader::new(io::stdin()).lines());
    loop {
        let a = mp.next();
        match a {
            Some(l) => {
                println!("line: {:?}", l);

                for _ in 0..num_peeks {
                    println!("peek: {:?}", mp.peek());
                }
            }
            None => break,
        }
    }
}

output:

$ printf '1\n2\n3\n4\n' | cargo run
line: Ok("1")
peek: Some(Ok("2"))
peek: Some(Ok("3"))
line: Ok("2")
peek: Some(Ok("3"))
peek: Some(Ok("4"))
line: Ok("3")
peek: Some(Ok("4"))
peek: None
line: Ok("4")
peek: None
peek: None
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user5359531
  • 3,217
  • 6
  • 30
  • 55
1

You can't iterate over stdin twice, because you can read it once only. If you want to do something similar, you need to buffer the data in your app yourself.

In your question, you duplicate not only the stream, but also create two buffered readers. This is important, because the buffered reader will likely read more data than required each time. For example, to get lines of text, it won't read byte-after-byte looking for a newline because that would be slow. Most likely it will read a multiple-of-page-size blocks into a preallocated space instead. Then your lines will come from there without any stream reads until it runs out of lines to return.

Your solution works because you have only one backing buffer, so everything works as expected. In the question though, after the first execution of for line in reader you don't have anything in your stream anymore - it's all been consumed. So mp.next() / mp.peek() won't read anything.

If you want to confirm this, try with input of multiple KB - you'll see mp.peek return values from the middle of your input.

viraptor
  • 33,322
  • 10
  • 107
  • 191
  • This doesn't appear to be an answer to the question: *How to iterate over stdin twice?* – Shepmaster Aug 14 '19 at 02:21
  • @Shepmaster The author posted a better solution themselves. The actual answer is "you can't do that" (adding to the answer), but that's not very helpful. – viraptor Aug 14 '19 at 04:00