Как эта замещенная таблица DP слишком медленная для SPOJ?
SPOILERS: Я работаю над http://www.spoj.pl/problems/KNAPSACK/, поэтому не заглядывайте, если вы не хотите, чтобы возможное решение испортилось для вас.
Шаблон:
import Data.Sequence (index, fromList)
import Data.MemoCombinators (memo2, integral)
main = interact knapsackStr
knapsackStr :: String -> String
knapsackStr str = show $ knapsack items capacity numItems
where [capacity, numItems] = map read . words $ head ls
ls = lines str
items = map (makeItem . words) $ take numItems $ tail ls
Некоторые типы и помощники для установки сцены:
type Item = (Weight, Value)
type Weight = Int
type Value = Int
weight :: Item -> Weight
weight = fst
value :: Item -> Value
value = snd
makeItem :: [String] -> Item
makeItem [w, v] = (read w, read v)
И основная функция:
knapsack :: [Item] -> Weight -> Int -> Value
knapsack itemsList = go
where go = memo2 integral integral knapsack'
items = fromList $ (0,0):itemsList
knapsack' 0 _ = 0
knapsack' _ 0 = 0
knapsack' w i | wi > w = exclude
| otherwise = max exclude include
where wi = weight item
vi = value item
item = items `index` i
exclude = go w (i-1)
include = go (w-wi) (i-1) + vi
И этот код работает; Я попытался подключить тестовый пример SPOJ, и он дает правильный результат. Но когда я отправляю это решение на SPOJ (вместо импорта MemoCombinators от Luke Palmer, я просто копирую и вставляю необходимые части в отправленный источник), он превышает срок. =/
Я не понимаю, почему; Я спросил ранее об эффективном способе выполнения рюкзака 0-1, и я уверен, что это примерно так же быстро, как и получается: memoized функция, которая будет только рекурсивно вычислять подзаголовки, которые ему абсолютно необходимы, чтобы получить правильный результат. Я каким-то образом испортил мемуары? Есть ли медленный момент в этом коде, который мне не хватает? SPOJ просто предвзято относится к Haskell?
Я даже положил {-# OPTIONS_GHC -O2 #-}
в начало представления, но, увы, это не помогло. Я пробовал аналогичное решение, которое использует 2D-массив Sequence
s, но также отклонялся как слишком медленный.
Ответы
Ответ 1
Есть одна серьезная проблема, которая действительно замедляет это. Это тоже полиморфно. Типоспецифические версии функций могут быть намного быстрее, чем полиморфные разновидности, и по какой-либо причине GHC не встраивает этот код в точку, где он может определять точные типы использования. Когда я изменяю определение integral
на:
integral :: Memo Int
integral = wrap id id bits
Я получаю примерно 5-кратное ускорение; Я думаю, что это достаточно быстро, чтобы быть принятым на SPOJ.
Это все еще значительно медленнее, чем решение gorlum0. Я подозреваю, причина в том, что он использует массивы, и вы используете пользовательский тип trie. Использование trie займет гораздо больше памяти, а также сделает поиск медленнее из-за дополнительных указаний, промахов кэш-памяти и т.д. Возможно, вы сможете многое разделить, если вы ограничиваете и удаляете поля в IntMap, но я не уверен это возможно. Попытка скрыть поля в BitTrie
создает для меня ошибки во время выполнения.
Pure haskell memoizing code может быть хорошим, но я не думаю, что это так же быстро, как делать небезопасные вещи (по крайней мере, под капотом). Вы можете применить метод метод Леннарта Аугссона, чтобы узнать, лучше ли это в воспоминаниях.
Ответ 2
Единственное, что замедляет работу Haskell - это IO, тип String в Haskell дает поддержку UTF8, которая нам не нужна для SPOJ. ByteStrings быстро растут, поэтому вы можете захотеть использовать их вместо них.