康威的生命游戏递归步骤功能

时间:2017-06-03 17:23:08

标签: haskell

为了帮助我开始理解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.

2 个答案:

答案 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 是电路板的高度和宽度。

对于有限大小的电路板,它有点棘手 - 您需要决定在边界上做什么。