我也参加了 Coursera 的 Scala FP 课程,遇到了和你一样的问题。我也想出了相同的工作解决方案。了解为什么一个有效而另一个无效的关键在于函数的递归分解。首先,让我们看看您的第一个不终止的解决方案。
def union(that: TweetSet): TweetSet = (left union(right)) union(that) incl(elem)
让我们使用一个简单的示例树和一些任意树that:
val tree = NonEmpty(tweet1, NonEmpty(tweet2, Empty, Empty), NonEmpty(tweet3, Empty, Empty))
val that: TweetSet = ...
tree.union(that)
扩展到:
tree.left.union(tree.right)).union(that).incl(tree.elem)
进一步扩展为:
tree.left.left.union(tree.left.right).union(tree.right).incl(tree.left.elem).union(that).incl(tree.elem)
现在我们可以在 Empty TweetSets(tree.left.left 和 tree.left.right)上调用基本案例
tree.right.incl(tree.left.elem).union(that).incl(tree.elem)
到此为止,我们来看看第二种解决方案。
def union(that: TweetSet): TweetSet = left union(right union(that)) incl(elem)
tree.union(that)
扩展到:
tree.left.union(tree.right.union(that)).incl(tree.elem)
再次展开:
tree.left.union(tree.right.left.union(tree.right.right.union(that)).incl(tree.right.elem)).incl(tree.elem)
对 tree.right.left 和 tree.right.right 应用基本情况
tree.left.union(that.incl(tree.right.elem)).incl(tree.elem)
在每个步骤上执行相同数量的步骤后,您可以看到我们有非常不同的表达方式。
解决方案1 = tree.right.incl(tree.left.elem).union(that).incl(tree.elem)
解决方案2 = tree.left.union(that.incl(tree.right.elem)).incl(tree.elem)
在方案一中,可以看到incl调用发生在下一个union的左侧:
tree.right.incl(tree.left.elem).union(that).incl(tree.elem)
^^^^
在解决方案 2 中,incl 出现在下一个 union 的右侧。
tree.left.union(that.incl(tree.right.elem)).incl(tree.elem)
^^^^
所以我们可以看到解决方案 1 在联合之前构建了一个全新的树,其元素比上一次迭代少了一个。对于将要处理的树中的每个左分支,此过程将重复。一个 n^2 的效率。创建 n^2 新树时会发生内存分配错误。解决方案 2 使用现有树作为下一个联合的左侧,并从基本情况(n 的效率)返回新树。要与给定的基本情况建立有效的联合,您必须构建 union 表达式的右侧,因为构建左侧会导致工作量成倍增加。