Risponderò in Scala, perché il mio Haskell non è così nuovo, e quindi la gente crederà che questa sia una domanda generale di algoritmo di programmazione funzionale. Mi attenerò alle strutture e ai concetti di dati che sono facilmente trasferibili.
Possiamo iniziare con una funzione che genera una sequenza di collatz, che è relativamente semplice, tranne per il fatto che è necessario passare il risultato come argomento per renderlo ricorsivo in coda:
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)
}
}
Questo in realtà mette la sequenza in ordine inverso, ma questo è perfetto per il nostro prossimo passo, che è quello di memorizzare le lunghezze in una mappa:
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)))
}
Lo chiameresti con la risposta del primo passaggio, la lunghezza iniziale e una mappa vuota, come calculateLengths(collatz(22), 1, Map.empty))
. Questo è come si memorizza il risultato. Ora dobbiamo modificare collatz
per poter usare questo:
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)
}
}
Eliminiamo il controllo n == 1
perché possiamo solo inizializzare la mappa con 1 -> 1
, ma dobbiamo aggiungere 1
alle lunghezze che abbiamo inserito nella mappa all'interno di calculateLengths
. Ora restituisce anche la lunghezza memorizzata dove si è arrestata la ricorsione, che possiamo usare per inizializzare calculateLengths
, come:
val initialMap = Map(1 -> 1)
val (result, length) = collatz(22, initialMap)
val newMap = calculateLengths(result, lengths, initialMap)
Ora abbiamo implementazioni relativamente efficienti dei pezzi, dobbiamo trovare un modo per alimentare i risultati del calcolo precedente nell'input del prossimo calcolo. Questo è chiamato fold
, e assomiglia a:
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)
Ora per trovare la risposta effettiva, dobbiamo solo filtrare le chiavi nella mappa tra l'intervallo specificato e trovare il valore massimo, dando un risultato finale di:
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
}
Nel mio REPL per intervalli di dimensioni pari a circa 1000, come l'input di esempio, la risposta ritorna praticamente istantaneamente.