Haskell Iterate через 2d список, фильтр, вывод 1d список
Я думал, что в своих исследованиях в 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
будет хранить его позиция в списке 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
но после этого я не уверен, как передавать индексы. Я полагаю, что если бы я мог отображать "сложенные плитки", выводя кортеж (значение, индекс), я мог бы легко выяснить остальное.
update В случае, если кто-то заинтересован, я получил его работу, и вот демонстрация его (с исходным кодом и ссылкой на GitHub)! Мне нужно будет потратить больше времени, чтобы понять каждый из ответов, поскольку это мой первый раз, когда я программировал игру с использованием FP. Большое спасибо!
http://kennycason.com/posts/2013-10-10-haskell-sdl-gameboy-boxxle.html
Ответы
Ответ 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..]
Ответ 2
Здесь простое решение (не гарантируйте, что оно жизнеспособно для tiles
размером 10000x10000, что-то для вас проверить;)
Подход, как обычно, в Haskell, является нисходящим развитием. Вы думаете: что делать blackBox
? Для каждой строки tiles
она должна собрать Coord
из фрагментов с 1
для этой строки и объединить их.
Это дает вам другую функцию blackBoxRow
, только для строк. Что делать? Удалите нули из строки и оберните остальные в Coord
s, так что там 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
создаем функцию, которая вместо этого берет кортеж, а затем мы используем 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-индексированные координаты, я оставлю вас для изменения этого решения.
Ответ 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
Параметр zip
сопоставляет значения tile (которые я имею в виду как 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]
Ответ 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]]
, которые являются строками с x
, где раньше был 1
. Затем я сопоставляю y
в каждой строке, переворачивая пары, поэтому я оставляю (x,y)
вместо (y,x)
. В качестве последнего шага я выравниваю строки в один список, так как мне больше не нужно их разделять:
xsAndYs = concat $ zipWith applyYs [0..] x1s
applyYs i = map (flip (,) i)
Наконец, я преобразую каждый элемент через map
ping Coord
поверх него. uncurry
необходимо, потому что Coord
не принимает кортеж в качестве аргумента.