【问题标题】:Basic Haskell Functions基本的 Haskell 函数
【发布时间】:2014-09-06 08:58:23
【问题描述】:

我是 Haskell 的新手,我正在努力学习基础知识。我需要为位置声明一个名为 Pos 的类型,它将有两个整数,然后我需要有方向北、南、西、东,以便我可以根据方向更改位置。 一旦我这样做了,我需要创建一个名为 move 的函数,它将获取一个移动列表和初始位置,并在所有移动之后返回该位置。 这是我的代码,但我被困在必须遍历移动列表的地步。

type Pos = (Int, Int)
data Direction = North | South | East | West

move :: Direction -> Pos -> Pos
move North (x,y) = (x, y+1)
move West (x,y) = (x-1, y)
move South (x,y) = (x, y-1)
move East (x,y) = (x+1, y)

moves :: [Direction] -> Pos -> Pos
moves [] (x,y) = (x,y)
moves (h:xs) (x,y) 
        | h == North = move North (x,y)
        | h == West  = move West (x,y)
        | h == South = move South (x,y)
        | otherwise  = move East (x,y)

我在这里错过了什么?

【问题讨论】:

  • 你没有去“迭代”。 递归地思考。你有一个起点p 和一个移动序列m1,...,mn。要执行移动,您首先在起点p 上执行移动m1,从而到达点p'。现在您必须以起点p' 执行移动m2,...,mn...这与之前的问题相同,但参数不同。

标签: haskell


【解决方案1】:

将一系列事物组合成一个事物称为折叠列表。有(大约)两个列表折叠:foldlfoldr。倾向于使用折叠是学习 Haskell 的重要一步。我们需要foldl,它有类型

foldl :: (a -> b -> a) -> a -> [b] -> a

现在foldl 使用组合函数和初始值来组合列表中的内容,例如

foldl (£) s [x,y,z] = (((s £ x) £ y) £ z)

foldl 中的 l 是 left 的缩写,因此它可以帮助您记住您的起始值 s 将在左侧结束,但更重要的是括号与左侧相关联。)

第三个参数是一个列表参数[b]。我们将其用于移动列表,因此 b 的类型将为 Direction

第二个参数是a 类型的起始值,因此我们将使用它作为您的初始位置,因此a 类型将是Pos

第一个参数是将列表中的某些内容与当前值组合的函数。现在我们知道baDirectionPos 类型,我们知道我们的组合函数必须有Pos -> Direction -> Pos 类型。 move 函数几乎正是我们所需要的,除了我们需要交换参数。 flip 函数就是这样做的,所以 flip move 有我们需要的类型。

所以我们将foldl的类型特化为

foldl :: (Pos -> Direction -> Pos) -> Pos -> [Direction] -> Pos

并定义

moves :: [Direction] -> Pos -> Pos
moves ds i = foldl (flip move) i ds

现在foldl 有一个名为foldl' 的“严格”版本,在这种情况下它更快,所以如果你在快节奏的游戏中使用它,或者处理大量动作,你会想用那个。

与往常一样,您可以通过搜索函数名称或在 hoogle 上键入来找到函数。

还有一个 foldr 函数,它以不同的方式折叠列表。您可以在this question 中了解它们之间的区别。不过,简而言之,foldr 的工作方式如下:

foldr (?) s [x,y,z] = (x ? (y ? (z ? s)))

foldr 中的 r 是 right 的缩写,因此它可以帮助您记住您的起始值 s 将在右侧结束,但更重要的是括号与右侧相关联。)

【讨论】:

  • 如果列表中的第一个动作是第一个要采取的动作,你不想要一个 foldl 吗?
  • @ReinHenrichs 呵呵——我刚刚登录,因为当我的孩子们玩的时候,我也有了同样的认识。正在进行编辑。
  • @ReinHenrichs 谢谢。已排序。
  • 很好的答案。我只是想指出,如果你重构为move :: Pos -> Direction -> Posmoves :: Pos -> [Direction] -> Pos,那么moves 的实现会减少到moves = foldl move
  • moves = flip $ foldl (flip move) ^^
【解决方案2】:

在与h 对应的单步移动之前或之后,您需要对moves 的其余部分进行递归调用以获取Directions 的其余部分。如果之前,那么每个案例都变成这样:

move North (moves xs (x,y))

如果您希望它在之后,考虑到问题规范,这可能是正确的方法,那么它将是:

moves xs (move North (x,y))

但是在moves 中重复案例分析有点不必要,因为您可以直接使用h 调用move,例如

moves (h:xs) (x,y) = moves xs (move h (x,y))

事实上,您甚至不需要在 (x,y) 元组上显式地进行模式匹配;

moves (h:xs) pos = moves xs (move h pos)

正如另一个答案所指出的,这与内置的 foldl 函数相同(取决于参数顺序),但最好先了解如何手动操作。

【讨论】:

  • 我认为如果你使用moves (h:xs) (x,y) = moves xs (move h (x,y))会更清楚。
  • 谢谢。我以为我必须使用监护人来检查列表中的移动类型......
猜你喜欢
  • 1970-01-01
  • 2015-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多