为了帮助我开始理解Haskell,我正在实施Conway的生命游戏,并试图在我的步骤函数中试图建立一个2D列表。以下相关定义:
data Cell = On | Off deriving (Eq, Show)
type Board = [[Cell]]
type Coord = (Int, Int)
type IndexedBoard = [[(Coord, Cell)]]
testBoard = [[On, On, On]
,[On, Off, On]
,[On, On, On]]
numNeighbors :: Board -> Coord -> Int
-- Returns the number of live neighbors given a Board and a Coord on that board
-- assume this is implemented correctly, because it's a bunch of code
-- that you don't otherwise need to read, and it passes tests.
addIndexes :: Board -> IndexedBoard
addIndexes xs = [[((x,y), el) | (x,el) <- zip [0..] row] | (y,row) <- zip [0..] xs]
removeIndexes :: IndexedBoard -> Board
removeIndexes = map (map snd)
-- addIndexes testBoard is
-- [ [((0, 0), On), ((1, 0), On), ((2, 0), On)]
-- ,((0, 1), On), ((1, 1), Off), ((2, 1), On)]
-- ,((0, 2), On), ((1, 2), On), ((2, 2), On)] ]
-- and removeIndexes . addIndexes = id
我的步功能是我遇到麻烦的地方。我不确定如何构建我期望的2D列表而没有相互递归(我试图避免任意)。
step :: Board -> Board
step board = removeIndexes . step' . addIndexes $ board where
step' :: IndexedBoard -> IndexedBoard
step' [] = []
step' ([]:rest) = step' rest
step' (((coord, cell):restrow):rest) = [(coord, newCell (numNeighbors board coord) cell)] : (step' (restrow:rest))
newCell :: Int -> Cell -> Cell
newCell 2 On = On
newCell 3 _ = On
newCell _ _ = Off
我希望step testBoard
的跟进输出:
[[On, Off, On ]
,[Off, Off, Off]
,[On, Off, On ]]
但我得到了:
[[On], [Off], [On]
,[Off], [Off], [Off]
,[On], [Off], [On]]
我理解这是因为step'
的递归情况给出了[(coord, newCell (numNeighbors board coord) cell)] : ...
,但如果我将其从列表中删除,我会收到以下错误:
[1 of 1] Compiling Main ( gameoflife.hs, interpreted )
gameoflife.hs:51:44: error:
• Couldn't match type ‘(Coord, Cell)’ with ‘[(Coord, Cell)]’
Expected type: IndexedBoard
Actual type: [(Coord, Cell)]
• In the expression:
(coord, newCell (numNeighbors board coord) cell)
: (step' (restrow : rest))
In an equation for ‘step'’:
step' (((coord, cell) : restrow) : rest)
= (coord, newCell (numNeighbors board coord) cell)
: (step' (restrow : rest))
In an equation for ‘step’:
step board
= removeIndexes . step' . addIndexes $ board
where
step' :: IndexedBoard -> IndexedBoard
step' [] = []
step' ([] : rest) = step' rest
step' (((coord, cell) : restrow) : rest)
= (coord, newCell (numNeighbors board coord) cell)
: (step' (restrow : rest))
newCell :: Int -> Cell -> Cell
newCell 2 On = On
newCell 3 _ = On
newCell _ _ = Off
gameoflife.hs:51:96: error:
• Couldn't match type ‘[(Coord, Cell)]’ with ‘(Coord, Cell)’
Expected type: [(Coord, Cell)]
Actual type: IndexedBoard
• In the second argument of ‘(:)’, namely
‘(step' (restrow : rest))’
In the expression:
(coord, newCell (numNeighbors board coord) cell)
: (step' (restrow : rest))
In an equation for ‘step'’:
step' (((coord, cell) : restrow) : rest)
= (coord, newCell (numNeighbors board coord) cell)
: (step' (restrow : rest))
Failed, modules loaded: none.
答案 0 :(得分:4)
step'
的最后一个等式是错误的。
step' (((coord, cell):restrow):rest) = [(coord, newCell (numNeighbors board coord) cell)] : (step' (restrow:rest))
对于每行中的每个单元格,您将生成一个新的单单元格行作为输出。如果你坚持手工编写所有递归,你可以通过查询递归调用返回的第一行来解决这个问题,而不是每次都创建一个新行:
step' :: IndexedBoard -> IndexedBoard
step' [] = []
step' ([]:rest) = [] : step' rest
step' (((coord, cell):restrow):rest) =
let (row:more) = step' (restrow:rest)
in ((coord, newCell (numNeighbors board coord) cell) : row) : more
然而,有一个更好的方法:你所做的只是尝试将函数应用于嵌套列表中的每个(Coord, Cell)
对,同时保持列表的结构。但这正是map
的用途!您应该定义一个更简单的函数来更新列表中的单个条目,然后将其映射到所有条目:
updateCell :: (Coord, Cell) -> (Coord, Cell)
updateCell (coord, cell) = (coord, newCell (numNeighbors board coord) cell)
step' :: IndexedBoard -> IndexedBoard
step' = map (map updateCell)
答案 1 :(得分:1)
对于实际问题,请参阅amalloy的答案。这是一个太长的评论......
添加索引然后随机访问其他单元格的整个想法(如numNeighbours
所做的那样)在Haskells中非常不同寻常。指数总是忘记很多结构信息,不安全,特别是列表效率很低。而且非常不必要:你所访问的只是直接邻居。
更好的方法是概括在其直接环境中映射元素的想法。
mapInEnvi :: (a -> a -> a -> r) -> [a] -> [r]
mapInEnvi f (a:b:c:l) = f a b c : mapInEnvi
... †
好消息是这样一个高阶函数如何组成:在你的情况下你有一个二维列表,但你基本上只能在每个方向上独立映射:
step :: Board -> Board
step = mapInEnvi modRow
where modRow above row below = mapInEnvi modCell $ zip3 above row below
modCell (ne,e,se)
(n ,m, s)
(nw,w,sw) = case length $ filter (==On) [ne,e,se,n,s,nw,w,sw] of
0 -> Off
1 -> Off
2 -> m
3 -> On
_ -> Off
与您的目标解决方案不同,这不需要执行任何直接位置查找,因此复杂性只是 O ( h · w )而不是 O ( h · w ·( h + w ))其中 h 和 w 是电路板的高度和宽度。
† 对于有限大小的电路板,它有点棘手 - 您需要决定在边界上做什么。