Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: scanLeft one item behind when reading from stdin

Tags:

iterator

scala

If I process the input from stdin with scanLeft, the resulting output is always one line behind my last input:

io.Source.stdin
  .getLines
  .scanLeft("START:")((accu, line) => accu + " " + line)
  .foreach(println(_))

Results in (my manual inputs are preceded by >):

> first
START:
> second
START: first
> third
START: first second

The sensible output I want is:

> first
START: first
> second
START: first second
> third
START: first second third

As you can see, the output following the first input line should already contain the string of the first input line.

I already tried it using .scanLeft(...).drop(1).foreach(...), but this leads to the following result:

> first
> second
START: first
> third
START: first second

How do I correctly omit the pure seed to get the desired result?

[UPDATE] For the time being I am content with Andrey Tyukin's nifty workaround. Many thanks for suggesting it.

But of course, if there is any alternative to scanLeft that does not send the seed as first item into the following iteration chain, I will prefer that solution.

[UPDATE]

User jwvh understood my objective and provided an excellent solution to it. To round off their suggestion I seek a way of preprocessing the lines before sending them into the accumulation callback. Thus the readLine command should not be called in the accumulation callback but in a different chain link I can prepend.

like image 624
ideaboxer Avatar asked Mar 21 '18 22:03

ideaboxer


Video Answer


2 Answers

Edit Summary: Added a map to demonstrate that the preprocessing of lines returned by getLines is just as trivial.


You could move println into the body of scanLeft itself, to force immediate execution without the lag:

io.Source.stdin
  .getLines
  .scanLeft("START:") {
    (accu, line) => accu + " " + line
    val res = accu + " " + line
    println(res)
    res
  }.foreach{_ => }

However, this seems to behave exactly the same as a shorter and more intuitive foldLeft:

io.Source.stdin
  .getLines
  .foldLeft("START:") {
    (accu, line) => accu + " " + line
    val res = accu + " " + line
    println(res)
    res
  }

Example interaction:

first
START: first
second
START: first second
third
START: first second third
fourth
START: first second third fourth
fifth
START: first second third fourth fifth
sixth
START: first second third fourth fifth sixth
seventh
START: first second third fourth fifth sixth seventh
end
START: first second third fourth fifth sixth seventh end

EDIT

You can of course add a map-step to preprocess the lines:

io.Source.stdin
  .getLines
  .map(_.toUpperCase)
  .foldLeft("START:") {
    (accu, line) => accu + " " + line
    val res = accu + " " + line
    println(res)
    res
  }

Example interaction (typed lowercase, printed uppercase):

> foo
START: FOO
> bar
START: FOO BAR
> baz
START: FOO BAR BAZ
like image 96
Andrey Tyukin Avatar answered Sep 28 '22 08:09

Andrey Tyukin


You can get something pretty similar with Stream.iterate() in place of scanLeft() and StdIn.readLine in place of stdin.getLines.

def input = Stream.iterate("START:"){prev =>
  val next = s"$prev ${io.StdIn.readLine}"
  println(next)
  next
}

Since a Stream is evaluated lazily you'll need some means to materialize it.

val inStr = input.takeWhile(! _.contains("quit")).last
START: one                //after input "one"<return>
START: one two            //after input "two"<return>
START: one two brit       //after input "brit"<return>
START: one two brit quit  //after input "quit"<return>
//inStr: String = START: one two brit

You actually don't have to give up on the getLines iterator if that's a requirement.

def inItr = io.Source.stdin.getLines

def input = Stream.iterate("START:"){prev =>
  val next = s"$prev ${inItr.next}"
  println(next)
  next
}

Not sure if this addresses your comments or not. Lots depends on where possible errors might come from and how they are determined.

Stream.iterate(document()){ doc =>
  val line = io.StdIn.readLine  //blocks here
                     .trim
                     .filterNot(_.isControl)
                     //other String or Char manipulations
  doc.update(line)
  /* at this point you have both input line and updated document to play with */
  ... //handle error and logging requirements
  doc //for the next iteration
}

I've assumed that .update() modifies the source document and returns nothing (returns Unit). That's the usual signature for an update() method.

Much of this can be done in a call chain (_.method1.method2. etc.) but sometimes that just makes things more complicated.

Methods that don't return a value of interest can still be added to a call chain by using something called the kestrel pattern.

like image 29
jwvh Avatar answered Sep 28 '22 08:09

jwvh