我将在Scala中回答,因为我的Haskell不那么新鲜,因此人们会相信这是一个通用的函数编程算法问题。我将坚持易于转移的数据结构和概念。
我们可以从生成collatz序列的函数开始,该函数相对简单,除了需要将结果作为参数传递以使其尾部递归:
def collatz(n: Int, result: List[Int] = List()): List[Int] = {
if (n == 1) {
1 :: result
} else if ((n & 1) == 1) {
collatz(3 * n + 1, n :: result)
} else {
collatz(n / 2, n :: result)
}
}
这实际上将序列置于相反的顺序,但这对于下一步(将长度存储在地图中)非常理想:
def calculateLengths(sequence: List[Int], length: Int,
lengths: Map[Int, Int]): Map[Int, Int] = sequence match {
case Nil => lengths
case x :: xs => calculateLengths(xs, length + 1, lengths + ((x, length)))
}
您可以使用第一步的答案,初始长度和一个空的映射(例如)来调用它calculateLengths(collatz(22), 1, Map.empty))
。这就是您记住结果的方式。现在我们需要进行修改collatz
才能使用此功能:
def collatz(n: Int, lengths: Map[Int, Int], result: List[Int] = List()): (List[Int], Int) = {
if (lengths contains n) {
(result, lengths(n))
} else if ((n & 1) == 1) {
collatz(3 * n + 1, lengths, n :: result)
} else {
collatz(n / 2, lengths, n :: result)
}
}
我们省去了n == 1
检查,因为我们可以用初始化地图1 -> 1
,但是我们需要增加1
放入地图内部的长度calculateLengths
。现在,它还会返回停止递归的记忆长度,我们可以使用它来初始化calculateLengths
,例如:
val initialMap = Map(1 -> 1)
val (result, length) = collatz(22, initialMap)
val newMap = calculateLengths(result, lengths, initialMap)
现在我们已经有了相对高效的实现,我们需要找到一种方法将前一个计算的结果输入到下一个计算的输入中。这称为fold
,看起来像:
def iteration(lengths: Map[Int, Int], n: Int): Map[Int, Int] = {
val (result, length) = collatz(n, lengths)
calculateLengths(result, length, lengths)
}
val lengths = (1 to 10).foldLeft(Map(1 -> 1))(iteration)
现在要找到实际的答案,我们只需要过滤给定范围之间的映射中的键,并找到最大值即可得到最终结果:
def answer(start: Int, finish: Int): Int = {
val lengths = (start to finish).foldLeft(Map(1 -> 1))(iteration)
lengths.filterKeys(x => x >= start && x <= finish).values.max
}
在我的REPL中,范围为1000左右,就像示例输入一样,答案几乎立即返回。