Упрощение функции haskell
Я действительно борюсь с банкоматом Haskell.
мне понадобилось почти 6 часов, чтобы написать функцию, которая делает то, что я хочу. К сожалению, я не удовлетворен его видом.
кто-нибудь может дать мне какие-либо подсказки, как переписать его?
get_connected_area :: Eq generic_type => [[generic_type]] -> (Int, Int) -> [(Int,Int)] -> generic_type -> [(Int,Int)]
get_connected_area habitat point area nullValue
| elem point area = area
| not ((fst point) >= 0) = area
| not ((snd point) >= 0) = area
| not ((fst point) < (length habitat)) = area
| not ((snd point) < (length (habitat!!0))) = area
| (((habitat!!(fst point))!!(snd point))) == nullValue = area
| otherwise =
let new_area = point : area
in
get_connected_area habitat (fst point+1, snd point) (
get_connected_area habitat (fst point-1, snd point) (
get_connected_area habitat (fst point, snd point+1) (
get_connected_area habitat (fst point, snd point-1) new_area nullValue
) nullValue
) nullValue
) nullValue
функция get - это [[generic_type]] (представляющая ландшафтную карту) и ищет полностью связанную область вокруг точки, которая не равна заданному нулевому значению.
например.:
Если функция вызывается следующим образом:
get_connected_area [[0,1,0],[1,1,1],[0,1,0],[1,0,0]] (1,1) [] 0
, что буквально означает
0 1 0
1 1 1
0 1 0
1 0 0
представляет собой карту (например, Google maps). Начните с точки (координат) (1,1), я хочу получить все координаты элементов, которые образуют связанную область с данной точкой.
результат поэтому должен быть:
0 1 0
1 1 1
0 1 0
1 0 0
и corresponting возвращаемого значения (список координат смелый 1С):
[(2,1),(0,1),(1,2),(1,0),(1,1)]
4 ответов
одно небольшое изменение заключается в том, что вы можете использовать сопоставление шаблонов для переменной point
. Это означает, что вы можете использовать (x, y)
вместо point
в объявлении функции:
get_connected_area habitat (x, y) area nullValue = ...
везде fst point
, просто поставить x
, и везде у вас есть snd point
, поставил y
.
другая модификация-использовать больше переменных для подвыражений. Это может помочь с вложенными рекурсивными вызовами. Например, сделайте переменную для самой вложенной звоните:
....
where foo = get_connected_area habitat (x, y-1) new_area nullValue
просто поставить foo
вместо вызова. Теперь эту технику можно повторить для "нового" внутреннего вызова. (Обратите внимание, что вы должны выбрать более описательное имя, чем foo
. Может быть!--11-->?)
отметим, что not (x >= y)
это то же самое, что x < y
. Используйте это, чтобы упростить все условия. Поскольку эти условия проверяют, находится ли точка внутри ограничивающего прямоугольника, большая часть этой логики может быть отнесена к функции isIn :: (Int, Int) -> (Int, Int) -> (Int, Int) -> Bool
что будет get_connected_area
больше читаемый.
это будет мой первый быстрый проход через функцию и своего рода минимум, который может пройти обзор кода (только с точки зрения стиля):
getConnectedArea :: Eq a => [[a]] -> a -> (Int, Int) -> [(Int,Int)] -> [(Int,Int)]
getConnectedArea habitat nullValue = go where
go point@(x,y) area
| elem point area = area
| x < 0 = area
| y < 0 = area
| x >= length habitat = area
| y >= length (habitat!!0) = area
| ((habitat!!x)!!y) == nullValue = area
| otherwise =
foldr go (point : area)
[ (x+1, y), (x-1, y), (x, y+1), (x, y-1) ]
мы персонализация habitat
и nullValue
Как только на верхнем уровне (уточнение того, что делает рекурсивная работа), удалите косвенность в предикатах, используйте camel-case (underdashes затемняют, где происходит применение функции), замените generic_type
С a
(использование шумной переменной здесь фактически имеет противоположный эффект от одного вы намеревались; я в конечном итоге пытаюсь выяснить, какую специальную семантику вы пытаетесь вызвать, когда интересная вещь заключается в том, что тип не имеет значения (если его можно сравнить для равенства)).
на данный момент есть много вещей мы можем сделать:
- представьте, что мы пишем реальный код и беспокоимся об асимптотике обработки списков как массивов (
!!
иlength
) и наборы (elem
), и использовать правильный массив и установить структуры данных вместо - переместите проверку границ (и возможную проверку нулевого значения) в новую функцию поиска (цель состоит в том, чтобы иметь только один
... = area
пункт если это возможно - рассмотрим улучшения алгоритма: можем ли мы избежать рекурсивной проверки ячейки, из которой мы только что пришли алгоритмически? можем ли мы избежать прохождения
area
полностью (что делает наш поиск приятно ленивым/ "продуктивным")?
вот мое мнение:
import qualified Data.Set as Set
type Point = (Int, Int)
getConnectedArea :: (Point -> Bool) -> Point -> Set.Set Point
getConnectedArea habitat = \p -> worker p Set.empty
-- \p is to the right of = to keep it out of the scope of the where clause
where
worker p seen
| p `Set.member` seen = seen
| habitat p = foldr worker (Set.insert p seen) (neighbors p)
| otherwise = seen
neighbors (x,y) = [(x-1,y), (x+1,y), (x,y-1), (x,y+1)]
что я сделал
-
foldr
над соседями, как предположили некоторые комментаторы. - поскольку порядок точек не имеет значения, я использую
Set
вместо списка, поэтому он семантически лучше подходит и быстрее загружается. - названы некоторые полезные промежуточные абстракции, такие как
Point
иneighbors
. - лучшая структура данных для среды обитания также была бы хорошей, так как списки линейное время для доступа, возможно, 2D
Data.Array
-но насколько эта функция заботится, все, что вам нужно-это функция индексацииPoint -> Bool
(вне границ и нулевое значение обрабатываются одинаково), поэтому я заменил параметр структуры данных самой функцией индексирования (это общее преобразование в FP).
мы видим, что также можно было бы абстрагироваться от neighbors
функция, а затем мы придем к очень общему обходу графа метод
traverseGraph :: (Ord a) => (a -> [a]) -> a -> Set.Set a
в терминах которого вы могли бы написать getConnectedArea
. Я рекомендую сделать это в образовательных целях-оставить как упражнение.
редактировать
вот пример того, как вызвать функцию в терминах (почти) вашей старой функции:
import Control.Monad ((<=<))
-- A couple helpers for indexing lists.
index :: Int -> [a] -> Maybe a
index _ [] = Nothing
index 0 (x:_) = x
index n (_:xs) = index (n-1) xs
index2 :: (Int,Int) -> [[a]] -> Maybe a
index2 (x,y) = index x <=< index y
-- index2 uses Maybe's monadic structure, and I think it's quite pretty.
-- But if you're not ready for that, you might prefer
index2' (x,y) xss
| Just xs <- index y xss = index x xs
| otherwise = Nothing
getConnectedArea' :: (Eq a) => [[a]] -> Point -> a -> [a]
getConnectedArea' habitat point nullValue = Set.toList $ getConnectedArea nonnull point
where
nonnull :: Point -> Bool
nonnull p = case index2 p habitat of
Nothing -> False
Just x -> x /= nullValue
OK Я попытаюсь упростить ваш код. Однако уже есть хорошие ответы, и поэтому я буду заниматься этим с немного более концептуальным подходом.
Я считаю, что вы могли бы выбрать лучшие типы данных. Например,Data.Matrix
кажется, обеспечивает идеальный тип данных вместо вашего [[generic_type]]
тип. Также для координат я бы не выбрал тип кортежа, так как тип кортежа существует для упаковки разных типов. Это функтор и экземпляры monad не очень полезны когда он выбран в качестве системы координат. Но так как кажется Data.Matrix
просто доволен кортежами в качестве координат, которые я сохраню.
OK ваш перефразированный код выглядит следующим образом;
import Data.Matrix
gca :: Matrix Int -> (Int, Int) -> Int -> [(Int,Int)]
gca fld crd nil = let nbs = [id, subtract 1, (+1)] >>= \f -> [id, subtract 1, (+1)]
>>= \g -> return (f,g)
>>= \(f,g) -> return ((f . fst) crd, (g . snd) crd)
in filter (\(x,y) -> fld ! (x,y) /= nil) nbs
*Main> gca (fromLists [[0,1,0],[1,1,1],[0,1,0],[1,0,0]]) (2,2) 0
[(2,2),(2,1),(2,3),(1,2),(3,2)]
первое, что нужно отметить, это Matrix
тип данных-index 1
на основе. Таким образом, у нас есть центральная точка в (2,2)
.
второе... у нас есть список из трех элементов функций, определенных как [id, subtract 1, (+1)]
. Содержащиеся в функции Num a => a -> a
тип и мне нужно, чтобы они определите окружающие пиксели заданной координаты, включая заданную координату. Таким образом, у нас есть линия, как если бы мы сделали;
[1,2,3] >>= \x -> [1,2,3] >>= \y -> return [x,y]
в результате [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]
что в нашем случае дало бы 2 комбинации всех функций вместо чисел 1,2 и 3.
который затем мы применяем к нашей заданной координате один за другим с каскадной инструкцией
>>= \[f,g] -> return ((f . fst) crd, (g . snd) crd)
, который дает все соседние координаты.
тогда его ничего больше чем фильтровать соседние фильтры, проверяя, не равны ли они nil
значение в матрице.