Reputation: 2100
I was going through groupBy in Haskell and the way I understand it is that the groupBy function takes a List and a condition and groups the element based on the specified condition.
let value = [-3,-5,-1,1,1,1,2,3,-1,-2]
groupBy (\x y -> (x > 0) == (y > 0)) value
It works fine, but why should we give two variables in the lambda function? Why does "groupBy (>0) value" not work? Also should both the conditions be the same? What will happen if they are different. Please explain with examples.
Upvotes: 2
Views: 1051
Reputation: 34378
Lignum's answer explains why the groupBy
predicate takes two arguments. To put it in another way, groupBy
sets up boundaries, and you need two sides to be able to define a boundary.
Also should both the conditions be the same? What will happen if they are different.
Yes, they should. As the Data.List
documentation notes...
The predicate is assumed to define an equivalence.
In other words, if p :: a -> a -> Bool
is the predicate you pass to groupBy
, then all of...
p x x = True
p x y = p y x
p x y && p y z
then p x z
... should hold. Weird things happen if that isn't the case. For instance, we might at first think that...
groupBy (\x y -> (x > 0) == (y < 0)) [-1,2,-3,-4,2,1,2,-3,1,1,2]
... would produce groups composed of alternating above zero and below zero elements. Instead, we get bogus results:
GHCi> groupBy (\x y -> (x > 0) == (y < 0)) [-1,2,-3,-4,2,1,2,-3,1,1,2]
[[-1,2],[-3],[-4,2,1,2],[-3,1,1,2]]
(The reason it goes wrong is that the guarantee of the predicate being an equivalence is exploited by implementing groupBy
in terms of span
.)
Finally, there is a nicer way of writing your groupBy
predicate: using on
from Data.Function
:
GHCi> groupBy ((==) `on` (> 0)) value
[[-3,-5,-1],[1,1,1,2,3],[-1,-2]]
(g `on` f) x y = g (f x) (f y)
. You might even define:
-- A more general type: Eq b => (a -> b) -> [a] -> [[a]]
groupBySingle :: (a -> Bool) -> [a] -> [[a]]
groupBySingle p = groupBy ((==) `on` p)
Upvotes: 5
Reputation: 7360
groupBy
operates by checking whether the current element belongs to the group based on the previous one.
The behaviour you want is best descibed by a separate function, repeatedly applying span
and break
:
myFunc :: (a -> Bool) -> [a] -> [[a]]
myFunc _ [] = []
myFunc p xs = satp : notsatp : myFunc p rest
where
(satp, rest') = span p xs
(notsatp, rest) = break p rest'
Upvotes: 2
Reputation: 1096
[...] the way I understand it is that the groupBy function takes a List and a condition and groups the element based on the specified condition.
Not entirely, it groups adjacent elements based on the specified condition. Notice how your code evaluates to this list:
[[-3,-5,-1],[1,1,1,2,3],[-1,-2]]
instead of this list:
[[-3,-5,-1,-1,-2],[1,1,1,2,3]]
Therefore, the lambda expression that you pass to groupBy
determines whether two adjacent elements should be in a group together.
Upvotes: 2