Reputation: 1589
Given a list [1,2,31,23,22,1,43]
Would like to use a higher order function to group elements [[1,2,31], [23,22,1], [43]]
. increasing / decreasing list, where the elements initially the elements are in increasing order and then in decreasing order.
groupIncDec :: Ord a => [a] -> [[a]]
Each element in the output list [[a]]
should be either all increasing or all decreasing, like in the example above, first element [1,2,31]
is all increasing, second [23,22,1]
all decreasing and because the there is a single element left and the previous state was all decreasing, [43]
can be classified as all increasing.
Could not figure out if/how to use groupBy
. Any hints would be appreciated.
Thanks.
[Edit: added some more description, which will hopefully clarify the question further.]
Upvotes: 1
Views: 532
Reputation: 50864
Here's another approach. For the moment, let's assume that the list has no adjacent equal elements, like your example. If the list lst
is compared with its own tail, element-by-element:
> let lst = [1,2,31,23,22,1,43]
> let dir = zipWith compare lst (tail lst)
> dir
[LT,LT,GT,GT,GT,LT]
then dir
represents whether adjacent elements are increasing or decreasing. If we were to repeat the head of this list:
> let dir' = head dir : dir
> dir'
[LT,LT,LT,GT,GT,GT,LT]
then dir'
corresponds to how the list [1,2,31,23,22,1,43]
should be grouped. By zipping dir'
with lst
and grouping by the dir'
elements, we get:
> import Data.Function -- for `on`
> let groups = groupBy ((==) `on` fst) (zip dir' lst)
> groups
[[(LT,1),(LT,2),(LT,31)],[(GT,23),(GT,22),(GT,1)],[(LT,43)]]
which we can filter to:
> map (map snd) groups
[[1,2,31],[23,22,1],[43]]
So, putting this all together, we get a higher-order solution:
groupIncDec' :: Ord a => [a] -> [[a]]
groupIncDec' lst =
let dir = zipWith compare lst (tail lst)
in map (map snd)
$ groupBy ((==) `on` fst)
$ zip (head dir : dir) lst
This doesn't work properly on lists with adjacent duplicate elements, but we can take such a list:
lst' = [1,2,31,31,23,22,22,1,43]
and group it:
group lst' = [[1],[2],[31,31],[23],[22,22],[1],[43]]
and then effectively apply the above algorithm before "ungrouping" it again to get the final result. That looks like this:
groupIncDec :: Ord a => [a] -> [[a]]
groupIncDec xs =
let ys = group xs
dir = zipWith (comparing head) ys (tail ys)
in map (concatMap snd)
$ groupBy ((==) `on` fst)
$ zip (head dir : dir) ys
which works like so:
> groupIncDec [1,2,31,23,22,1,43]
[[1,2,31],[23,22,1],[43]]
> groupIncDec [1,2,31,31,23,22,22,1,43]
[[1,2,31,31],[23,22,22,1],[43]]
Upvotes: 4
Reputation: 3818
groupBy
is meant to group by equality, not a general binary relation. For example:
Prelude Data.List> groupBy (<) [1,3,2]
[[1,3,2]]
it is a span of first element 1
(as 1<3
, 1<2
).
I came up with a solution of foldl
, but this is not a very clean solution.
groupIncDec :: Ord a => [a] -> [[a]]
groupIncDec = map reverse . reverse . foldl f []
where f [] x = [[x]]
f ([y]:ys) x = [x,y]:ys
f (ys@(y1:y2:_):yss) x = if x < y1 && y1 < y2 || x > y1 && y1 > y2
then (x:ys):yss
else [x]:ys:yss
f
is of type [[a]] -> a -> [[a]]
, i.e. the accumulation part.
Because Haskell List is good at dealing the head element (with help of :
), so the results are stored backward. Thus map reverse . reverse
is needed.
Upvotes: 2