私のHaskellはそれほど新鮮ではないので、私はScalaで答えます。そして、人々はこれが一般的な関数型プログラミングアルゴリズムの質問であると信じるでしょう。すぐに転送できるデータ構造と概念に固執します。
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
}
入力例のように、サイズが1000程度の範囲のREPLでは、ほぼ瞬時に答えが返されます。