Making a list into a list of lists by splitting on an element (and omitting that element)

I have a data Weather = Sunny | Cloudy | Rainy deriving (Show, Eq) datatype and my job is to group only the non-rainy days using a [Weather] -> [[Weather]] function.

Let's say that I have a list like this:

[Rainy, Cloudy, Sunny, Sunny, Cloudy, Rainy, Rainy, Sunny, Cloudy, Sunny, Cloudy, Rainy, Sunny], and I want to make it into a list of lists containing only the Sunny or Cloudy days like so:

[[Cloudy, Sunny, Sunny, Cloudy], [Sunny, Cloudy, Sunny, Cloudy], [Sunny]].

I am prohibited to use the splitOn function from Data.List.Split.

My attempt at solving this:

groupNotRainy :: [Weather] -> [[Weather]]
groupNotRainy [] = [[]]
groupNotRainy [x]
   | x /= Rainy = [[x]]
   | otherwise = [[]]
groupNotRainy [x,y]
   | x == Rainy = [[y]]
   | y == Rainy = [[x]]
   | otherwise = [[x,y]]
groupNotRainy (x:y:xs)
   | y == Rainy = [[x], groupNotRainy xs]

Which gives me a Couldn't match type ‘[Weather]’ with ‘Weather’ error.


Solution 1:

You were close to a solution. In order to typecheck, the end of your code should be changed like this:

groupNotRainy (x:y:xs)
   | y == Rainy  =  [x] : (groupNotRainy xs)
   | otherwise   =  ...TODO...

because the type of groupNotRainy xs is [[Weather]].

Your following patterns happen to overlap for an empty xs:

groupNotRainy [x,y]
groupNotRainy (x:y:xs)

so it is probably best to keep just the second one. There, you have 2 boolean values to test. I think the easiest/cleanest (if not shortest) way is just to enumerate the 4 cases:

groupNotRainy [] = []
groupNotRainy [x]
   | x /= Rainy  =  [[x]]
   | otherwise   =  []
groupNotRainy (x:y:xs)
   | x == Rainy && y == Rainy         =  groupNotRainy xs
   | x /= Rainy && y == Rainy         =  [x] : (groupNotRainy xs)
   | x == Rainy && y /= Rainy         =  groupNotRainy (y:xs)
   | otherwise  {- both non-rainy -}  =  let  yss = groupNotRainy (y:xs)
                                         in   (x : head yss) : tail yss

If both x and y are OK (non-rainy), one computes the result of the function for y:xs, and then prepends x to the first list in that (necessarily non-empty) result.

Please note the last clause is an otherwise. The compiler is not omniscient and would complain if it was just | (p x) && (p y).

Besides, it is probably more idiomatic to define a general purpose function:

grouper :: (a -> Bool) -> [a] -> [[a]]
grouper p []     = []
grouper p [x]
   | (p x)       =  [[x]]
   | otherwise   =  []
grouper p (x:y:xs)
   | (not $ p x)  &&  (not $ p y)   =  grouper p xs
   | (      p x)  &&  (not $ p y)   =  [x] : (grouper p xs)
   | (not $ p x)  &&  (      p y)   =  grouper p (y:xs)
   | otherwise  {- both x y OK -}   =  let  yss = grouper p (y:xs)  -- not empty
                                       in   (x : head yss) : tail yss

and then define groupNotRainy by currying, also known as “partial application”:

groupNotRainy :: [Weather] -> [[Weather]]
groupNotRainy = grouper (/= Rainy)