【问题标题】:Haskell: surprising behavior of "groupBy"Haskell:“groupBy”的令人惊讶的行为
【发布时间】:2009-08-22 16:27:24
【问题描述】:

我试图弄清楚库函数 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]]

我错过了什么?

【问题讨论】:

    标签: haskell combinators


    【解决方案1】:

    看看groupByghc实现:

    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 假设给定函数(第一个参数)测试相等,因此假设比较函数是 reflexivetransitive 和 对称(参见 equivalence relation)。这里的问题是 小于 关系不是自反的,也不是对称的。


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

    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
    

    【讨论】:

    【解决方案2】:

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

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

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

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

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

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

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

    现在它比较了所有元素的相等性。

    ...只是,你没有传递它预期的那种功能。

    简而言之,when it wants an equality test, give it an equality test

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        我只想指出,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
        

        【讨论】:

          猜你喜欢
          • 2019-10-05
          • 1970-01-01
          • 2021-08-20
          • 1970-01-01
          • 1970-01-01
          • 2012-07-27
          • 2015-10-19
          • 2012-08-11
          • 1970-01-01
          相关资源
          最近更新 更多