检查“矩阵”中的每个字段(有限制)

时间:2017-04-22 13:35:57

标签: haskell recursion matrix

我昨天刚学会了哈斯克尔,而且我遇到了一项任务。 我给了一个矩阵;用这样的函数实现:

board 1 1 = 0
board 1 2 = 1
board 1 3 = 0
board 2 1 = 2
board 2 2 = 0
board 2 3 = 0
board 3 1 = 1
board 3 2 = 0
board 3 3 = 2
board _ _ = -1

只是你有一点背景: 该矩阵用于缩小沉船游戏。 所以你会检查一个像这样的条目2 1

board 2 1

并获得相应的结果。 0是默认值,-1不存在; 1代表玩家1拥有的船,2代表2。

现在我必须编写一个只需要计算特定玩家拥有的船只数量的功能,它应该返回该数量。 例如,在该示例板中,2的量将是2。 但是,我有一个限制,我不能使用任何与列表相关的东西。 我想我必须使用递归,这就是我被困住的地方。

我已经尝试过这个: (k是玩家编号,res应该是结果。)

amountofships k board = rekurs 1 1 board res
    where
    res = 0
    rekurs x y board res =
        if (board x y == -1) then return(res)
        else return(rekurs (x + 1) 1 board res)
        where
        new_rekurs a b board res2 =
            if (board a b == -1) then return(res2)
            else if (board a b == k) then return(new_rekurs a (b+1) board (res + 1))
            else return(new_rekurs a (b+1) board res)
            where
            res2 = 0

它的意思是拥有一个遍历每一列的resursion函数,它有另一个递归函数,它将检查该列中的每个条目,返回金额,然后检查下一列等。

它没有编译,我不认为它是一个优雅的方法,但我不能想到另一个。 我会感谢任何帮助。

编辑:谢谢大家的回答。我看到我的代码中的问题在哪里。

我使用了丹尼尔提出的结构,它至少编译:

amountofships k board = rekursColumn 1 1 0
  where
        rekursColumn x y res
            | board x y = res
            | otherwise = rekursColumn (x + 1) y (res + rowTotal)
            where
                rowTotal = rekursRow x y 0

        rekursRow x y res
            | board x y = res
            | otherwise = rekursRow x (y + 1) (res + isOurBoat)
            where
                isOurBoat = if board x y == k then 1 else 0

我仍然得到Could not deduce (Num Bool) arising from the literal ‘1’ 通话时出错 amountofships 1 example_board 。我想这是因为我没有指定输入类型是什么?特别是因为一个参数实际上是一个函数。

编辑2:哦,我知道问题出在哪里。我需要检查一下board x y == -1 我的代码现在正在运作:

amountofships :: Int -> (Int -> Int -> Int) -> Int
amountofships k board = rekursColumn 1 1 0
  where
        rekursColumn x y res
            | board x y == -1 = res
            | otherwise = rekursColumn (x + 1) y (res + rowTotal)
            where
                rowTotal = rekursRow x y 0

        rekursRow x y res
            | board x y == -1 = res
            | otherwise = rekursRow x (y + 1) (res + isOurBoat)
            where
                isOurBoat = if board x y == k then 1 else 0

谢谢大家的帮助!

2 个答案:

答案 0 :(得分:3)

好的,是的,你有点困惑。

所以我的建议首先是尝试理顺你想做的事情,主要是英语。我将尝试引导你到那个没有直接给你答案的,因为这个练习的目的是让你学习更多Haskell。

所以在我看来 - 尽管你没有解释过 - 你打算发生的事情是:

  1. amountofships调用内部函数rekurs,告诉它从位置1 1开始并向其传递一个应该从0开始的累加器参数

  2. rekurs应检查它给出的位置,如果该位置是-1说“哦,我离开网格,返回累加器参数”,否则它应该返回调用自己的结果是右边有一个点。

  3. 然后你也可以在某处定义一个你永远不会打电话的new_rekurs

    首先让我们先尝试修复你的逻辑,然后如果你仍然无法将其转换为Haskell,我们就可以解决这个问题。

    所以你似乎遵循的一般模式是“调用一个带有累加器参数的内部尾递归函数来检查”我已经完成了吗?“并且,如果完成,则返回累加器参数。如果没有完成,则为计算它应该去的下一个地方然后自己调用它。“

    现在,这是一个可以用来解决这个问题的好模式,但是你正在做的事情有两个问题:

    1. 您永远不会向累加器添加任何内容。
    2. 你需要二维旅行。
    3. 所以,在两个方面旅行:有两种方法可以做到这一点。一种方法是保持单个函数在每个步骤中不断增加x,直到达到-1,然后y增加1并将x重置为{ {1}}。另一种方式 - 我认为在你的情况下会更容易,更容易 - 有两个函数叫1rekursBoard(或者你喜欢的任何名字),第一个函数调用第二个函数得到每排相关船舶的数量。

      使用双功能解决方案,您要做的是:

      1. 在顶层,请致电rekursRowrekursBoard 1 1 0xy的参数 - 请注意,您无需继续传递{ {1}}功能于内部功能)
      2. res中检查给定的位置是否不在线。如果是,结果只是board。如果没有,则结果为rekursBoard,其中res是通过调用rekursBoard (x+1) y (res+rowTotal)来计算的。
      3. rowTotal中,检查给定的位置是否不在线上。如果是,结果只是rekursRow x y 0。否则,结果为rekursRow,如果现货resrekursRow x (y+1) (res+isOurBoat)的船只与isOurBoat匹配,则1计算为x给出了-level函数,如果没有,则y
      4. 您的函数的整体结构可能如下所示:

        k

        这不是构建它的唯一方法,当然,如果我正在编写这个问题的答案表,那就是我要做的事情。构建它的另一种非常可行的方法是使0amountofships k board = -- .... some call here where rekursBoard x y res = -- ... some stuff here where rowTotal = -- ... some call here rekursRow x y res = -- ... some stuff here where isOurBoat = -- ... something here 的{​​{1}}子句中定义,而不是rekursRow参数。

        现在,关于在Haskell中工作的一般词 - Haskell有一个名为where的函数,意味着几乎没有你期望的那样。说真的,在学习Haskell的早期阶段,除非你从书中复制样板代码,否则不要使用rekursBoardx不做你期望的事。我个人认为该功能命名不佳,现代Haskell课程应避免在页面上放置return,而是在return使用return的每个地方使用return。 (因为在现代Haskell编译器中,purereturn对于所有标准Monad都是一样的)

        因此,如果您在此问题的Haskell代码中加入return,那么很抱歉

        不要这样做。

        相反,习惯于编写这样的代码:

        pure

        即只是表格return。没有countEmptySquare x y board = if board x y == 0 then 1 else 0 语句,只是简单的表达。

        这一开始看起来很乏味,将每个计算分解为命名部分,但它会变得更好。但是,为了确定早期发生的事情,你应该仔细分解。

答案 1 :(得分:1)

首先让我从语法上清理你的代码:

amountofships :: ... -- Always write out type signatures!
amountofships k board = rekurs 1 1 board res
 where res = 0
       rekurs x y board res
         | board x y == -1  = return res  -- guards usually read nicer than `if`
         | otherwise        = return $ rekurs (x + 1) 1 board res
        where new_rekurs a b board res2
                | board a b == -1  = return res2 -- no need for parens around function arguments!
                | board a b == k   = return $ new_rekurs a (b+1) board (res + 1)
                | otherwise        = return $ new_rekurs a (b+1) board res
               where res2 = 0

现在,这里的一个大问题是return。请注意,Haskell中的return与大多数其他语言中的return非常不同。 是函数结果通常需要的关键字,而return本身只是一个库函数:

return :: Monad m => a -> m a

您需要将“纯类型”a的值(例如3 :: Int)注入m a类型的 monadic action 的结果中,例如Just 3 :: Maybe Int。例如,您可以这样使用它:

-- Find the first even number in the tuple, if any
fstEven :: (Int, Int) -> Maybe Int
fstEven (a,b)
 | even a     = return a  -- for the `Maybe` monad, this is the
 | even b     = return b  --  same as writing `Just a` / `Just b`.
 | otherwise  = Nothing

注意我没有编写return Nothing:这会将已经存在的monadic,空值 Nothing :: Maybe Int包装到另一个monadic层中,这个monadic层太多了。< / p>

在您的代码中类似:您将每个结果包装在return中,但从不打开任何内容。

因为在你的情况下,无论如何一切都是“纯粹的”,根本就没有必要。要创建函数的结果,只需将其写出来,如:

-- Use the first number in the tuple, if it's even; else use the second.
fstIfEven :: (Int, Int) -> Int
fstIfEven (a,b)
 | even a     = a
 | otherwise  = b

或者,在您的情况下,

amountofships :: ... -- Always write out type signatures!
amountofships k board = rekurs 1 1 board res
 where res = 0
       rekurs x y board res
         | board x y == -1  = res
         | otherwise        = rekurs (x + 1) 1 board res
        where new_rekurs a b board res2
                | board a b == -1  = res2
                | board a b == k   = new_rekurs a (b+1) board (res + 1)
                | otherwise        = new_rekurs a (b+1) board res
               where res2 = 0

看起来更好,但它不起作用 - 这是一个有趣的问题。看,你似乎并没有真正想到递归。对于Haskell中的“递归循环”,您不会使用类似res = 0初始化变量,然后以某种方式在循环过程中更改它。而是直接使用初始值作为“前端参数”调用循环体函数,然后保持函数使用其他参数调用自身。我将仅针对一个网格维度的简化问题进行演示。我将使用描述性标记类型代替这些幻数

data Player = PlayerA | PlayerB
 deriving (Eq, Show)

data BoardField = Coast | OpenSea | Ship Player
 deriving (Eq, Show)

type Board = Int -> BoardField   -- “array” of fields which may have ships in them, starting with index 0

amountOfShips :: Player -> Board -> Int
amountOfShips k board = go 0 0  -- the `board` is always the same, no need to pass
                                -- it explicitly to the worker-loop function `go`
 where go x res = case board x of  -- `case` is even better than guards, if all you're doing is equality comparison.
        Coast -> res             -- When we've reached the coast we're done, there can be no more ships.
        Ship player
          | player==k  -> go (x+1) (res+1)  -- Found ship of the requested player,
                                            -- therefore continue recursion with incremented accumulator
        _  -> go (x+1) res  -- No ships here, so continue recusion with the same accumulator.