7

For example, I need a function:

gather :: Int -> [a] -> [[a]]
gather n list = ???

where gather 3 "Hello!" == ["Hel","ell","llo","ol!"].

I have a working implementation:

gather :: Int-> [a] -> [[a]]
gather n list = 
    unfoldr 
        (\x -> 
            if fst x + n > length (snd x) then 
                Nothing 
            else 
                Just 
                    (take 
                        n 
                        (drop 
                            (fst x)
                            (snd x)), 
                    (fst x + 1, snd x))) 
        (0, list)

but I am wondering if there is something already built into the language for this? I scanned Data.List but didn't see anything.

iobender
  • 3,346
  • 1
  • 17
  • 21

2 Answers2

16

You could use tails:

gather n l = filter ((== n) . length) $ map (take n) $ tails l

or using takeWhile instead of filter:

gather n l = takeWhile ((== n) . length) $ map (take n) $ tails l

EDIT: You can remove the filter step by dropping the last n elements of the list returned from tails as suggested in the comments:

gather n = map (take n) . dropLast n . tails
  where dropLast n xs = zipWith const xs (drop n xs)
Lee
  • 142,018
  • 20
  • 234
  • 287
  • 1
    I'd use `takeWhile` instead of `filter`, but that's a pretty modest optimization. – Carl Jul 06 '14 at 20:16
  • 9
    This seems semantically correct, but operationally a bit rough: checking every sublist's length seems like too much work. There's a standard trick for saying "take all but the last `n` elements of a list", which is `\xs -> zipWith const xs (drop n xs)`; perhaps you could use that trick here. – Daniel Wagner Jul 06 '14 at 23:13
  • @DanielWagner - Thanks for the suggestion, that's much cleaner. – Lee Jul 06 '14 at 23:47
5

The dropping of tails can be arranged for automagically, thanks to the properties of zipping,

import Data.List (tails)

g :: Int -> [a] -> [[a]]
g n = foldr (zipWith (:)) (repeat []) . take n . tails

or else a simple transpose . take n . tails would suffice. Testing:

Prelude Data.List> g 3 [1..10]
[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9],[8,9,10]]
Prelude Data.List> transpose . take 3 . tails $ [1..10]
[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9],[8,9,10],[9,10],[10]]


(edit 2018-09-16:) The use of zipping can be expressed on a higher level, with traverse ZipList:

g :: Int -> [a] -> [[a]]
g n = getZipList . traverse ZipList . take n . tails
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • What I so like about both is that they do both strings and Int lists. The invocation parameters could be improved but that is not important. Mine will do Int but not strings without additional logic. .... gv no sz = zipWith enumFromTo [1..no] [sz..] > > > > gv 3 4 for a list of 3 four element lists. – fp_mora Apr 02 '18 at 22:24
  • the point here though is to rearrange a given list, not re-create it. `zipWith enumFromTo` creates the lists of integers that mimic the result, but what if we have to rearrange the result of `sort ([1,4..20] ++ [1,5..30])`? Rearranging ignores the nature of elements and just manipulates the *list structure*, regardless of what it contains. Like the list was a chain of boxes (each holding some element in it), and we're working with the boxes, not with what's *in* them. (the boxes *not* actually holding the values, but rather holding *pointers* to those values - each box holds one pointer). – Will Ness Apr 03 '18 at 07:39
  • That's what's meant by ["parametricity"](https://en.wikipedia.org/wiki/Parametricity) in Haskell, when we say "`[a]` is a *parametric* type, a 'list' of *'something'*, whatever that something is" - that *something* being the parameter, `a`, in the type `[] a` i.e. `[a]`. So when we re-arrange a list, we create new "list structure" i.e. new chains of boxes, holding the new pointers *to the same values* that were in the original boxes. Just like it is done in Lisp, too. – Will Ness Apr 03 '18 at 07:39
  • 1
    In Lisp, a box's pointer can be changed, with `set-car!` or `set-cdr!`. Not so in Haskell. But conceptually this model prevents excessive copying of values on rearrangements, which would be unavoidable if we'd say we have boxes with values actually *in* them (new chain would hold copies of values in its boxes). Of course in Haskell, with its referential transparency, a copied value can't be distinguished from a new pointer to the same value (with normal Haskell values), except for the *performance* effects (memory size, speed, amount of GCs (garbage collections), etc.). – Will Ness Apr 03 '18 at 07:49
  • [#t,#t,#t] Racket forwent set-car, etc. It violates, as you said, Ref Transparency and also immutability. A list is a required parameter I see, now. A recursive function uses tail. take & length. I like the following better than my last of the recursive and it handles any list or string, Thank you so very much . . . g2 sz ls= [take sz t|t<- tails ls,length t>=sz] – fp_mora Apr 03 '18 at 16:09