我昨天刚学会了哈斯克尔,而且我遇到了一项任务。 我给了一个矩阵;用这样的函数实现:
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
谢谢大家的帮助!
答案 0 :(得分:3)
好的,是的,你有点困惑。
所以我的建议首先是尝试理顺你想做的事情,主要是英语。我将尝试引导你到那个没有直接给你答案的,因为这个练习的目的是让你学习更多Haskell。
所以在我看来 - 尽管你没有解释过 - 你打算发生的事情是:
amountofships
调用内部函数rekurs
,告诉它从位置1
1
开始并向其传递一个应该从0
开始的累加器参数
rekurs
应检查它给出的位置,如果该位置是-1
说“哦,我离开网格,返回累加器参数”,否则它应该返回调用自己的结果是右边有一个点。
然后你也可以在某处定义一个你永远不会打电话的new_rekurs
。
首先让我们先尝试修复你的逻辑,然后如果你仍然无法将其转换为Haskell,我们就可以解决这个问题。
所以你似乎遵循的一般模式是“调用一个带有累加器参数的内部尾递归函数来检查”我已经完成了吗?“并且,如果完成,则返回累加器参数。如果没有完成,则为计算它应该去的下一个地方然后自己调用它。“
现在,这是一个可以用来解决这个问题的好模式,但是你正在做的事情有两个问题:
所以,在两个方面旅行:有两种方法可以做到这一点。一种方法是保持单个函数在每个步骤中不断增加x
,直到达到-1
,然后y
增加1
并将x
重置为{ {1}}。另一种方式 - 我认为在你的情况下会更容易,更容易 - 有两个函数叫1
和rekursBoard
(或者你喜欢的任何名字),第一个函数调用第二个函数得到每排相关船舶的数量。
使用双功能解决方案,您要做的是:
rekursRow
(rekursBoard 1 1 0
,x
和y
的参数 - 请注意,您无需继续传递{ {1}}功能于内部功能)res
中检查给定的位置是否不在线。如果是,结果只是board
。如果没有,则结果为rekursBoard
,其中res
是通过调用rekursBoard (x+1) y (res+rowTotal)
来计算的。rowTotal
中,检查给定的位置是否不在线上。如果是,结果只是rekursRow x y 0
。否则,结果为rekursRow
,如果现货res
和rekursRow x (y+1) (res+isOurBoat)
的船只与isOurBoat
匹配,则1
计算为x
给出了-level函数,如果没有,则y
。您的函数的整体结构可能如下所示:
k
这不是构建它的唯一方法,当然,如果我正在编写这个问题的答案表,那就是我要做的事情。构建它的另一种非常可行的方法是使0
在amountofships 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的早期阶段,除非你从书中复制样板代码,否则不要使用rekursBoard
。 x
不做你期望的事。我个人认为该功能命名不佳,现代Haskell课程应避免在页面上放置return
,而是在return
使用return
的每个地方使用return
。 (因为在现代Haskell编译器中,pure
和return
对于所有标准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.