Possiamo farlo in modo molto efficiente creando una struttura che possiamo indicizzare in un tempo sub-lineare.
Ma prima,
{-# LANGUAGE BangPatterns #-}
import Data.Function (fix)
Definiamolo f
, ma utilizziamolo "ricorsione aperta" anziché chiamarsi direttamente.
f :: (Int -> Int) -> Int -> Int
f mf 0 = 0
f mf n = max n $ mf (n `div` 2) +
mf (n `div` 3) +
mf (n `div` 4)
È possibile ottenere un nonemo f
utilizzandofix f
Questo ti consentirà di testare f
ciò che intendi per piccoli valori f
chiamando, ad esempio:fix f 123 = 144
Potremmo memorizzarlo definendo:
f_list :: [Int]
f_list = map (f faster_f) [0..]
faster_f :: Int -> Int
faster_f n = f_list !! n
Ciò si comporta passabilmente bene e sostituisce ciò che avrebbe impiegato O (n ^ 3) tempo con qualcosa che memorizza i risultati intermedi.
Ma ci vuole ancora tempo lineare solo per indicizzare per trovare la risposta memorizzata mf
. Ciò significa che risulta come:
*Main Data.List> faster_f 123801
248604
sono tollerabili, ma il risultato non scala molto meglio di così. Possiamo fare di meglio!
Innanzitutto, definiamo un albero infinito:
data Tree a = Tree (Tree a) a (Tree a)
instance Functor Tree where
fmap f (Tree l m r) = Tree (fmap f l) (f m) (fmap f r)
E poi definiremo un modo per indicizzarlo, così possiamo trovare un nodo con indice n
nel tempo O (log n) :
index :: Tree a -> Int -> a
index (Tree _ m _) 0 = m
index (Tree l _ r) n = case (n - 1) `divMod` 2 of
(q,0) -> index l q
(q,1) -> index r q
... e potremmo trovare un albero pieno di numeri naturali per essere conveniente, quindi non dobbiamo armeggiare con quegli indici:
nats :: Tree Int
nats = go 0 1
where
go !n !s = Tree (go l s') n (go r s')
where
l = n + s
r = l + s
s' = s * 2
Dal momento che possiamo indicizzare, puoi semplicemente convertire un albero in un elenco:
toList :: Tree a -> [a]
toList as = map (index as) [0..]
Puoi controllare il lavoro finora verificando che toList nats
ti dia[0..]
Adesso,
f_tree :: Tree Int
f_tree = fmap (f fastest_f) nats
fastest_f :: Int -> Int
fastest_f = index f_tree
funziona proprio come con l'elenco sopra, ma invece di impiegare un tempo lineare per trovare ciascun nodo, può inseguirlo nel tempo logaritmico.
Il risultato è notevolmente più veloce:
*Main> fastest_f 12380192300
67652175206
*Main> fastest_f 12793129379123
120695231674999
In effetti è molto più veloce che puoi passare e sostituire Int
con Integer
sopra e ottenere risposte ridicolmente grandi quasi istantaneamente
*Main> fastest_f' 1230891823091823018203123
93721573993600178112200489
*Main> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358