Nous pouvons le faire très efficacement en créant une structure que nous pouvons indexer en temps sub-linéaire.
Mais d'abord,
{-# LANGUAGE BangPatterns #-}
import Data.Function (fix)
Définissons f
mais faites-lui utiliser la "récursion ouverte" plutôt que de l'appeler directement.
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)
Vous pouvez obtenir un non-mémorisé f
en utilisant fix f
Cela vous permettra de tester que f
fait ce que vous voulez dire pour de petites valeurs de f
en appelant, par exemple : fix f 123 = 144
Nous pourrions mémoriser ceci en définissant :
f_list :: [Int]
f_list = map (f faster_f) [0..]
faster_f :: Int -> Int
faster_f n = f_list !! n
Cela fonctionne passablement bien, et remplace ce qui allait prendre O(n^3) temps avec quelque chose qui mémorise les résultats intermédiaires.
Mais cela prend toujours du temps linéaire juste pour indexer pour trouver la réponse mémorisée pour mf
. Cela signifie que des résultats comme :
*Main Data.List> faster_f 123801
248604
sont tolérables, mais le résultat ne s'étend pas beaucoup mieux que cela. Nous pouvons faire mieux !
Tout d'abord, définissons un arbre infini :
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)
Puis nous définirons un moyen de l'indexer, afin de trouver un nœud avec l'indice n
en O(log n) à la place :
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
... et nous pouvons trouver qu'un arbre rempli de nombres naturels est pratique pour ne pas avoir à manipuler ces indices :
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
Puisque nous pouvons indexer, vous pouvez simplement convertir un arbre en une liste :
toList :: Tree a -> [a]
toList as = map (index as) [0..]
Vous pouvez vérifier le travail effectué jusqu'à présent en vérifiant que toList nats
vous donne [0..]
Maintenant,
f_tree :: Tree Int
f_tree = fmap (f fastest_f) nats
fastest_f :: Int -> Int
fastest_f = index f_tree
fonctionne de la même manière que la liste ci-dessus, mais au lieu de prendre un temps linéaire pour trouver chaque nœud, on peut le poursuivre en temps logarithmique.
Le résultat est considérablement plus rapide :
*Main> fastest_f 12380192300
67652175206
*Main> fastest_f 12793129379123
120695231674999
En fait, c'est tellement plus rapide que vous pouvez passer et remplacer Int
con Integer
ci-dessus et obtenir des réponses ridiculement grandes presque instantanément
*Main> fastest_f' 1230891823091823018203123
93721573993600178112200489
*Main> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358
Pour une bibliothèque prête à l'emploi qui implémente la mémorisation basée sur l'arbre, utilisez MemoTrie :
$ stack repl --package MemoTrie
Prelude> import Data.MemoTrie
Prelude Data.MemoTrie> :set -XLambdaCase
Prelude Data.MemoTrie> :{
Prelude Data.MemoTrie| fastest_f' :: Integer -> Integer
Prelude Data.MemoTrie| fastest_f' = memo $ \case
Prelude Data.MemoTrie| 0 -> 0
Prelude Data.MemoTrie| n -> max n (fastest_f'(n `div` 2) + fastest_f'(n `div` 3) + fastest_f'(n `div` 4))
Prelude Data.MemoTrie| :}
Prelude Data.MemoTrie> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358
111 votes
Seulement dans le sens où c'est un travail que je fais à la maison :-)