我试图弄清楚库函数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]]
我错过了什么?
答案 0 :(得分: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
假设给定函数(第一个参数)测试相等性,因此假设比较函数是 reflexive ,传递和对称(见equivalence relation)。这里的问题是 less-than 关系不是自反的,也不是对称的。
编辑:以下实施仅假定传递性:
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
答案 1 :(得分:9)
“&lt;”的事实不是一个平等的考验。
你可能会期待一些行为,因为你以不同的方式实现它,但这不是它所承诺的。
为什么输出它是一个合理的答案的例子是如果它扫过它,做
[1, 2, 3, 2, 4, 1, 5, 9] ->
[[1,2,3], [2,4], [1,5,9]]
现在有3组相等的元素。所以它检查它们中的任何一个是否实际上是相同的:
因为它知道每个组中的所有元素是相等的,所以它可以只查看每个元素中的第一个元素,1,2和1。
1&gt; 2?是!所以它合并了前两组。
1&gt; 1?没有!所以它留下了最后一组。
现在它将所有元素进行了比较。
......只是,你没有传递它预期的那种功能。
简而言之,when it wants an equality test, give it an equality test。
答案 2 :(得分:9)
问题是Haskell Report中groupBy
的引用实现将元素与第一个元素进行了比较,因此这些组并没有严格增加(它们只需要比第一个元素都大)。您想要的是groupBy
的一个版本,用于测试相邻的元素,例如实现here。
答案 3 :(得分: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