Wir können dies sehr effizient tun, indem wir eine Struktur erstellen, die wir in sublinearer Zeit indizieren können.
Aber zuerst,
{-# LANGUAGE BangPatterns #-}
import Data.Function (fix)
Lassen Sie uns definieren f
, aber lassen Sie es "offene Rekursion" verwenden, anstatt sich selbst direkt aufzurufen.
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)
Sie können eine unmemoized f
mit verwendenfix f
Auf diese Weise können Sie testen, f
was Sie für kleine Werte f
von tun, indem Sie beispielsweise Folgendes aufrufen:fix f 123 = 144
Wir könnten dies auswendig lernen, indem wir definieren:
f_list :: [Int]
f_list = map (f faster_f) [0..]
faster_f :: Int -> Int
faster_f n = f_list !! n
Das funktioniert passabel gut und ersetzt das, was O (n ^ 3) Zeit in Anspruch nehmen würde, durch etwas, das die Zwischenergebnisse auswendig lernt.
Es dauert jedoch immer noch eine lineare Zeit, nur um zu indizieren, um die gespeicherte Antwort für zu finden mf
. Dies bedeutet, dass Ergebnisse wie:
*Main Data.List> faster_f 123801
248604
sind erträglich, aber das Ergebnis skaliert nicht viel besser. Wir können es besser machen!
Definieren wir zunächst einen unendlichen Baum:
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)
Und dann definieren wir einen Weg, um darin zu indizieren, damit wir stattdessen einen Knoten mit Index n
in O (log n) Zeit finden können:
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
... und wir finden vielleicht einen Baum voller natürlicher Zahlen, damit wir nicht mit diesen Indizes herumspielen müssen:
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
Da wir indizieren können, können Sie einfach einen Baum in eine Liste konvertieren:
toList :: Tree a -> [a]
toList as = map (index as) [0..]
Sie können die bisherige Arbeit überprüfen, indem Sie überprüfen, ob toList nats
Sie diese erhalten[0..]
Jetzt,
f_tree :: Tree Int
f_tree = fmap (f fastest_f) nats
fastest_f :: Int -> Int
fastest_f = index f_tree
funktioniert genau wie in der obigen Liste, aber anstatt lineare Zeit zu benötigen, um jeden Knoten zu finden, kann er in logarithmischer Zeit verfolgt werden.
Das Ergebnis ist erheblich schneller:
*Main> fastest_f 12380192300
67652175206
*Main> fastest_f 12793129379123
120695231674999
In der Tat ist es so viel schneller , dass Sie durchmachen können und ersetzen Int
mit Integer
oben und fast augenblicklich lächerlich großen Antworten erhalten
*Main> fastest_f' 1230891823091823018203123
93721573993600178112200489
*Main> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358