I'm going to find a solution to this problem:
Splitting given list to sublist with given sub-list length and skip length, for example:
groupEvery 3 1 [1..6] = [[1,2,3], [2,3,4], [3,4,5], [4,5,6]]
groupEvery 3 2 "abcdefghij" = [ "abc", "cde", "efg", "ghi", "ij"]
The groupEvery n p l
args details are:
- n is length of sub-list: Int
- f is length of skip: Int
- xs is input list: [a]
Here is my solution:
groupEvery :: Int -> Int -> [a] -> [[a]] -> [[a]]
groupEvery n p xs acc
| length xs <= n = acc [xs]
| otherwise = groupEvery n p dropped (acc [segment])
where
dropped = drop p xs
segment = take n xs
But this solution is not lazy, for example take 3 Lib2.groupEvery 3 1 [1..] []
never finished.
My question is about
- Better solutions for the
groupEvery
function ? - How we can write recursive lazy function that accumulate some data ? In other words is there any structure for this kind of recursive functions that accumulate results and are lazy ?
CodePudding user response:
The main problem is that you use an accumulator and only return the list when you walked over the entire input list. Furthermore length l
will walk through the entire list. You don't need to calculate the length. This is also inefficient: if the list is long it will each time make a linear run.
You can pattern match on the list and for an empty list return an empty list and for a non-empty list yield the take n xs
and recurse with drop p xs
.
groupEvery :: Int -> Int -> [a] -> [[a]]
groupEvery _ _ [] = []
groupEvery n p xs = take n xs : groupEvery n p (drop p xs)
This thus produces:
ghci> take 3 (groupEvery 3 1 [1..])
[[1,2,3],[2,3,4],[3,4,5]]
You can further improve the function by combining take n
and drop p
such that you only walk over the min n p
parts of the list once. I leave that as an exercise.