如何在 Haskell 中创建一个二维数组?

5

我正在尝试通过创建一个棋盘游戏来学习Haskell。我目前有一个由[[Char]]组成的游戏,我想创建另一个相同列和行的棋盘,并填充字符"a"。我该如何做呢?此外,你能解释一下存储值和访问的工作原理吗?


2
一般来说,array 包是您想要的。作为初学者,只需使用嵌套列表即可。 - Alec
1
欢迎来到StackOverflow。这是一个有价值的问题,但我们通常希望看到一些自己解决问题的努力。 - leftaroundabout
1个回答

4

与其创建一个内容不同但大小相同的新板,将此操作视为替换现有板的内容可能更有帮助。当然,因为Haskell是一种不可变语言,这实际上等同于同样的事情——改变某物的唯一方法是生成它的新版本——但这应该可以帮助您看到这本质上是一种映射操作。

replaceWithA :: [[a]] -> [[Char]]
replaceWithA xss = map (map (const 'a')) xss
-- or, point-free:
replaceWithA = map (map (const 'a'))
-- or, as a list comprehension:
replaceValues xss = [['a' | x <- xs] | xs <- xss]

如果你想深入了解,可以让编译器为你编写此代码。 Functor 类型类将 map 推广到不是简单列表的结构:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

这个泛化的 map 在哪些方面有所不同?如果你用 [] 代替 f,你会发现 fmapmap 有相同的签名:
fmap :: (a -> b) -> [a] -> [b]

实际上,[]Functor实例是这样实现的:

instance Functor [] where
    fmap = map

无论如何,GHC自己知道如何编写Functor实例。我将为2D列表定义一个newtype包装器,并说出魔法言辞deriving Functor

{-# LANGUAGE DeriveFunctor #-}
import Data.Functor

newtype TwoDimensional a = TwoDimensional { getTwoDimensional :: [[a]] } deriving Functor

生成的 fmap 将具有以下签名:
fmap :: (a -> b) -> TwoDimensional a -> TwoDimensional b

现在您可以使用标准的机器来替换固定值的元素:
replaceWithA :: TwoDimensional a -> TwoDimensional Char
replaceWithA = ('a' <$)

当你在Haskell和它的标准抽象(例如Functor)方面获得经验时,你会更容易发现一个给定操作是否是更通用模式的实例。仔细设置类型可以让你将大量样板代码委托给编译器,从而使你能够简洁地、声明性地解决问题的有趣部分。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接