按列表的第一个元素对列表进行分组

时间:2018-12-10 13:29:10

标签: haskell group-by

我想通过解决ProjectEuler问题来在业余时间学习Haskell,当我遇到问题5时,我最终尝试按列表的第一个元素对列表进行分组。下面是我想要的行为的一个示例:

输入:

[[2], [3], [2, 2], [5], [7], [3, 3]]`

输出:

[[[2], [2, 2]], [[3], [3, 3]], [[5]], [[7]]]

为此,我编写了以下代码

import Data.List (groupBy)

factors = [[2], [3], [2, 2], [5], [7], [3, 3]]
groupedFactors = 
    let comp x y = (head x) == (head y)
    in groupBy comp factors

但是,以上代码的结果为以下列表

[[[2]],[[3]],[[2,2]],[[5]],[[7]],[[3,3]]]

我试图调试它,所以我用GHCI编写了以下代码:

factors = [[2], [3], [2, 2], [5], [7], [3, 3]]
comp x y = (head x) == (head y)
comp (factors!!0) (factors!!2)

哪个产生了True,比较了第四个元素产生了False,如预期的那样。

最后,我想说我当然可以用另一种方法来解决问题,但是我很想知道这里发生了什么。对我来说,理解为什么会出现这种行为比解决问题的方法更为重要(尽管我也不会拒绝解决方案)。

1 个答案:

答案 0 :(得分:4)

首先让我们注意,与编写显式命名的comp相比,使用on combinator更容易:

Prelude Data.List Data.Function> groupBy ((==)`on`head) [[2], [2,2], [3], [3,5]] 
[[[2],[2,2]],[[3],[3,5]]]

现在,group *函数始终仅将列表中已经相邻的元素聚集在一起。

Prelude Data.List Data.Function> group "aaabac"
["aaa","b","a","c"]

原因是,这可以在 O n )时间内懒惰地完成,而仅在给定相等谓词的情况下从列表中的任何位置收集元素都是O ( n ²)。为了使高效有效,这是首先对列表进行排序的常用方法,该方法将分组候选仅以O( n ·log < em> n )时间。

Prelude Data.List Data.Function> group $ sort "aaabac"
["aaaa","b","c"]

以您的示例为例,

> groupBy ((==)`on`head) $ sortBy (compare`on`head) [[2], [3], [2, 2], [5], [7], [3, 3]]
[[[2],[2,2]],[[3],[3,3]],[[5]],[[7]]]

可以使用sortOn函数简化此过程,该函数已经内置了预映射功能:

> groupBy ((==)`on`head) $ sortOn head [[2], [3], [2, 2], [5], [7], [3, 3]]
[[[2],[2,2]],[[3],[3,3]],[[5]],[[7]]]

甚至更短于extra包中的groupSortOn,它具有全部功能:

Prelude Data.List.Extra> groupSortOn head [[2], [3], [2, 2], [5], [7], [3, 3]]
[[[2],[2,2]],[[3],[3,3]],[[5]],[[7]]]

由于我通常不鼓励使用head,因此建议您考虑使用take 1代替。