scala – 在Stream算法上折叠的懒惰解决方案(从结果中获取K个元素应仅消耗来自输入的Q元素)

一些同事和我提出了一个有趣的问题并且正在辩论,似乎无法将我们的头脑包裹在一个懒惰的解决方案中.有吗?

这是在尝试学习函数式编程时出现的,我们想到了在惰性解决方案方面对简单问题的扩展

我将首先介绍非延迟问题(我们发现了一个简单的折叠flatMap解决方案)

Let’s say we have a word. W

We want to generate all subsets of the word, such that:

For “abcd” the result would be :
“”, “a”, “b”, “ab”, “c”, “ac”, “bc”, “abc”, “d”, “ad”, “bd”, “abd”, “cd”, “acd”, “bcd”, “abcd”

基本上为char填充char并使用当前char来组合结果,
总是加倍结果.

问题:

Can we find a lazy solution to this problem ?

Given:

  • the input as a Stream of Char

  • expected result a Stream of Strings

Can we consume the input stream lazily with just enough data to produce only
the amount of results we need

以下是我在Scala中的解决方案:

import org.scalatest.{FreeSpec, Matchers}

class Problem extends FreeSpec with Matchers {

  private def solution(word: Stream[Char]) = foldr(compose, Stream(""))(word)

  def compose(letter: Char, results: => Stream[String]): Stream[String] = {
    results append results.map(word => word + letter)
  }

  def foldr[A, B](combine: (A, =>B) => B, base: B)(xs: Stream[A]): B =
    if (xs.isEmpty) base
    else
      combine(xs.head, foldr(combine, base)(xs.tail))

  "Problem" - {

    "Taking 5 elements from the result should evaluate only 3 elements from the initial stream" in {
      solution(
        Stream('a', 'b', 'c', 'd', 'e', 'f').map(
          x => {
            println(s"Applying map on element: '$x'")
            x
          }
        )
      ).take(5).toList shouldBe List("", "f", "e", "fe", "d")
    }
  }

}

我使用了这个blogpost的foldr实现,因为我知道Scala流不是一个懒惰的foldRight?

这个解决方案的问题是放在那里进行调试的地图表明解决方案不是懒惰的

Applying map on element: 'a'
Applying map on element: 'b'
Applying map on element: 'c'
Applying map on element: 'd'
Applying map on element: 'e'
Applying map on element: 'f'

因为它使用Stream中的所有元素

我试过的另一个解决方案就是这个:

import org.scalatest.{FreeSpec, Matchers}

class Problem extends FreeSpec with Matchers {

  private def solution(word: Stream[Char]) = word.foldRight(Stream("")) (add)

  def add(letter: Char, results: Stream[String]): Stream[String] = results.flatMap(result =>  {
    println(s"Composing result '$result' with letter: '$letter'")
    Stream(result, letter + result)
  })

  "Problem" - {

    "Taking 5 elements from the result should evaluate only 3 elements from the initial stream" in {
      solution(
        Stream('a', 'b', 'c', 'd', 'e', 'f').map(
          x => {
            println(s"Applying map on element: '$x'")
            x
          }
        )
      ).take(5).toList shouldBe List("", "a", "b", "ab", "c")
    }
  }
}

哪个产生:

Applying map on element: 'a'
Applying map on element: 'b'
Applying map on element: 'c'
Applying map on element: 'd'
Applying map on element: 'e'
Applying map on element: 'f'
Composing result '' with letter: 'f'
Composing result '' with letter: 'e'
Composing result '' with letter: 'd'
Composing result '' with letter: 'c'
Composing result '' with letter: 'b'
Composing result '' with letter: 'a'
Composing result 'b' with letter: 'a'
Composing result 'c' with letter: 'b'
Composing result 'c' with letter: 'a'

我不知道我是否朝着正确的方向前进.任何形式的帮助将不胜感激.谢谢!

最终解决方案基于The Archetypal Paul答案

  private def solution(word: Stream[Char]) =
    word.scanLeft((Stream(""), Stream(""))) ((acc, l)=> {
      val r = acc._2.map(_ + l)
      (r, acc._2 append r)
    }).flatMap(_._1)

最佳答案 干得好:

val zs= Stream('a','b','c','d')
zs.map( x => {println(s"Applying map on element: '$x'"); x})
  .scanLeft("")((a, b) => a :+ b)
  .flatMap(_.permutations)
  .take(4).toList
  //> Applying map on element: 'a'
  //| Applying map on element: 'b'
  //| Applying map on element: 'c'
  //| res1: List[String] = List("", a, ab, ba)

它比它需要的更贪婪(take(5)评估d)但它是懒惰的.

点赞