简体   繁体   English

了解递归 scala countChange function 中的评估顺序

[英]Understanding evaluation order in recursive scala countChange function

I have a function that counts the number of ways to count change given an amount of money and a list of coins.我有一个 function,它计算给定金额和硬币清单的零钱计数方法的数量。 This function appears to pass basic unit tests but if I am being completely honest, I don't fully understand why it works.这个 function 似乎通过了基本的单元测试,但如果我完全诚实,我不完全理解它为什么有效。

  def countChange(money: Int, coins: List[Int]): Int = {

    def countChangeRec(money: Int, coins: List[Int]): Int = {
      if (money == 0) 1
      else if(money > 0 && coins.nonEmpty) {
        countChangeRec(money - coins.head, coins) +
          countChangeRec(money, coins.tail)
      }
      else 0
    }

    if(money == 0 || coins.isEmpty) 0
    else countChangeRec(money, coins)
  }

Taking a basic example with arguments money = 4 and coins = List(1, 2)以 arguments money = 4coins = List(1, 2)为例

I currently come up with the following evaluation order to return 2 (which is not correct).我目前提出以下评估顺序以返回 2(这是不正确的)。

  1. countChangeRec(4-1, List(1, 2)) + countChangeRec(4, List(2))
  2. countChangeRec(3-1, List(1, 2)) + countChangeRec(4-2, List(2))
  3. countChangeRec(2-1, List(1, 2)) + countChangeRec(2-2, List(2))
countChangeRec(1-1, List(1, 2)) // => 1 returns 1 since money = 0 
          + countChangeRec(0, List(2)) // => 1 since money = 0
  1. returns 2 // this is not the case since the functional actually returns 3.返回 2 // 情况并非如此,因为函数实际上返回 3。

You can expand the evaluation as follows (the order doesn't actually matter, this being a pure function) using the evaluation order is to expand the left-most function application and evaluate integer additions:您可以按如下方式扩展评估(顺序实际上并不重要,这是一个纯函数),使用评估顺序是扩展最左边的 function 应用程序并评估 integer 添加:

  • countChangeRec(4, List(1, 2))
  • countChangeRec(3, List(1, 2)) + countChangeRec(4, List(2))
  • countChangeRec(2, List(1, 2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • countChangeRec(1, List(1, 2)) + countChangeRec(2, List(2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • countChangeRec(0, List(1, 2)) + countChangeRec(1, List(2)) + countChangeRec(2, List(2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 1 + countChangeRec(1, List(2)) + countChangeRec(2, List(2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 1 + countChangeRec(-1, List(2)) + countChangeRec(2, List(2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 1 + 0 + countChangeRec(2, List(2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 1 + countChangeRec(2, List(2)) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 1 + countChangeRec(0, List(2)) + countChangeRec(2, Nil) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 1 + 1 + countChangeRec(2, Nil) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 2 + countChangeRec(2, Nil) + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 2 + 0 + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 2 + countChangeRec(3, List(2)) + countChangeRec(4, List(2))
  • 2 + countChangeRec(1, List(2)) + countChangeRec(3, Nil) + countChangeRec(4, List(2))
  • 2 + countChangeRec(-1, List(2)) + countChangeRec(1, Nil) + countChangeRec(3, Nil) + countChangeRec(4, List(2))
  • 2 + 0 + countChangeRec(1, Nil) + countChangeRec(3, Nil) + countChangeRec(4, List(2))
  • 2 + countChangeRec(1, Nil) + countChangeRec(3, Nil) + countChangeRec(4, List(2))
  • 2 + 0 + countChangeRec(3, Nil) + countChangeRec(4, List(2))
  • 2 + countChangeRec(3, Nil) + countChangeRec(4, List(2))
  • 2 + 0 + countChangeRec(4, List(2))
  • 2 + countChangeRec(4, List(2))
  • 2 + countChangeRec(2, List(2)) + countChangeRec(4, Nil)
  • 2 + countChangeRec(0, List(2)) + countChangeRec(2, Nil) + countChangeRec(4, Nil)
  • 2 + 1 + countChangeRec(2, Nil) + countChangeRec(4, Nil)
  • 3 + countChangeRec(2, Nil) + countChangeRec(4, Nil)
  • 3 + 0 + countChangeRec(4, Nil)
  • 3 + countChangeRec(4, Nil)
  • 3 + 0
  • 3

each application of a function uses, effectively, its own copy of the arguments it's been passed. function 的每个应用程序都有效地使用它自己的 arguments 副本,它已通过。

This evaluation order is very similar to Scala's.这个评估顺序与 Scala 的非常相似。 The only difference is that this effectively evaluates function arguments in parallel, while in Scala it would be something like:唯一的区别是这有效地评估了 function arguments 并行,而在 Scala 它将是这样的:

  • countChangeRec(4, List(1, 2))
  • countChangeRec(4-List(1, 2).head, List(1, 2)) + countChangeRec(4, List(1, 2).tail)
  • countChangeRec(4-1, List(1, 2)) + countChangeRec(4, List(1, 2).tail)
  • countChangeRec(3, List(1, 2)) + countChangeRec(4, List(1, 2).tail)

and so forth... so technically my evaluation strategy would correspond to等等......所以从技术上讲,我的评估策略将对应于

def countChangeRec(money: Int, coins: List[Int]): Int =
  if (money == 0) 1
  else if (money > 0 && coins.nonEmpty) {
    val minusHead = money - coins.head
    val tail = coins.tail
    countChangeRec(minusHead, coins) + countChangeRec(money, tail)
  } else 0

with the observation that since there's no dependency between minusHead and tail , we can evaluate them in parallel.观察到由于minusHeadtail之间没有依赖关系,我们可以并行评估它们。

One important thing to know is that if an expression has no side effects (eg mutating a var or mutable structure, performing I/O, going into an infinite loop, or throwing an exception (this covers the 5 broad categories of impurity in Scala)), any strategy for evaluation which is compatible with data dependencies gives the same result.要知道的一件重要的事情是,如果表达式没有副作用(例如,改变var或可变结构、执行 I/O、进入无限循环或抛出异常(这涵盖了 Scala 中的 5 大类杂质) ),任何与数据相关性兼容的评估策略都会给出相同的结果。 This includes parallelizing the evaluation (which includes offloading evaluation to a different computer), and caching results.这包括并行化评估(包括将评估卸载到不同的计算机)和缓存结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM