2011-09-22 10 views
14

SPOILERS: Estoy trabajando en http://www.spoj.pl/problems/KNAPSACK/, así que no se asome si no quiere una posible solución para usted.¿Cómo es que esta tabla DP memorada es demasiado lenta para SPOJ?

El repetitivo:

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 

Algunos tipos y ayudantes para preparar el escenario:

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) 

Y la función primaria:

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 

Y funciona este código; Intenté conectar el caso de prueba de muestra SPOJ y produce el resultado correcto. Pero cuando envío esta solución a SPOJ (en lugar de importar los MemoCombinators de Luke Palmer, simplemente copio y pego las partes necesarias en la fuente enviada), excede el límite de tiempo. =/

No entiendo por qué; I asked earlier acerca de una forma eficiente de realizar la mochila 0-1, y estoy bastante convencido de que esto es lo más rápido posible: una función memorada que solo calculará recursivamente las subentradas que absolutamente necesita para producir la resultado correcto ¿Metí la memoria de alguna manera? ¿Hay algún punto lento en este código que me falta? ¿Es SPOJ simplemente parcial en contra de Haskell?

Incluso puse {-# OPTIONS_GHC -O2 #-} en la parte superior de la presentación, pero, por desgracia, no sirvió de nada. He intentado con una solución similar que utiliza una matriz 2D de Sequence s, pero también se rechazó por ser demasiado lenta.

+0

¿Has probado el perfilado? En general, no he encontrado que la memorización sea tan útil para la mayoría de las tareas. Además, usar una secuencia no parece ser de mucha ayuda aquí, ¿tal vez un 'Mapa' en su lugar? – ivanm

+0

¿Puede mostrarnos el código completo con los bits necesarios de memotries? Eso haría más fácil diagnosticar el problema. –

+2

La operación 'index' es' O (log (min (i, n-i))) '(según eglefino). Tal vez deberías usar una matriz o vector. –

Respuesta

4

Hay un problema importante que realmente ralentiza esto. Es muy polimorfo Las versiones especializadas de tipos de funciones pueden ser mucho más rápidas que las variedades polimórficas y, por la razón que sea, GHC no está incorporando este código al punto en que puede determinar los tipos exactos en uso. Cuando cambio la definición de integral a:

integral :: Memo Int 
integral = wrap id id bits 

consigo un aumento de velocidad de aproximadamente 5 veces; Creo que es lo suficientemente rápido para ser aceptado en SPOJ.

Sin embargo, esto es aún más lento que la solución de gorlum0. Sospecho que la razón es porque usa matrices y usa un tipo de trie personalizado. Usar un trie llevará mucha más memoria y también hará que las búsquedas sean más lentas debido a desviaciones adicionales, fallas en la memoria caché, etc. Es posible que pueda compensar la diferencia si restringe y desagrupa campos en IntMap, pero no estoy seguro eso es posible. Intentar restringir campos en BitTrie crea bloqueos de tiempo de ejecución para mí.

El código de memoria de Pure haskell puede ser bueno, pero no creo que sea tan rápido como hacer cosas inseguras (al menos bajo el capó). Puede aplicar Lennart Augustsson's technique para ver si le va mejor en la memorización.

0

Lo único que ralentiza a Haskell es IO, The String type en Haskell brinda compatibilidad con UTF8 que no necesitamos para SPOJ. ByteStrings es increíblemente rápido, por lo que es posible que desee considerar usarlos.

Cuestiones relacionadas