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)