我认为我在Haskell研究中一帆风顺,直到......
我有一个[[Int]]
tiles = [[1,0,0]
,[0,1,0]
,[0,1,0]
]
和数据类型:
data Coord = Coord
{ x :: Int
, y :: Int
} deriving (Eq)
根据输入tiles
,我一直在尝试输出[Coord]
,这样只有在Coord
的值为1时才生成tiles
,并且Coord
会将其存储在2d列表中:
blackBox :: [[Int]] -> [Coord]
blackBox tiles = <magic>
-- given the above example I would expect:
-- [(Coord 0 0),(Coord 1 1),(Coord 1 2)]
我尝试过首先将[[Int]]转换为[Int],通过:
foldTiles :: [[Int]] -> [Int]
foldTiles tiles = foldr (++) [] tiles
但在那之后,我不确定如何传递指数。我想如果我可以映射“折叠的瓷砖”,输出一个元组(值,索引),我可以很容易地找出其余部分。
更新 如果有人感兴趣,我让它工作,这里是它的演示(源代码和GitHub的链接)!我将不得不花更多的时间来理解每个答案,因为这是我第一次使用FP编写游戏。非常感谢!
http://kennycason.com/posts/2013-10-10-haskell-sdl-gameboy-boxxle.html
答案 0 :(得分:12)
这是列表理解发光的地方。
blackBox tiles =
[Coord x y -- generate a Coord pair
| (y, row) <- enumerate tiles -- for each row with its coordinate
, (x, tile) <- enumerate row -- for each tile in the row (with coordinate)
, tile == 1] -- if the tile is 1
或者您可以使用等效的do
表示法(因为列表是monad),这需要导入Control.Monad
(对于guard
。)
blackBox tiles = do
(y, row) <- enumerate tiles -- for each row with its coordinate
(x, tile) <- enumerate row -- for each tile in the row (with coordinate)
guard $ tile == 1 -- as long as the tile is 1
return $ Coord x y -- return a coord pair
为了帮助理解,后一个函数的工作方式类似于以下Python函数。
def black_box(tiles):
for y, row in enumerate(tiles):
for x, tile in enumerate(row):
if tile == 1:
yield Coord(x, y)
列表monad的 do
符号对于处理列表非常方便,我认为,所以值得环顾四周!
在这两个例子中,我都使用了定义
enumerate = zip [0..]
答案 1 :(得分:2)
这是一个简单的解决方案(不保证它适用于大小为10000x10000的tiles
,这是您需要检查的内容;)
这种方法与Haskell一样,是一种自上而下的开发方式。你认为:blackBox
应该做什么?对于tiles
的每一行,它应该为该行收集Coord
的{{1}}个1
个,并将它们连接起来。
这为您提供了另一个函数blackBoxRow
,仅用于行。它该怎么办?从行中删除零,并将其余部分包裹在Coord
中,以便filter
然后map
。您还要保留行号和列号,以便映射使用各自坐标连接的切片。
这会给你:
tiles :: [[Int]]
tiles = [[1,0,0]
,[0,1,0]
,[0,1,0]
]
data Coord = Coord {
x :: Int
,y :: Int
} deriving (Eq, Show)
blackBox :: [[Int]] -> [Coord]
blackBox tiles2d = concat (map blackBoxRow (zip [0..] tiles2d))
blackBoxRow :: (Int, [Int]) -> [Coord]
blackBoxRow (row, tiles1d) = map toCoord $ filter pickOnes (zip [0..] tiles1d) where
pickOnes (_, value) = value == 1
toCoord (col, _) = Coord {x=col, y=row}
main = print $ blackBox tiles
结果:
~> runhaskell t.hs
[Coord {x = 0, y = 0},Coord {x = 1, y = 1},Coord {x = 1, y = 2}]
答案 2 :(得分:2)
我看到它的方式,你可以把你的2D列表通过一系列转换。我们需要的第一个可以用一些更有用的东西替换列表中的1
,例如它的行:
assignRow :: Int -> [Int] -> [Int]
assignRow n xs = map (\x -> if x == 1 then n else x) xs
我们现在可以使用zipWith
和[1..]
执行第一步:
assignRows :: [[Int]] -> [[Int]]
assignRows matrix = zipWith assignRow [1..] matrix
这方面有用的是,即使矩阵不是方形,它也会起作用,并且只要矩阵出现就会终止。
接下来我们需要分配列号,在这里我将立即执行几个步骤。这会产生坐标的元组,但是r == 0
中存在无效的(这就是我使用[1..]
的原因,否则,您将丢失第一行),因此我们将其过滤掉。接下来,我们uncurry Coord
创建一个取代元组的函数,然后我们使用flip,然后将这个东西映射到元组列表上。
assignCol :: [Int] -> [Coord]
assignCol xs = map (uncurry (flip Coord)) $ filter (\(c, r) -> r /= 0) $ zip [1..] xs
我们可以构建我们的assignCols
:
assignCols :: [[Int]] -> [Coord]
assignCols matrix = concatMap assignCol matrix
允许我们构建最终函数
assignCoords :: [[Int]] -> [Coord]
assignCoords = assignCols . assignRows
你可以通过减少一些eta来压缩这一点。
如果您想要0索引坐标,我将让您修改此解决方案。
答案 3 :(得分:2)
快速而肮脏的解决方案:
import Data.Maybe (mapMaybe)
data Coord = Coord {
x :: Int
,y :: Int
} deriving (Eq, Show)
blackBox :: [[Int]] -> [Coord]
blackBox = concatMap (\(y, xks) -> mapMaybe (toMaybeCoord y) xks)
. zip [0..] . map (zip [0..])
where
toMaybeCoord :: Int -> (Int, Int) -> Maybe Coord
toMaybeCoord y (x, k) = if k == 1
then Just (Coord x y)
else Nothing
zip
s对使用x和y坐标的tile值(我称之为k
)(我们正在处理列表,所以我们必须添加索引,如果我们需要他们)。 mapMaybe
很方便,因此我们可以在一个步骤中映射(以构建Coords
)和过滤(以删除零切片)。 concatMap
这里也做了两件事:它映射一个函数(括号内的匿名函数),生成一个列表列表然后展平它。务必检查中间函数和结果的类型,以获得更清晰的转换图像。
答案 4 :(得分:2)
这是使用列表推导。
blackBox :: [[Integer]] -> [Coord]
blackBox ts = [Coord x y | (t,y) <- zip ts [0..], (e,x) <- zip t [0..], e == 1]
答案 5 :(得分:0)
只要我们收集答案,这是另一个:
blackBox :: [[Int]] -> [Coord]
blackBox ts = map (uncurry Coord) xsAndYs
where
xsAndYs = concat $ zipWith applyYs [0..] x1s
applyYs i = map (flip (,) i)
x1s = map (map fst . filter ((==1) . snd)) xs
xs = map (zip [0..]) ts
<强>解释强>
这会在每行中分配x
个索引:
xs = map (zip [0..]) ts
然后我过滤每一行以仅保留带有1
的元素,然后删除1
(因为它不再有用):
x1s = map (map fst . filter ((==1) . snd)) xs
这导致类型为[[Int]]
的某些内容,其中x
的行位于1
以前。然后,我会映射每行中的y
s,翻转这些对,以便我留下(x,y)
而不是(y,x)
。最后一步,我将行压缩成一个列表,因为我不再需要将它们分开了:
xsAndYs = concat $ zipWith applyYs [0..] x1s
applyYs i = map (flip (,) i)
最后,我将map
ping Coord
转换为每个元素。 uncurry
是必要的,因为Coord
不会将元组作为参数。