My goal is to take an input like:
[(8, P1), (8, P2), (10, P3)]
And turn it into something like:
[(8, [P1, P2]), (10, P3)]
Given that numbers like 8
and 10
are the datatype Time
(wrapping and Int
) and P1
and P2
are the datatypes Person
(which wraps a String
). This is what I did so far:
groupTogether :: [(Time, Person)] -> [(Time, Person)]
groupTogether [] = []
groupTogether ((x, y) : (a, b) : ks)
| x == a = (x, (y : (b))) : groupTogether ks
| otherwise = (x, y) : groupTogether ((a, b) : ks)
And It "kinda" works but usually the outputs are things like (8, Name1Name2)
instead of (8, [Name1, Name1])
. And I just don't know how to write what the function should do once there is only one element in the list. It says that there is an "exaustive pattern" missing. What am I doing wrong? If I try to put the elements togheter using :
the code won't run.
CodePudding user response:
How about this? (Note that the Foldable t
can be though of as a list)
% ghci
λ> :m Data.Map
λ> data P = P1 | P2 | P3 deriving (Eq, Ord, Show)
λ> let input = [(8, P1), (8, P2), (10, P3)] :: [(Int, P)]
λ> :type Prelude.foldl
_ :: Foldable t => (b -> a -> b) -> b -> t a -> b
λ> :type Prelude.foldl (\mp (k, p) -> insertWith (<>) k [p] mp)
_ :: (Foldable t, Ord k) => Map k [a] -> t (k, a) -> Map k [a]
λ> :type Prelude.foldl (\mp (k, p) -> insertWith (<>) k [p] mp) mempty
_ :: (Foldable t, Ord k) => t (k, a) -> Map k [a]
λ> :type Prelude.foldl (\mp (k, p) -> insertWith (<>) k [p] mp) mempty input
_ :: Map Int [P]
λ> Prelude.foldl (\mp (k, p) -> insertWith (<>) k [p] mp) mempty input
fromList [(8,[P2,P1]),(10,[P3])]
λ> toList (Prelude.foldl (\mp (k, p) -> insertWith (<>) k [p] mp) mempty input)
[(8,[P2,P1]),(10,[P3])]
CodePudding user response:
What about this?
import Data.List
import Data.Function
data P = P1 | P2 | P3 deriving Show
x = [(8, P1), (8, P2), (10, P3)]
fun list = let lists = groupBy ((==) `on` fst) x -- [[(8,P1),(8,P2)],[(10,P3)]]
nums = map (fst . head) lists -- [8,10]
ps = (map . map) snd lists -- [[P1,P2],[P3]]
in zip nums ps -- [(8,[P1,P2]),(10,[P3])]
In lists
I've grouped the items by equality on the number, in nums
I've extracted the number which is common to all items in each group, in ps
I've extracted those P*
things, whatever they are, via (map . map) . snd
which applies snd
through two functorial layers.
Note that if in general the items in x
with equal number are not necessarily adjacent (in your example they are), you might want to sort
the list before using groupBy
, using an appropriate sorting algorithm, as suggested in the comments below.
As regards your desired output
[(8, [P1, P2]), (10, P3)]
this is simply not possible to obtain, because in Haskell all the elemnts of a list have the same type, but (8, [P1, P2])
and (10, P3)
have to different types, namely (Time, [Person])
and (Time, Person)
. This was already implied by some of the comments under your question, but you haven't corrected your question yet (you should). In my answer I've assumed you meant to write [(8, [P1, P2]), (10, [P3])]
.
As regards your attempt
groupTogether :: [(Time, Person)] -> [(Time, Person)]
groupTogether [] = []
groupTogether ((x, y) : (a, b) : ks)
| x == a = (x, (y : (b))) : groupTogether ks
| otherwise = (x, y) : groupTogether ((a, b) : ks)
there are several syntactic problems with it:
- the signature is wrong, as it signals that the output has the same type of the input; this is certainly possible, but does not reflect the (corrected) desired output; probably you meant to write
groupTogether :: [(Time, Person)] -> [(Time, [Person])]
groupTogether [] = []
handles an empty list input, whereasgroupTogether ((x, y) : (a, b) : ks)
handles a two-elements-or-more list input, but there's no way to deal with a singleton list, which is exactly what the "exaustive pattern missing" error alludes to;- since
y
andb
have the same type,Person
, the expressiony : (b)
is incorrect because it's equivalent toy:b
, and:
wants ana
on the left and a[a]
on the right; you might want to change that toy:[b]
, or maybe[y,b]
; - in a similar way,
y
in theotherwise
case should be[y]
.
However, even if you apply the corrections above, there would still be something that is not quite right. Look at this:
groupTogether ((x, y) : (a, b) : ks)
| x == a = (x, y : [b]) : groupTogether ks
You're pattern matching the first two pair in the list and putting them in one single pair, but what if the first pair in ks
has another a
as its first element? You're leaving it in ks
, not grouping it with the other two. This is either wrong or not clear from the text of your question, in which case you should improve it.