项目欧拉15 - 最后一次尝试

时间:2012-02-24 11:09:26

标签: haskell

在过去的三天里,我一直在努力解决Haskell中的Project Euler 15问题。

这是我目前的状态:

import Data.Map as Map

data Coord = Coord Int Int deriving (Show, Ord, Eq)

corner :: Coord -> Bool 
corner (Coord x y) = (x == 0) && (y == 0)

side :: Coord -> Bool
side (Coord x y) = (x == 0) || (y == 0)

move_right :: Coord -> Coord
move_right (Coord x y) = Coord (x - 1) y

move_down :: Coord -> Coord
move_down (Coord x y) = Coord x (y - 1)

calculation :: Coord -> Integer
calculation coord 
           | corner coord = 0
           | side coord = 1 
           | otherwise = (calculation (move_right coord)) + (calculation (move_down coord)) 

problem_15 :: Int -> Integer
problem_15 size =
           calculation (Coord size size)

它工作得很好,但是如果' n'越来越大了。

据我所知,我可以使用动态编程和哈希表(例如Data.Map)来缓存计算值。

我试图use memoization,但没有成功。我正在尝试to use Data.Map,但每个下一个错误都比以前更加可怕。所以我问你的帮助:如何缓存已计算的值?

我知道这个问题的数学解决方案(Pascal三角形),但我对算法解决方案很感兴趣。

2 个答案:

答案 0 :(得分:8)

这个问题更适合于二维数组缓存,而不是Map,因为我们有一个有界的输入值范围。

import Control.Applicative
import Data.Array

data Coord = Coord Int Int deriving (Show, Ord, Eq, Ix)

calculation :: Coord -> Integer
calculation coord@(Coord maxX maxY) = cache ! coord where
    cache = listArray bounds $ map calculate coords
    calculate coord
        | corner coord = 0
        | side coord   = 1
        | otherwise    = cache ! move_right coord + cache ! move_down coord

    zero  = Coord 0 0
    bounds = (zero, coord)
    coords = Coord <$> [0..maxX] <*> [0..maxY]

我们将deriving Ix添加到Coord,以便我们可以直接将其用作数组索引,在计算中,我们初始化一个二维数组cache,其下限为Coord 0 0和更高coord的界限。然后,我们只是引用缓存中的值,而不是递归调用calculation

现在我们可以相对快速地计算出更大的值。

*Main> problem_15 1000 2048151626989489714335162502980825044396424887981397033820382637671748186202083755828932994182610206201464766319998023692415481798004524792018047549769261578563012896634320647148511523952516512277685886115395462561479073786684641544445336176137700738556738145896300713065104559595144798887462063687185145518285511731662762536637730846829322553890497438594814317550307837964443708100851637248274627914170166198837648408435414308177859470377465651884755146807496946749238030331018187232980096685674585602525499101181135253534658887941966653674904511306110096311906270342502293155911108976733963991149120

答案 1 :(得分:6)

既然你已经知道了正确的(有效的)解决方案,我就不会为你破坏任何东西:

您可以使用数组(这里非常合适,因为域是一个矩形)

import Data.Array

pathCounts :: Int -> Int -> Array (Int,Int) Integer
pathCounts height width = solution
  where
    solution =
        array ((0,0),(height-1,width-1)) [((i,j), count i j) | i <- [0 .. height-1]
                                                             , j <- [0 .. width-1]]
    count 0 j = 1  -- at the top, we can only come from the left
    count i 0 = 1  -- on the left edge, we can only come from above
    count i j = solution ! (i-1,j) + solution ! (i,j-1)

或者您可以使用State monad(先前计算的值是状态,存储在Map中):

import qualified Data.Map as Map
import Control.Monad.State.Strict

type Path = State (Map Coord Integer)

calculation :: Coord -> Path Integer
calculation coord = do
    mb_count <- gets (Map.lookup coord)
    case mb_count of
      Just count -> return count
      Nothing
          | corner coord -> modify (Map.insert coord 0) >> return 0 -- should be 1, IMO
          | side coord -> modify (Map.insert coord 1) >> return 1
          | otherwise -> do
              above <- calculation (move_down coord)
              left <- calculation (move_right coord)
              let count = above + left
              modify (Map.insert coord count)
              return count

并使用

运行它
evalState (calculation target) Map.empty

或者你可以在hackage上使用其中一个记忆包,我记得data-memocombinators,但还有更多,甚至更好一些。 (当然还有更多可能的方法。)