【问题标题】:Scala Recursive For Comprehension Prepends Empty List Only Once, Why?Scala Recursive For Comprehension 只在空列表前面添加一次,为什么?
【发布时间】:2021-01-04 01:04:01
【问题描述】:

类似于这篇文章here,我正在研究“Scala 中的函数式编程”字谜课程。我无法弄清楚组合功能,但在其他地方找到了这个非常优雅的解决方案

def combinations(occurrences: Occurrences): List[Occurrences] =
  List() :: (for {
    (char, max) <- occurrences
    count <- 1 to max
    rest <- combinations(occurrences filter {case (c, _) => c > char})
  } yield List((char, count)) ++ rest)

我理解 for 理解是如何创建组合的,但我不明白为什么在每次递归调用期间空列表没有预先附加到每个内部列表中。这几乎就像编译器跳过了 prepend 语句,只执行了表达式的右侧。

例如输入combinations(List(('a', 2), ('b', 2)))返回预期的结果集:

res1: List[forcomp.Anagrams.Occurrences] = List(List(), List((a,1)), List((a,1), (b,1)), List((a,1), (b,2)), List((a,2)), List((a,2), (b,1)), List((a,2), (b,2)), List((b,1)), List((b,2)))

只有一个空列表。查看递归调用,我希望每个递归都有另一个 Empty 列表。有人能解释一下这个优雅的解决方案是如何工作的吗?

【问题讨论】:

  • 请注意,List((char, count)) ++ res 使整个事情变得非常低效。改为这样做(char, count) :: res
  • :: 导致类型错误 type mismatch; found : List[List[Equals with java.io.Serializable]] required: List[forcomp.Anagrams.Occurrences] (which expands to) List[List[(Char, Int)]] List() :: (for {
  • 不,它没有:scastie.scala-lang.org/BalmungSan/SwM22gLuRt2rmpz2h6EFDA/8 - 顺便说一句,combinations2 是我自己解决问题的方法。
  • 啊,我明白了,谢谢。

标签: scala recursion for-comprehension


【解决方案1】:

为了便于理解,这里没有任何东西会产生一个空列表。即使

combinations(occurrences filter {case (c, _) => c > char})

包含一个空列表并在 rest &lt;- ... 中返回它(它应该用于第一个元素),在 List((char, count)) ++ rest 中添加一个值,使其设计为非空。

因此,整个 for-comprehension 必须返回一个 List 的非空 Lists,其前面附加了一个空列表。

这基本上是通过归纳构建解决方案:

  • 如果您有一个空列表 - 返回一个空列表,因为它是此输入的有效解决方案
  • 如果你以(char, maxOccurrences) :: rest开头
    • 假设您对combinations(rest) 有一个有效的解决方案
    • 然后采用每个这样的解决方案并将(char, 1) 添加到rest 的每个元素中,
    • 然后采用每个这样的解决方案并将(char, 2) 添加到rest 的每个元素中,
    • ...
    • 然后采用每个这样的解决方案并将(char, maxOccurrences) 添加到rest 的每个元素
    • 然后将所有这些结果合并到一个解决方案中
    • 所有这些都是非空的,因为您总是预先添加了一些东西
    • 因此您缺少空集,因此您将其显式添加到所有其他解决方案组合中以创建(char, maxOccurrences) :: rest 的完整解决方案

因为你有一个有效的起点和一个有效的方法来从上一个创建下一步,你知道你总是可以创建一个有效的解决方案。

在理解中

  def combinations(occurrences: Occurrences): List[Occurrences] =
    List() :: (for {
      (char, max) <- occurrences
      count <- 1 to max
      rest <- combinations(occurrences filter {case (c, _) => c > char})
    } yield List((char, count)) ++ rest)

正在做同样的事情

def combinations(occurrences: Occurrences): List[Occurrences] =
  List() :: occurrences.flatMap { case (char, max) =>
    (1 to map).flatMap { count =>
      combinations(occurrences filter {case (c, _) => c > char}).map { rest =>
        (char, count) :: rest
      }
    }
  }

与

相同
def combinations(occurrences: Occurrences): List[Occurrences] =
  occurrences.map { case (char, max) =>
    (1 to map).map { count =>
      val newOccurence = (char, count)
      combinations(occurrences filter {case (c, _) => c > char}).map { rest =>
        newOccurence :: rest
      }
    }
  }.flatten.flatten.::(List())

您可以轻松地将其与上面的感应配方进行比较:

def combinations(occurrences: Occurrences): List[Occurrences] =
  occurrences.map { case (char, max) =>
    // for every character on the list of occurrences
    (1 to max).map { count =>
      // you construct (char, 1), (char, 2), ... (char, max)
      val newOccurence = (char, count)
      // and for each such occurrence
      combinations(occurrences filter {case (c, _) => c > char}).map { rest =>
        // you prepend it into every result from smaller subproblem
        newOccurence :: rest
      }
    }
  }
   // because you would have a List(List(List(List(...)))) here
   // and you need List(List(...)) you flatten it twice
  .flatten.flatten
  // and since you are missing empty result, you prepend it here
  .::(List())

您发布的解决方案以更紧凑的方式完成了完全相同的事情 - 而不是.map().flatten,而是由理解隐藏的.flatMaps。

【讨论】:

  • 当你写“即使rest包含一个空列表并在休息时返回它rest 并不总是包含空列表,因为前置是出现的第一步。每次我们添加一些东西休息时,我们不会包括那个空列表吗?
【解决方案2】:

我认为跟踪电话将帮助您更好地理解它。我将对步骤进行编号,以便在它们之间轻松地来回切换。

  1. combinations(List(('a', 2), ('b', 2))),我们先:

    (char, max) <- ('a', 2)
    count <- 1
    

    occurrences filter {case (c, _) =&gt; c &gt; char} 的结果将是List(('b', 2))。

  2. 因此我们现在计算combinations(List(('b', 2))):

    (char, max) <- ('b', 2)
    count <- 1
    

    现在,occurrences filter {case (c, _) =&gt; c &gt; char} 的结果将是 List()。

  3. 我们要计算combiantions(List()):

    for 理解将是List(),List() :: List() 的结果是List()。因此我们回到第 2 步:

  4. (第 2 步)我们有:

    (char, max) <- ('b', 2)
    count <- 1
    

    现在我们没有休息的元素了。所以第 2 步的结果将是List() :: List(),也就是List()。回到第 1 步。因此,我们将为该选项屈服 List((char, count)) ++ rest),即:List(('b', 1)) ++ List()),即List(('b', 1)),我们现在处于同一调用的下一次迭代中:

    (char, max) <- ('b', 2)
    count <- 2
    

    rest 将再次变为List(),我们现在将为该选项屈服List((char, count)) ++ rest),即:List(('b', 2)) ++ List()),即List(('b', 2))。现在我们要添加空列表,结果是:List(List(), List((b,1)), List((b,2)))。

  5. (第 1 步)我们有:

    (char, max) <- ('a', 2)
    count <- 1
    

    现在我们有了:

    rest <- List()
    

    所以我们产生List((char, count)) ++ rest 即:List(('a', 1)) ++ List() 即:List(('a', 1))

    现在我们继续:

    rest <- List((b,1))
    

    所以我们产生了List((char, count)) ++ rest,即:List(('a', 1)) ++ List((b,1)),即:List(('a', 1), ('b', 1))。

    现在我们继续:

    rest <- List((b,2))
    

    所以我们得到List((char, count)) ++ rest,即:List(('a', 1)) ++ List((b,2)),即:List(('a', 1), ('b', 2))。到目前为止的汇总结果是:

     List(List(('a', 1)), List(('a', 1), ('b', 1)), List(('a', 1), ('b', 2)))
    

现在max 将增加到 2,我们将在步骤 2-4 中进行相同的计算,这将产生,具有完全相同的逻辑:

List(List(('a', 2)), List(('a', 2), ('b', 1)), List(('a', 2), ('b', 2)))

现在(char, max) 将更改为('b', 2),这将导致它(在应用步骤 2-4 中的相同逻辑之后):

List(List(('b', 1)), List(('b', 2)))

当聚合在一起时,我们得到想要的输出。

真正有助于了解我刚才解释的内容是添加打印消息:

def combinations(occurrences: Occurrences): List[Occurrences] = {
  println("Starting with: " + occurrences)
  val result = List() :: (for {
    (char, max) <- occurrences
    count <- 1 to max
    rest <- combinations(occurrences filter {case (c, _) => c > char })
  } yield {
    val result = List((char, count)) ++ rest
    println("Occurrences are: " + occurrences + " Result is: " + result)
    result
  })
  println("Done with: " + occurrences + " results are: " + result)
  result
}

然后调用println("Done: " + combinations(List(('a', 2), ('b', 2)))) 的结果是:

Starting with: List((a,2), (b,2))
Starting with: List((b,2))
Starting with: List()
Done with: List() results are: List(List())
Occurrences are: List((b,2)) Result is: List((b,1))
Starting with: List()
Done with: List() results are: List(List())
Occurrences are: List((b,2)) Result is: List((b,2))
Done with: List((b,2)) results are: List(List(), List((b,1)), List((b,2)))
Occurrences are: List((a,2), (b,2)) Result is: List((a,1))
Occurrences are: List((a,2), (b,2)) Result is: List((a,1), (b,1))
Occurrences are: List((a,2), (b,2)) Result is: List((a,1), (b,2))
Starting with: List((b,2))
Starting with: List()
Done with: List() results are: List(List())
Occurrences are: List((b,2)) Result is: List((b,1))
Starting with: List()
Done with: List() results are: List(List())
Occurrences are: List((b,2)) Result is: List((b,2))
Done with: List((b,2)) results are: List(List(), List((b,1)), List((b,2)))
Occurrences are: List((a,2), (b,2)) Result is: List((a,2))
Occurrences are: List((a,2), (b,2)) Result is: List((a,2), (b,1))
Occurrences are: List((a,2), (b,2)) Result is: List((a,2), (b,2))
Starting with: List()
Done with: List() results are: List(List())
Occurrences are: List((a,2), (b,2)) Result is: List((b,1))
Starting with: List()
Done with: List() results are: List(List())
Occurrences are: List((a,2), (b,2)) Result is: List((b,2))
Done with: List((a,2), (b,2)) results are: List(List(), List((a,1)), List((a,1), (b,1)), List((a,1), (b,2)), List((a,2)), List((a,2), (b,1)), List((a,2), (b,2)), List((b,1)), List((b,2)))
Done: List(List(), List((a,1)), List((a,1), (b,1)), List((a,1), (b,2)), List((a,2)), List((a,2), (b,1)), List((a,2), (b,2)), List((b,1)), List((b,2)))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-21
    • 2014-07-04
    • 1970-01-01
    相关资源
    最近更新 更多