【问题标题】:Haskell Iterate over 2d list, filter, output 1d listHaskell迭代二维列表,过滤,输出一维列表
【发布时间】:2013-10-11 01:27:10
【问题描述】:

我以为我在 Haskell 学习中一帆风顺,直到...

我有一个 [[Int]]

tiles = [[1,0,0]
        ,[0,1,0]
        ,[0,1,0]
        ]

和数据类型:

data Coord = Coord
    { x :: Int
    , y :: Int 
    } deriving (Eq)

基于输入tiles,我一直在尝试输出[Coord],这样Coord 仅在tiles 的值为1 时生成,Coord 将存储它在二维列表中的位置:

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

【问题讨论】:

    标签: haskell


    【解决方案1】:

    这是列表推导大放异彩的地方。

    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 表示法(因为列表是一个单子),这需要导入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)
    

    我认为,列表单子的do 表示法对于处理列表非常方便,因此值得您花点心思!


    在这两个例子中我都使用了定义

    enumerate = zip [0..]
    

    【讨论】:

    • 非常感谢!终于懂zip了哈哈
    【解决方案2】:

    这是一个简单的解决方案(不保证它对于大小为 10000x10000 的 tiles 是可行的,这需要您检查;)

    与 Haskell 中的往常一样,这种方法是自上而下的开发。你想:blackBox 应该怎么做?对于tiles 的每一行,它应该为该行收集带有1 的磁贴的Coords,并将它们连接起来。

    这为您提供了另一个函数blackBoxRow,仅适用于行。它应该怎么做?从行中删除零,并将其余部分包装在Coords 中,因此有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}]
    

    【讨论】:

    • 非常感谢!我试过了,它奏效了:)我将不得不更彻底地阅读它(以及其他答案以进一步理解它)
    【解决方案3】:

    在我看来,您可以通过一系列转换来放置您的 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 来创建一个接受元组的函数,然后我们对其使用翻转,然后将这个东西映射到元组列表上。

    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 索引坐标,我会让你修改这个解决方案。

    【讨论】:

      【解决方案4】:

      快速而肮脏的解决方案:

      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
      

      zips 将平铺值(我将其称为k)与 x 和 y 坐标配对(我们正在处理列表,因此如果需要它们,我们必须添加索引)。 mapMaybe 很方便,因此我们可以在一个步骤中进行映射(以构造 Coords)和过滤(以删除零图块)。 concatMap 在这里也做了两件事:它映射一个函数(括号内的匿名函数),生成一个列表列表,然后将其展平。请务必检查中间函数的类型和结果,以便更清楚地了解转换。

      【讨论】:

        【解决方案5】:

        就是这样,使用列表推导式。

        blackBox :: [[Integer]] -> [Coord]
        blackBox ts = [Coord x y | (t,y) <- zip ts [0..], (e,x) <- zip t [0..], e == 1]
        

        【讨论】:

        • 这只会选择每行中的第一个“1”图块。 elemIndex 是罪魁祸首。
        • @duplode:可能是我没有得到问题。那么当有多个 1 的图块时会发生什么,即 [1,1,1] .. 它应该导致每个 1 的多个 Coord 吗?
        • 这似乎是 OP 的意图 - 就像在游戏板中一样,其值是指示图块是否被占用的标志。
        • @duplode:更新了答案。谢谢你的澄清
        • 这似乎很有说服力:)
        【解决方案6】:

        只要我们正在收集答案,这里还有另一个:

        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]] 类型的内容,这些行是 xs 的行,而 1s 过去是。然后我在每一行中映射ys,翻转这对,所以我留下(x,y)而不是(y,x)。作为最后一步,我将行合并为一个列表,因为我不再需要将它们分开:

        xsAndYs = concat $ zipWith applyYs [0..] x1s
        applyYs i = map (flip (,) i)
        

        最后我通过mapping Coord 转换每个元素。 uncurry 是必要的,因为 Coord 不接受元组作为参数。

        【讨论】:

          猜你喜欢
          • 2017-09-15
          • 1970-01-01
          • 2013-05-09
          • 1970-01-01
          • 2019-01-12
          • 1970-01-01
          • 2018-12-01
          • 2021-08-06
          • 1970-01-01
          相关资源
          最近更新 更多