【问题标题】:Strange behavior of groupBygroupBy 的奇怪行为
【发布时间】:2021-07-31 10:09:24
【问题描述】:

我想知道为什么 groupBy 的以下调用不起作用:我的谓词是 x < y,所以我希望 [1, 6] 是一个组,但是 Haskell 将 [1, 6, 4, 2] 放入一个组中。

Prelude Data.List> groupBy (\x y -> x < y) [8,5,3,2,1,6,4,2]
[[8],[5],[3],[2],[1,6,4,2]]

更奇怪的是,当我将最后一个数字更改为 -2 时,我期望的行为与上面的示例相同。也就是说,由于 2 和 -2 都小于 4,我希望在结果中 [1, 6, 4, -2] 将组成一个组。但是,这一次,Haskell 将 -2 设置为一个组。

Prelude Data.List> groupBy (\x y -> x < y) [8,5,3,2,1,6,4,-2]
[[8],[5],[3],[2],[1,6,4],[-2]]

是不是我对groupBy的理解有误?

【问题讨论】:

  • groupBy 旨在与 等价关系 一起使用,特别是在groupBy p 中,您应该使用p x y ≡ p y x。但是,我很惊讶这实际上是一个要求...
  • 我的意思是,这不是必需的。你可以将任何你想要的函数传递给groupBy。但是,如果您使用令人惊讶的相等谓词,您可能会得到一组令人惊讶的组。
  • @leftaroundabout @amalloy 谢谢。我不知道groupBy“需要”相等关系。不过,知道为什么 -2 的处理方式与 2 不同会很有趣
  • x 始终是所构造组的第一个项。

标签: haskell


【解决方案1】:

groupBy 的实现中,x 始终是子列表的第一个 项。确实,groupBy 被实现为:

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

尤其是span (eq x) 在这里很重要,因为x 将是新组的第一项。

因为x 因此不是列表中的前一个值。如果我们因此运行groupBy 和列表[5, 3, 2, 1, 6, 4, -2],我们得到:

list current list x=? check with outcome
[5,3,2,1,6,4,-2] [8] 8 / /
[5,3,2,1,6,4,-2] [8] 8 5 False
[3,2,1,6,4,-2] [5] 5 / /
[3,2,1,6,4,-2] [5] 5 3 False
[3,2,1,6,4,-2] [3] 3 / /
[2,1,6,4,-2] [3] 3 2 False
[2,1,6,4,-2] [2] 2 1 False
[1,6,4,-2] [2] 2 / /
[1,6,4,-2] [2] 2 1 False
[6,4,-2] [1] 1 / /
[4,-2] [1,6] 1 6 True
[-2] [1,6,4] 1 4 True
[] [-2] -2 / /

尤其是我们比较x=1y=4 的情况很重要。如果x 只是之前的值,我们应该开始生成一个新列表,但是由于x 是列表的第一项,所以情况并非如此。

通常你应该只使用equivalence relation ~ [wiki],这样的关系是:

  1. 自反:所以x ~ x 是真的;
  2. 对称:所以x ~ y 当且仅当y ~ x;和
  3. 传递:所以x ~ yy ~ z 暗示x ~ z

你的等价关系不是自反的,也不是对称的。因此,这不是groupBy 一起使用的有效函数。

【讨论】:

  • 这个答案将大大升级,如果你说OP在考虑什么功能,比较相邻值以放置组边界的功能
【解决方案2】:

groupBy p l 的概念定义是它产生l 的子列表,这样对于l 中的每个xs,您都有

all (==True) [p x y | x<-xs, y<-xs]

IOW,每个子列表都应该是p等价类的一部分。只有当p等价关系时,这个概念才有意义。特别是,您需要p x y ≡ p y x,并且定义等式还假设p x x 始终为真。

标准库中的实现非常清楚地表明了这个想法:结果中的每个x:ys 列表都有ysspan 定义的元素,这些元素通过关系等效于x。所以在你的情况下,你得到1:[6,4,2],其中6,4,2都大于1

显然,groupBy 实际上并没有检查 p x y 的结果列表中的所有元素对,所以这只有在 p 确实是等价关系时才有意义。

您对这个想法的期望——而且 IMO 这并非不合理——是仅对于所有x,y 使得xy 的左邻居,您希望p x y 持有。这通常是一个较弱的条件,但如果p 是等价关系,那么它实际上暗示了原始条件,因为这样的关系也是传递。所以也许实现实际上应该是

groupBy' :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy' _ [] = []
groupBy' _ (x:l) = (x:xs) : zss
 where (xs,zss) = case l of
        [] -> ([],[])
        zs@(y:_)
         -> let ys:zss' = groupBy' p zs
            in if p x y then (ys, zss')
                        else ([], ys:zss')

(这可以简化一点,但不会像旧实现那样懒惰。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-25
    • 1970-01-01
    • 2018-11-19
    • 1970-01-01
    • 1970-01-01
    • 2019-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多