如何在使用单子时避免空间泄漏foldM
?mapM
State
去年的第 20 天代码出现有一个难题,即根据如何穿过迷宫的说明生成迷宫地图。例如,说明NN
给出了迷宫
|
|
*
(一条笔直的走廊往北两步),指示NNN(EE|WW)S
给了迷宫
+-+-+
| | |
|
*
(向北走一点,然后向东然后向南或向西然后向南)。
我试图解决这个问题的方法包括有一个State
单子,其中状态是Set
所有走廊部分的状态(Door
下面称为 s),值是您可以工作的位置列表。
如果你只是沿着一条走廊走Path
,我foldM
会沿着它走,更新当前位置。如果你在一个路口,沿着路口的每个分支,收集你最终到达的所有位置。
此代码在小测试输入上产生正确的结果,但在处理完整示例时存在巨大的空间泄漏。
分析表明它大部分时间都花在includeDoor
.
所以,问题。
- 有空间泄漏吗?如果是这样,在哪里,你怎么知道。
- 我如何解决它?
(我认为正在发生的事情是 Haskell 并没有尽快将完全评估Door
的 s 添加到 s 中Set
。在这种情况下,我不想在任何地方有任何懒惰。)
(我将输入解析为一组二元素向量,这些向量指示每条指令要执行的步骤。该代码运行良好且快速。)
import qualified Data.Set as S
import Linear (V2(..))
import Control.Monad.State.Strict
import Control.Monad.Extra (concatMapM)
type Coord = V2 Integer -- x, y, with north and east incresing values (origin a bottom left)
data Door = Door Coord Coord deriving (Show, Eq, Ord)
type Doors = S.Set Door
data MazeSection = Path [Coord] | Junction [Maze] deriving (Show, Eq)
type Maze = [MazeSection]
type Mapper = State Doors [Coord]
makeDoor :: Coord -> Coord -> Door
makeDoor !a !b
| a < b = Door a b
| otherwise = Door b a
emptyMap = S.empty
part1 maze =
do
let start = V2 0 0
let doors = execState (mapMaze [start] maze) emptyMap
print $ length doors
mapMaze :: [Coord] -> Maze -> Mapper
mapMaze !starts !sections =
foldM (\heres section -> mapMazeSection heres section) starts sections
mapMazeSection :: [Coord] -> MazeSection -> Mapper
mapMazeSection !starts (Junction mazes) =
concatMapM (\maze -> mapMaze starts maze) mazes
mapMazeSection !starts (Path steps) =
mapM mapPath starts
where mapPath start = foldM (\here step -> includeDoor here step) start steps
includeDoor :: Coord -> Coord -> State Doors Coord
includeDoor !here !step =
do let there = (here + step)
let door = there `seq` makeDoor here there
modify' (door `seq` S.insert door)
return there