我真的在与Haskell atm挣扎。
我花了将近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
函数得到[[generic_type]](表示横向地图),并搜索不等于给定nullValue的点周围的完全连通区域。
例如:
如果函数被调用如下:
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
代表地图(如谷歌地图)。从点(坐标)(1,1)开始我想得到与给定点形成连通区域的元素的所有坐标。
因此结果应为:
0 1 0
1 1 1
0 1 0
1 0 0
相应的返回值(粗体1的坐标列表):
[(2,1),(0,1),(1,2),(1,0),(1,1)]
答案 0 :(得分:8)
一个小的变化是您可以对变量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
而不是通话。现在可以对&#34; new&#34;重复此技术。内心的呼唤。 (请注意,您应该选择比foo
更具描述性的名称。也许down
?)
请注意,not (x >= y)
与x < y
相同。使用它来简化所有条件。由于这些条件测试一个点是否在一个边界矩形内,因此大部分逻辑可以考虑到函数isIn :: (Int, Int) -> (Int, Int) -> (Int, Int) -> Bool
,这将使get_connected_area
更具可读性。
答案 1 :(得分:5)
这将是我第一次快速浏览函数,以及可能通过代码审查的最小值(仅限于样式):
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(在函数应用程序发生的地方隐藏起来) ,将generic_type
替换为a
(在这里使用嘈杂变量实际上会产生与您预期的相反的效果;我最终会试图找出您在尝试调用时会调用的特殊语义有趣的是,类型并不重要(只要可以比较相等))。
此时我们可以做很多事情:
!!
和length
)和集合(elem
)的渐近性,并使用正确的数组和改为设置数据结构... = area
子句area
(使我们的搜索变得非常懒惰/&#34;高效&#34;)? 答案 2 :(得分:2)
这是我的看法:
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
。Data.Array
- 但就此功能而言,您只需要一个索引函数{{1 (超出边界和空值的处理方式相同),所以我用索引函数本身替换了数据结构参数(这是FP中的常见转换)。 我们可以看到,也可以抽象出Point -> Bool
函数,然后我们会得到一个非常通用的图遍历方法
neighbors
就你可以写traverseGraph :: (Ord a) => (a -> [a]) -> a -> Set.Set a
而言。我建议这样做是出于教育目的 - 留作练习。
修改强>
以下是一个如何根据(几乎)旧函数调用函数的示例:
getConnectedArea
答案 3 :(得分:1)
好的,我会尝试简化您的代码。然而,已经有了很好的答案,这就是为什么我会采用略微更概念化的方法解决这个问题。
我相信你可以选择更好的数据类型。例如,Data.Matrix
似乎在[[generic_type]]
类型的位置提供了理想的数据类型。另外对于坐标,我不会选择元组类型,因为元组类型可用于打包不同类型。当它被选为坐标系时,它的仿函数和monad实例不是很有用。然而,因为似乎Data.Matrix
对元组作为坐标很满意,我将保留它们。
好的,你的改写代码如下;
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
数据类型是基于索引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)
产生所有相邻坐标。
然后它只是通过检查它们是否不等于out矩阵中的nil
值来过滤相邻过滤器。