Ken
Ken

Reputation: 287

Break a list into sublists of the same length in Haskell

I am trying to write a Haskell code which takes in a list and return a list of list. When I do that as the following code, I am getting "non-exhaustive patterns in function reGroup"

reGroup :: [[Int]] -> [Int] -> [[Int]]
reGroup [[]] [] = [[]]
reGroup [[]] xs = reGroup [(take 3 xs)] (drop 3 xs)
reGroup [[a]] [] = [[a]]
reGroup [[a]] xs = reGroup [[a], (take 3 xs)] (drop 3 xs)

-- calling the reGroup function from another function as follow
reGroup [[]] [1,2,3,4,5,6,7,8,9]

What i want is [1,2,3,4,5,6,7,8,9] -> [[1,2,3], [4,5,6], [7,8,9]]. What am I doing wrong or can someone show me an easy way to it?

Upvotes: 0

Views: 824

Answers (5)

Philip JF
Philip JF

Reputation: 28539

It is probably easier to try to do this without the accumulator (the first argument). Then we would have

groupThree :: [a] -> [[a]] --why only work with Ints?
--if the list begins with three elements, stick them in a group
--then group the remainder of the list
groupThree (a:b:c:more) = [a,b,c]:groupThree more
--grouping an empty list gives you an empty list
groupThree [] = []
--if we have some number of elements less than three
--we can just stick them in a list
groupThree other = [other]

Or using drop and take

groupThree :: [a] -> [[a]]
groupThree [] = []
groupThree ls = (take 3 ls):groupThree (drop 3 ls)

which does exactly the same thing.

The reason your code does not work is that

reGroup [xs,ls] y

does not match with any of your cases--you only have code to handle the first argument being a list of exactly one element, that element being the empty list or a list with exactly one element.

The correct use of an accumulator would be

reGroup back [] = back
reGroup back ls = reGroup (back ++ [take 3 ls]) (drop 3 ls)

unfortunately, this is very inefficient since you are appending to the end of a list (taking time proportional to the length of that list...modulo lazieness). Instead, you should use

reGroup back [] = reverse back
reGroup back ls = reGroup ((take 3 ls):back) (drop 3 ls)

although I like the version without an accumulator better since it is lazier (and so can handle infinite lists).

Upvotes: 7

The Internet
The Internet

Reputation: 8103

take3 :: [a] -> [[a]]
take3 [] = []
take3 (x:[]) = [[x]]
take3 (x:y:[]) = [[x,y]]
take3 (x:y:z:xs) = [[x,y,z]] ++ take3 xs

Upvotes: 0

Codey McCodeface
Codey McCodeface

Reputation: 3028

Changing your code a little to this

reGroup :: [[Int]] -> [Int] -> [[Int]]; 
reGroup [[]] [] = []; 
reGroup a [] = a; 
reGroup [[]] xs = reGroup [(take 3 xs)] (drop 3 xs); 
reGroup a xs = a ++ reGroup [(take 3 xs)] (drop 3 xs); 

does the job.

Upvotes: 1

kaan
kaan

Reputation: 796

 [[a]]

is only a list with a list with one element, like [[1]].

So after one recursion you get from

reGroup [[]] [1,2,3,4,5,6,7,8,9]

to

reGroup [[1,2,3]] [4,5,6,7,8,9]

but for this case (a list with a list with 3 elements) is no pattern defined.

Upvotes: 4

paul
paul

Reputation: 1705

Try this:

reGroup xs n =
    if drop n xs == []
    then [take n xs]
    else [take n xs] ++ (reGroup (drop n xs) n)

Probably not the most efficient, but it's a start.

It outputs:

> reGroup [1..9]
[[1,2,3],[4,5,6],[7,8,9]]
> reGroup [1..10]
[[1,2,3],[4,5,6],[7,8,9],[10]]

And the reason you're getting that error is because you haven't covered all the patterns the function can match. Try throwing in a _ or two for your base case(s).

Upvotes: 2

Related Questions