我想通过解决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
,如预期的那样。
最后,我想说我当然可以用另一种方法来解决问题,但是我很想知道这里发生了什么。对我来说,理解为什么会出现这种行为比解决问题的方法更为重要(尽管我也不会拒绝解决方案)。
答案 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
代替。