简化haskell函数

时间:2017-09-03 16:37:03

标签: haskell

我真的在与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)]

4 个答案:

答案 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) ]

我们在顶层绑定habitatnullValue一次(澄清递归工作正在做什么),在谓词中删除间接,使用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而不是列表,因此它在语义上更合适,启动速度更快。
  • 命名了一些有用的中间抽象,例如Pointneighbors
  • 更好的栖息地数据结构也很好,因为列表是线性访问时间,可能是2D 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值来过滤相邻过滤器。