在过去的三天里,我一直在努力解决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三角形),但我对算法解决方案很感兴趣。
答案 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,但还有更多,甚至更好一些。 (当然还有更多可能的方法。)