15

我试图弄清楚库函数 groupBy(来自 Data.List)的行为,它声称通过作为第一个参数传入的“平等测试”函数对列表的元素进行分组。类型签名表明相等测试只需要有类型

(a -> a -> Bool)

但是,当我在 GHCi 6.6 中使用 (<) 作为“平等测试”时,结果不是我所期望的:

ghci> groupBy (<) [1, 2, 3, 2, 4, 1, 5, 9]
[[1,2,3,2,4],[1,5,9]]

相反,我希望运行数量严格增加,如下所示:

[[1,2,3],[2,4],[1,5,9]]

我错过了什么?

4

4 回答 4

34

看看groupBy的ghc实现:

groupBy                 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []           =  []
groupBy eq (x:xs)       =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs

现在比较这两个输出:

Prelude List> groupBy (<) [1, 2, 3, 2, 4, 1, 5, 9]
[[1,2,3,2,4],[1,5,9]]
Prelude List> groupBy (<) [8, 2, 3, 2, 4, 1, 5, 9]
[[8],[2,3],[2,4],[1,5,9]]

简而言之,发生的情况是:groupBy假设给定函数(第一个参数)测试相等,因此假设比较函数是自反的、传递的和对称的(参见等价关系)。这里的问题是小于关系不是自反的,也不是对称的。


编辑:以下实现仅假设传递性:

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' _   []                        = []
groupBy' _   [x]                       = [[x]]
groupBy' cmp (x:xs@(x':_)) | cmp x x'  = (x:y):ys
                           | otherwise = [x]:r
  where r@(y:ys) = groupBy' cmp xs
于 2009-08-22T16:34:37.880 回答
9

“<”不是相等测试的事实。

您可能会期待一些行为,因为您会以不同的方式实现它,但这不是它所承诺的。

为什么它输出的结果是一个合理的答案的一个例子是,如果它扫过它,做

[1, 2, 3, 2, 4, 1, 5, 9] ->
[[1,2,3], [2,4], [1,5,9]]

现在有 3 组相等的元素。所以它检查它们中的任何一个是否实际上是相同的:

因为它知道每个组中的所有元素都是相等的,所以它可以只查看每个组中的第一个元素,1、2 和 1。

1 > 2?是的!所以它合并了前两组。

1 > 1?不!所以它离开了最后一组。

现在它比较了所有元素是否相等。

...只是,您没有通过它预期的那种功能。

简而言之,当它想要一个相等测试时,给它一个相等测试

于 2009-08-22T16:36:04.757 回答
9

问题是 Haskell 报告中的参考实现groupBy将元素与第一个元素进行比较,因此组并没有严格增加(它们只需要都大于第一个元素)。相反,您想要的是对相邻元素groupBy进行测试的版本,例如此处的实现。

于 2009-08-22T18:50:28.650 回答
0

我只想指出 groupBy 函数还需要在应用之前对您的列表进行排序。

例如:

equalityOp :: (a, b1) -> (a, b2) -> Bool
equalityOp x y = fst x == fst y

testData = [(1, 2), (1, 4), (2, 3)]

correctAnswer = groupBy equalityOp testData == [[(1, 2), (1, 4)], [(2, 3)]]

otherTestData = [(1, 2), (2, 3), (1, 4)]

incorrectAnswer = groupBy equalityOp otherTestData == [[(1, 2)], [(2, 3)], [(1, 4)]]

出现这种行为是因为 groupBy 在其定义中使用了 span。为了获得不依赖于我们以任何特定顺序拥有基础列表的合理行为,我们可以定义一个函数:

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' eq []     = []
groupBy' eq (x:xs) = (x:similarResults) : (groupBy' eq differentResults)
    where similarResults   = filter (eq x) xs
          differentResults = filter (not . eq x) xs
于 2018-09-07T20:07:40.497 回答