【问题标题】:Scala: scanLeft one item behind when reading from stdinScala:从标准输入读取时扫描落后一项
【发布时间】:2018-08-31 05:57:35
【问题描述】:

如果我用scanLeft 处理来自stdin 的输入,则结果输出总是比我的最后一个输入晚一行:

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

结果(我的手动输入前面是>):

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

我想要的合理输出是:

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

如您所见,第一个输入行之后的输出应该已经包含了第一个输入行的字符串。

我已经尝试过使用.scanLeft(...).drop(1).foreach(...),但这会导致以下结果:

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

如何正确省略纯种子以获得所需的结果?

[更新] 目前,我对 Andrey Tyukin 的巧妙解决方法感到满意。非常感谢您的建议。

当然,如果有任何替代 scanLeft 的替代方案不会将种子作为第一项发送到以下迭代链中,我会更喜欢该解决方案。

[更新]

用户 jwvh 了解我的目标并为此提供了出色的解决方案。为了完善他们的建议,我寻求一种在将行发送到累积回调之前对其进行预处理的方法。因此,readLine 命令不应在累积回调中调用,而应在我可以预先添加的不同链链接中调用。

【问题讨论】:

    标签: scala iterator


    【解决方案1】:

    编辑摘要:添加了map 以证明getLines 返回的行的预处理同样简单。


    您可以将println 移动到scanLeft 本身的主体中,以强制立即执行而没有延迟:

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

    但是,这似乎与更短且更直观的foldLeft 的行为完全相同:

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

    交互示例:

    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
    

    编辑

    您当然可以添加一个map-step 来预处理这些行:

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

    示例交互(输入小写,打印大写):

    > foo
    START: FOO
    > bar
    START: FOO BAR
    > baz
    START: FOO BAR BAZ
    

    【讨论】:

    • 需要 .forEach{_ => } 吗?
    • @JordanCutler 否则它不会做任何事情,因为它很懒......它只是强制执行。也许有一些更适合的“强制”方法,我不确定。
    • 我试过了,你是对的。它只是在 .forEach{_ => } 不存在时结束程序,但为什么呢?什么是惰性的,为什么它只在有 forEach 时才尝试读取我的标准输入?
    • @JordanCutler “懒惰”的意思是:仅在需要时执行。如果根本不需要它,则永远不会执行。阅读列表和流之间的实际区别。也许你可以永远学习一个haskell,众所周知,这可以相当有效地消除这些问题,即使你实际上不经常使用它......;)
    • @JordanCutler 并非无法访问。在流之后添加println,启动它,进行一些输入,按Ctrl+D,它将打印println 中的内容,然后才退出(我只能说linux/bash)。我不能声称我发现整个惰性执行故事完全 100% 直观,但我不确定你到底不明白什么。我的回答有问题吗,还是应该是一个单独的问题?
    【解决方案2】:

    您可以使用Stream.iterate() 代替scanLeft()StdIn.readLine 代替stdin.getLines 获得非常相似的结果。

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

    由于 Stream 的评估是惰性的,您需要一些方法来实现它。

    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
    

    如果需要,您实际上不必放弃 getLines 迭代器。

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

    不确定这是否针对您的 cmets。很多取决于可能的错误可能来自哪里以及如何确定。

    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
    }
    

    我假设.update() 修改了源文档并且不返回任何内容(返回Unit)。这是update() 方法的常用签名。

    其中大部分可以在调用链中完成(_.method1.method2. 等),但有时这只会让事情变得更加复杂。

    不返回感兴趣值的方法仍然可以通过使用称为kestrel pattern 的东西添加到调用链中。

    【讨论】:

    • 我非常喜欢您的解决方案,因为它允许我通过附加链发送 accu 结果。例如:def in = Stream.iterate(Document())(_.update(io.StdIn.readLine)); /* any other stuff in between */ for(doc &lt;- in) { println(doc) }。我可以在代码的两个独立部分进行对象操作和对象输出。我只缺少一件事(可能您也有一个很酷的解决方案):我想以相同的方式(单独的代码块)预处理读取的行,而不会污染我的更新回调。
    • 我想到了一些可能性,但我不确定我是否理解这个问题。例如:您说要处理readLine 文本。这是否意味着文档会使用处理后的结果进行更新,或者处理是否会产生副作用并且更新是使用未处理的文本完成的?听起来这值得提出一个新问题,与更新当前问题相比,它会得到更多的关注和关注。
    • 可能值得提出一个新问题。但我想更简洁地说明我的目标:您为我提供了一个解决方案,允许我对文档更新的输出进行后处理。现在我正在为输入寻找类似的解决方案。回到您的问题:这意味着文档已使用处理后的结果进行更新。结果应该是一系列“映射”函数,其作用如下:line -&gt; (a) prepare(line) -&gt; (b) use the line to update the document -&gt; (c) do something with the resulting document
    • 当然也可以使用line -&gt; (a) -&gt; (a) -&gt; (b) -&gt; (c) -&gt; (c) -&gt; (c) (需要多少链节)。具体示例:line -&gt; (a) trim(line) -&gt; (a) filter comment lines -&gt; (b) update the document -&gt; (c) -&gt; save the resulting document object to the undo history -&gt; (c) print any errors -&gt; (c) print statistics。我希望能够根据需要灵活地插入尽可能多的链节。因此我需要更新回调来只做更新,而不是其他的(比如从标准输入中读取一行,这应该总是发生在链的开头)。
    • 答案已更新。仍应考虑提出一个新的、更清晰的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-30
    • 2012-02-17
    相关资源
    最近更新 更多