17

Haskell の勉強は順風満帆だと思っていたのですが...

私は[[Int]]を持っています

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

およびデータ型:

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

input に基づいて、 aの値が 1の場合にのみaが生成され、がその位置を 2d リストに格納するように、 atilesを出力しようとしました。[Coord]CoordtilesCoord

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

4

6 に答える 6

18

これは、リスト内包表記が輝く場所です。

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

または、( list はモナドであるため)同等の表記法を使用することもできますが、これには(. の場合)doインポートが必要です。Control.Monadguard

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..]
于 2013-10-11T06:18:03.663 に答える
2

これが簡単な解決策です(tilesサイズ10000x10000で実行可能であるとは限りません。それはあなたがチェックするものです;)

このアプローチは、Haskell でよくあるように、トップダウンの開発です。あなたは思う:何をすべきblackBoxか?その行ごとに、その行のタイルの をtiles収集し、それらを連結する必要があります。Coord1

blackBoxRowこれにより、行専用の別の関数 が得られます。それは何をすべきですか?行からゼロを削除し、残りをCoords でラップするので、filterand thenがあり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}]
于 2013-10-11T02:10:30.410 に答える
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代わりにタプルを受け取る関数を作成し、それに対してフリップを使用して、タプルのリストにこれをマップします。

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

イータを減らすことで、これをかなり圧縮することもできます。

インデックスが 0 の座標が必要な場合は、このソリューションを変更してください。

于 2013-10-11T02:11:08.153 に答える
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

これは、 s が使用されていた s[[Int]]を含む行であるtype の何かになります。次に、各行内に sをマップし、ペアを反転して、 の代わりに. 最後のステップとして、行を 1 つのリストにフラット化します。これは、行を個別に保持する必要がなくなったためです。x1y(x,y)(y,x)

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

最後に、各要素をmappingCoordして変換します。引数としてタプルを取らないuncurryので必要です。Coord

于 2013-10-12T22:26:48.867 に答える