Home > Back-end >  Haskell group elements in the list and concatenate different adjacent element together
Haskell group elements in the list and concatenate different adjacent element together

Time:06-09

The group function in Data.List can group the same element in a list Input:

import Data.List(group)
group "mississippi time"

Output:

["m","i","ss","i","ss","i","pp","i"," ","t","i","m","e"]

How to modify the group function to group the different adjacent elements together?

Expect:

["mi","ss","i","ss","i","pp","i time"]

Below is my WRONG solution.

I try to group the same elements and then group the different elements. When grouping different elements, I also check the length of each element. Since after groupSame, different elements are left alone.

-- |
-- >>> groupSame "mississippi time"
-- ["m","i","ss","i","ss","i","pp","i"," ","t","i","m","e"]
groupSame :: String -> [String]
groupSame [] = []
groupSame (x:xs) = (x : takeWhile (== x) xs) : groupSame (dropWhile (== x) xs)
-- >>> groupDiff ["m","i","ss","i","ss","i","pp","i"," ","t","i","m","e"]
-- ["mi","ss","i","ss","i","pp","i time"]
groupDiff :: [String] -> [String]
groupDiff [] = []
groupDiff [x] = [x]
groupDiff (x:y:xs)
 |length x > 1 && length y > 1 =  x:y : groupDiff xs 
 |length x > 1 && length y ==1 =  x: groupDiff (y:xs)
 |length x ==1 && length y > 1 =  x:y: groupDiff xs
 |length x ==1 && length y ==1 =  (x  y): groupDiff xs

The expected output is above the groupDiff signature. However I got:

["mi","ss","i","ss","i","pp","i"," t","im","e"]

CodePudding user response:

What about this?

 map concat $ groupBy (\x y -> all ((== 1) . length) [x, y]) $ group "mississippi time"

I guess the lambda can be shortened somehow.

I guess (== 1) . length could be named isSingleton.

And, it could be implemented as null . tail to save some parenthesis, fwiw:

 map concat $ groupBy (\x y -> all (null . tail) [x, y]) $ group "mississippi time"

With some more playing, you can even get rid of variables x and y and write it in point-free style, provided you write an alternative of all for pairs,

all' p (a, b) = p a && p b
map concat $ groupBy (curry $ all' (null . tail)) $ group "mississippi time"

but this is likely less readable.


You need import Data.List (groupBy) too.


Somebody else explained to you in a comment why your solution does not work.

My point, however, is that you shouldn't reach for such complicated solutions in the first place.

Hand-made recursion is a powerful tool, but for such a simple tast is just too much powerful; indeed, you get lost into details.

If you know that you just want to group things in some way, then reach out for functions that are for grouping, like group and groupBy, and see how you can combine them together to get what you want.

That's how I crafted my solution (which, performance-wise, might be not optimal), by just writing down what I wanted to do,

  • group letters by equality (thus obtaining strings of different lengths)
  • group together the adjacent strings that have length 1
  • concatenate the strings in each of the groups

and then extracting the functions I need from the key parts of my plan:

  • group by equality: that's what group does
  • group strings that have length 1: groupBy allows grouping given a predicate other than equality, length can compute the length, and (== 1) is for checking if a number is 1
  • concatenate the strings: concat does just that (for any list, not just strings, btw)
  • in each of the groups: that means I have to map on the list of groups.

Given the above, I got here:

 map concat $ groupBy (\x y -> doTheyGoTogether) $ group "mississippi time"

where I knew doTheyGoTogether would use (== 1) . length somehow. Not a huge leap to understand that (== 1) . length should be true for all of x and y: doTheyGoTogether = all ((== 1) . length).


P.S: Hoogle is your friend.

  • Related