Зачем использовать MemoCombinators, когда мы можем использовать лень? Хитрость в том, чтобы сделать что-то вроде
seqLength x = lengths !! x - 1
where lengths = map g [1..9999999]
g n | odd n = 1 + seqLength (3 * n + 1)
| otherwise = 1 + seqLength (n `div` 2)
, который должен работать запоминающимся способом. [Адаптировано из не хвостового рекурсивного решения @hammar]
Конечно, тогда seqLength равно O (n) для запомненного случая, так что он страдает меньшей производительностью. Однако это исправимо! Мы просто пользуемся тем фактом, что Data.Vector потоковый и имеет O (1) произвольный доступ. FromList и map будут выполнены одновременно (так как карта будет просто создавать thunks вместо фактических значений, потому что мы используем вектор в штучной упаковке). Мы также отказались от незарегистрированной версии, поскольку не можем запомнить все возможные значения.
import qualified Data.Vector as V
seqLength x | x < 10000000 = lengths V.! x - 1
| odd x = 1 + seqLength (3 * n + 1)
| otherwise = 1 + seqLength (n `div` 2)
where lengths = V.map g $ V.fromList [1..99999999]
g n | odd n = 1 + seqLength (3 * n + 1)
| otherwise = 1 + seqLength (n `div` 2)
Что должно быть сопоставимо или лучше с использованием MemoCombinators. У вас нет haskell на этом компьютере, но если вы хотите выяснить, что лучше, есть библиотека под названием Criterion , которая отлично подходит для такого рода вещей.
Я думаю, что использование Unboxed Vectors могло бы дать лучшую производительность. Это вызовет все сразу, когда вы оцените один предмет (я думаю ), но вам все равно это нужно. Следовательно, вы можете просто запустить foldl' max
, чтобы получить решение O(n)
, которое должно иметь меньше постоянных издержек.