3

这将是一个井字游戏实现:

data Row    = A | B | C  deriving (Show, Read, Eq, Ord, Enum, Bounded)
data Column = X | Y | Z  deriving (Show, Read, Eq, Ord, Enum, Bounded)
type Pos = (Row, Column)
data Player = Player String
data Field = Field [(Pos, Player)]
initialField :: Field
initialField = Field []

如您所见,initialField 只是一个空列表,当玩家移动时,(pos, player) 元组将被添加到列表中。到目前为止,一切都很好。但是现在我很难编写一个possibleMoves :: Field -> [Pos]函数来获取空字段。如何迭代 Haskell 中的所有行、列可能性?我觉得我的方法是错误的,但我是 Haskell 的新手,所以我没有更好的主意。

4

3 回答 3

5

我们可以通过列表推导获得所有职位(另请参阅其他答案)

positions :: [Pos]
positions = [(r,c) | r <- [A,B,C], c <- [X,Y,Z]]

和所有玩地图

occupied :: Field -> [Pos]
occupied (Field l) = fmap fst l

然后我们可以定义 possibleMoves (你需要导入 Data.List 来获取\\,列出差异):

possibleMoves :: Field -> [Pos]
possibleMoves f = positions \\ (occupied f)

更紧凑的版本利用列表理解约束:

possibleMoves :: Field -> [Pos]
possibleMoves (Field l) = [(r,c) | r <- [A,B,C], c <- [X,Y,Z], (r,c) `notElem` occupied]
  where occupied = fmap fst l
于 2013-05-09T20:11:59.670 回答
3

所有行(参考 =获取 Haskell 中所有可能的数据类型值的列表):

Prelude> [(minBound :: Row) ..]
[A,B,C]

所有列:

Prelude> [(minBound :: Column) ..]
[X,Y,Z]

计算笛卡尔积以找到所有可能性(参考 = Haskell 中 2 个列表的笛卡尔积):

Prelude> [(x, y) | x <- [(minBound :: Row)..], y <- [(minBound :: Column)..]]
[(A,X),(A,Y),(A,Z),(B,X),(B,Y),(B,Z),(C,X),(C,Y),(C,Z)]
于 2013-05-09T20:00:05.687 回答
2

如果你想要一个超短的“ulta-Haskelly”版本:

enumAll :: (Bounded a, Enum a) => [a]
enumAll = [minBound..maxBound]

positions :: [Pos]
positions = (,) <$> enumAll <*> enumAll

(您还需要为<$>and<*>运算符导入 Control.Applicative)

然后在 ghci 中:

*Main> positions
[(A,X),(A,Y),(A,Z),(B,X),(B,Y),(B,Z),(C,X),(C,Y),(C,Z)]

这到底是怎么回事?

enumAll只是一个通用的辅助函数(我很惊讶不能在标准库中快速找到它;可能我错过了它,你甚至不需要自己定义它)。它为您提供了任何有界可枚举类型的所有可能性的列表。这相当简单;Bounded表示类型有minBoundmaxBoundEnum表示您可以使用[a..b]语法来获取从a到的所有内容的列表b

(,)只是配对功能。如果你输入:t (,)ghci,它会告诉你(,) :: a -> b -> (a, b).

现在,那些奇怪<$><*>符号呢?它们基本上是功能应用的“特殊”形式。因此,您几乎可以阅读它,就像我们只是将(,)两个参数应用于enumAllenumAll。但是因为它是“特殊”的应用程序,它不仅仅给了我们一对(enumAll, enumAll)

我们在这里所做的是将Applicative实例用于列表。我不打算详细介绍它,但它的作用是帮助我们认为这[Row]不是行值列表,而是单个“未知”行值。它是一个可以是列表中任何元素的值,但我们不知道是哪一个。通常用于此的技术术语是[Row]可以被认为是不确定 Row的;这是一个Row可能有多种可能性的值,但我们无法确定它实际上是哪一种。

所以我们正在做的是将函数(,)(它只需要两个参数来构建一对)应用于两个非确定性值,以获得一个非确定性对(这是我们需要使用 and 的“特殊”版本的函数应用程序的地方<$><*>如果我们(,)通常使用(,) enumAll enumAll或直接构建对,(enumAll, enumAll)然后我们得到的只是一对正常的非确定性值,而不是一对非确定性的正常值 - ([a], [b])vs [(a, b)]) 。如果我从一个有界枚举的所有可能性和另一个有界枚举的所有可能性中取一对,那么我应该得到对的所有可能性!这正是发生的事情。

positions :: [Pos]这里实际上需要类型签名。这就是告诉 Haskell 我们正在构建一个(Row, Column)对列表而不是任何其他类型的枚举对,这就是它知道第一个enumAll枚举所有行和第二个 wsa 枚举所有列的方式。最通用的类​​型(,) <$> enumAll <*> enumAll实际上是(Enum a, Bounded a, Enum b, Bounded b) => [(a, b)],它可以正常工作,但在 ghci 中以交互方式使用会很痛苦,因为每当您尝试打印内容时,您都会不断获得模棱两可的类型变量。

于 2013-05-11T00:58:02.573 回答