Haskell迭代超过2d列表,过滤,输出1d列表

时间:2013-10-11 01:27:11

标签: haskell

我认为我在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

6 个答案:

答案 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不会将元组作为参数。