2012-04-10 2 views
5

Я пытаюсь реализовать простой алгоритм dp в Haskell (это для проблемы гипотезы Collatz из Project Euler); здесь является эквивалентом C++:Динамическое программирование с помощью Data.Map в Haskell?

map<int,int> a; 
int solve(int x) { 
    if (a.find(x) != a.end()) return a[x]; 
    return a[x] = 1 + /* recursive call */; 
} 

Так код, который я написал в Haskell не закончился выглядеть так:

solve :: (Memo, Int) -> (Memo, Int) 
solve (mem, x) = 
case Map.lookup x mem of 
    Just l -> (mem, l) 
    Nothing -> let (mem', l') = {- recursive call -} 
       mem'' = Map.insert x (1+l') mem' 
      in (mem'', 1+l') 

(я думаю, что я просто реализовав государственную монады здесь, но никогда виду, что на данный момент) код, который требует решения пытается найти наибольшее значение, которое он может дать для параметра в большинстве K = 1E6:.

foldl' 
(\(mem,ss) k -> 
    let (mem',x') = solve (mem, k) 
    in (mem', (x', k):ss)) 
(Map.singleton 1 1, [(1,1)]) [2..100000] 

код, как написано выше, не может с переполнением стека. Этого следует ожидать, я понимаю, потому что он создает действительно большой неоценимый тон. Поэтому я попытался использовать

x' `seq` (mem', (x',k):ss) 

внутри foldl ', и он вычисляет правильный ответ для K = 1e5. Но это не удается для K = 1e6 (переполнение стека за 12 секунд). Затем я попытался использовать

mem'' `seq` l' `seq` (mem'', 1+l') 

в последней строке решения, но это не имело значения (переполнение стека все еще). Тогда я попытался с помощью

mem'' `deepseq` l' `seq` (mem'', 1+l') 

Это крайне медленно, по-видимому, потому что deepseq прогулки по всей карте ает «», что делает временную сложность этого алгоритма квадратичной вместо п * журнал (п).

Каков правильный способ реализации этого? Я застрял, потому что не могу понять, как сделать все вычисления строгими, и я не совсем уверен, какая часть вычислений дает переполнение стека, но я подозреваю, что это карта. Я мог бы использовать, например, массивы, но я хочу понять, что я делаю неправильно здесь.

+0

Что вы подразумеваете под "правильным?" –

+1

Я имею в виду тот, который является примерно эквивалентом версии (C++), которую я имею в виду, но которая не прерывается при переполнении стека. – Kirill

ответ

6

Переполнение стека не исчезнет, ​​когда вы используете 32-разрядный целочисленный тип со знаком. Для некоторых начальных значений цепочка покидает 32-битный диапазон и вводит бесконечный цикл отрицательных чисел. Используйте Integer или Int64 или Word64.

+0

Арг, мой плохой. Это в пять раз медленнее, чем C++ с Integer/Int64, но не прерывается. Неважно. Благодарю. – Kirill

+0

Ваш кортеж не выглядит достаточно строгим, даже если вы используете 'foldl'', попробуйте добавить теги строгости!! На самом деле в 5 раз медленнее звучит довольно хорошо для Haskell. –

+1

@JeffBurdges С 'seq' это достаточно строго. 'maximum' может быть слишком ленивым, хотя, если это используется для поиска самой длинной цепи. Но никакая строгость не помогает против бесконечных циклов. –

Смежные вопросы