Haskell:用于对递增递减列表进行分组的高阶函数

时间:2017-09-13 22:04:58

标签: haskell

给定列表{% tabs %} {% tab id = "tab-1" title = "Tab One" %} Content in Tab 1 {% \tab %} {% tab id = "tab-2" title = "Tab Two" %} Content in Tab 2 {% \tab %} {% \tabs %} 想要使用更高阶函数对元素[1,2,31,23,22,1,43]进行分组。增加/减少列表,其中元素最初是元素按递增顺序然后按降序排列。

[[1,2,31], [23,22,1], [43]]

输出列表groupIncDec :: Ord a => [a] -> [[a]] 中的每个元素应该全部增加或全部减少,如上例所示,第一个元素[[a]]全部增加,第二个[1,2,31]全部减少,因为剩下一个元素,前一个状态都在减少,[23,22,1]可以归类为全部增加。

无法弄清楚是否/如何使用[43]。任何提示将不胜感激。

感谢。

[编辑:添加了更多描述,希望能进一步澄清这个问题。]

2 个答案:

答案 0 :(得分:4)

这是另一种方法。暂时,让我们假设列表没有相邻的相等元素,就像你的例子一样。如果将列表lst与其自己的尾部进行比较,则逐个元素:

> let lst = [1,2,31,23,22,1,43]
> let dir = zipWith compare lst (tail lst)
> dir
[LT,LT,GT,GT,GT,LT]

然后dir表示相邻元素是增加还是减少。如果我们重复这个清单的头部:

> let dir' = head dir : dir
> dir'
[LT,LT,LT,GT,GT,GT,LT]

然后dir'对应列表[1,2,31,23,22,1,43]的分组方式。通过使用dir'压缩lst并按dir'元素进行分组,我们得到:

> import Data.Function  -- for `on`
> let groups = groupBy ((==) `on` fst) (zip dir' lst)
> groups 
[[(LT,1),(LT,2),(LT,31)],[(GT,23),(GT,22),(GT,1)],[(LT,43)]]

我们可以过滤到:

> map (map snd) groups
[[1,2,31],[23,22,1],[43]]

所以,把这一切放在一起,我们得到一个更高阶的解决方案:

groupIncDec' :: Ord a => [a] -> [[a]]
groupIncDec' lst =
  let dir = zipWith compare lst (tail lst)
  in  map (map snd)
      $ groupBy ((==) `on` fst)
      $ zip (head dir : dir) lst

这在具有相邻重复元素的列表上无法正常工作,但我们可以采用这样的列表:

lst' = [1,2,31,31,23,22,22,1,43]

并将其分组:

group lst' = [[1],[2],[31,31],[23],[22,22],[1],[43]]

然后在" ungrouping"之前有效地应用上述算法它再次获得最终结果。看起来像这样:

groupIncDec :: Ord a => [a] -> [[a]]
groupIncDec xs =
  let ys = group xs
      dir = zipWith (comparing head) ys (tail ys)
  in  map (concatMap snd)
      $ groupBy ((==) `on` fst)
      $ zip (head dir : dir) ys

的工作原理如下:

> groupIncDec [1,2,31,23,22,1,43]
[[1,2,31],[23,22,1],[43]]
> groupIncDec [1,2,31,31,23,22,22,1,43]
[[1,2,31,31],[23,22,22,1],[43]]

答案 1 :(得分:2)

groupBy意味着按相等分组,而不是一般的二元关系。例如:

Prelude Data.List> groupBy (<) [1,3,2]
[[1,3,2]]

它是第一个元素1的范围(1<31<2)。

我提出了foldl的解决方案,但这不是一个非常干净的解决方案。

groupIncDec :: Ord a => [a] -> [[a]]
groupIncDec = map reverse . reverse . foldl f []
  where f []                 x = [[x]]
        f ([y]:ys)           x = [x,y]:ys
        f (ys@(y1:y2:_):yss) x = if x < y1 && y1 < y2 || x > y1 && y1 > y2
                                 then (x:ys):yss
                                 else [x]:ys:yss

f属于[[a]] -> a -> [[a]]类型,即累积部分。

因为Haskell List擅长处理head元素(在:的帮助下),所以结果存储在后面。因此需要map reverse . reverse

  1. http://hackage.haskell.org/package/base-4.10.0.0/docs/Data-List.html#v:groupBy
  2. http://hackage.haskell.org/package/base-4.10.0.0/docs/Data-List.html#v:foldl