Динамическое программирование с Data.Map в Haskell?

Я пытаюсь реализовать простой алгоритм dp в Haskell (это для проблемы гипотезы Коллатца из Project Euler); вот эквивалент С++:

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')

(Я думаю, что здесь я просто переопределяю монаду состояния, но пока не обращайте на это внимания.) Код, вызывающийsolve, пытается найти наибольшее значение, которое он может дать для параметра, не превышающего 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 проходит через всю карту mem'', делая временную сложность алгоритма квадратичной, а не n*log(n).

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


person Kirill    schedule 10.04.2012    source источник
comment
Что вы подразумеваете под правильным?   -  person Robert Harvey    schedule 10.04.2012
comment
Я имею в виду ту, которая примерно эквивалентна версии (c++), которую я имею в виду, но которая не дает сбоя при переполнении стека.   -  person Kirill    schedule 10.04.2012


Ответы (1)


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

person Daniel Fischer    schedule 10.04.2012
comment
Аргх, мой плохой. Это в пять раз медленнее, чем c++ с Integer/Int64, но без сбоев. Не берите в голову. Спасибо. - person Kirill; 10.04.2012
comment
Ваш кортеж не выглядит достаточно строгим, хотя вы используете foldl', попробуйте добавить некоторые теги строгости !. На самом деле медленнее в пять раз звучит неплохо для Haskell. - person Jeff Burdges; 10.04.2012
comment
@JeffBurges С seq все достаточно строго. Однако maximum может быть слишком ленивым, если он используется для поиска самой длинной цепочки. Но никакая строгость не поможет против бесконечных циклов. - person Daniel Fischer; 10.04.2012