3

I am trying to set up some functions to help with a current project I am working on. I am new to Haskell and struggling to implement my desired functions.

I have a list [a] and would like it to output a tuple of four different lists ([b],[b],[b],[b]) where each item in list [a] is successively placed in to the next list in the output tuple. So the first element in the input list [a] goes to the first list [b], the second element in [a] goes to the second list [b], the third element in [a] goes to the third list [b], and so on. I have tried using chunksOf and splitEvery/splitAt but cannot get the correct output. And help would be greatly appreciated! Thanks!

Will Ness
  • 70,110
  • 9
  • 98
  • 181
billbob
  • 41
  • 1
  • 1
    I think the output is then `[a] -> ([a], [a], [a], [a])`? So with `[a]`s as output, not `[b]`s. Can you share your implementation? – Willem Van Onsem Mar 14 '20 at 18:26
  • Yes, I was planning on altering the data type of each element of [a] as it is placed into its corresponding slot in [b] which is why I changed the names in this case. However, for this basic implementation, I think that it is correct to say that [a] will output since I still need to map my desired data type to each element in [a] as it is passed to the output tuple – billbob Mar 14 '20 at 19:36
  • A similar question was asked here: [Haskell: Splitting list into tuple of two new lists](https://stackoverflow.com/questions/7410989/haskell-splitting-list-into-tuple-of-two-new-lists) That question is about two-tuples (twoples, I guess ;) ), but some of the answers (dave4220 and Yitz) can easily be expanded to your situation. – MikaelF Mar 14 '20 at 20:14
  • @MikaelF thanks for the link. [this answer](https://stackoverflow.com/a/10939472/849891) as well, with the `foldr (\a ~(x,y) -> (a:y,x)) ([],[])` code snippet (which is also [here](https://wiki.haskell.org/index.php?title=Blow_your_mind&oldid=2630) and probably was known much earlier, too). to extend it to 3- or n-tuples... swapping is rotating, and the only thing left to decide is, in which direction? :) – Will Ness Mar 14 '20 at 21:18
  • (I've found [this old answer](https://stackoverflow.com/a/47994918/849891) of mine dealing with that, though in Scheme.) also, [this question](https://stackoverflow.com/questions/58993547/walk-through-a-list-split-function-in-haskell). – Will Ness Mar 14 '20 at 21:52
  • It always makes sense to split a list into two by some means and placing the components into a tuple. However for more than two and especially dynamically into `n` sublists which are of the same type, i would use a list instead of a tuple structure. – Redu Mar 15 '20 at 12:54

2 Answers2

7

You each time "rotate" the 4-tuple and prepend to the first element. So we can implement this with a foldr pattern that looks like:

toFour :: [a] -> ([a], [a], [a], [a])
toFour = foldr (\a (bs, cs, ds, as) -> (a:as, bs, cs, ds)) ([], [], [], [])

or with an irrefutable pattern:

toFour :: [a] -> ([a], [a], [a], [a])
toFour = foldr (\a ~(bs, cs, ds, as) -> (a:as, bs, cs, ds)) ([], [], [], [])

So here (bs, cs, ds, as) is the 4-tuple that we generated for the tail of the list, and we "rotate" to the right to construct the tuple (as, bs, cs, ds) and then prepend the item a to the first list of the 4-tuple.

For a list of integers, this gives us:

Prelude> toFour [1..2]
([1],[2],[],[])
Prelude> toFour [1..3]
([1],[2],[3],[])
Prelude> toFour [1..4]
([1],[2],[3],[4])
Prelude> toFour [1..5]
([1,5],[2],[3],[4])
Prelude> toFour [1..6]
([1,5],[2,6],[3],[4])
Prelude> toFour [1..10]
([1,5,9],[2,6,10],[3,7],[4,8])

When we work with the irrefutable pattern, this is done lazily, so we can for example distribute elements of an infinite list, and then take for example obtain the first 10 elements of the second item:

Prelude> (\(_, l, _, _) -> take 10 l) (toFour [1..])
[2,6,10,14,18,22,26,30,34,38]
Rein Henrichs
  • 15,437
  • 1
  • 45
  • 55
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 2
    This is a clever solution! I think it could also be generalized to arbitrary numbers of lists by using a list or sequence instead of a tuple. – Rein Henrichs Mar 14 '20 at 18:49
  • You could use the standard two-list based queue, I think. ```haskell bucket i = uncurry (++) . fmap reverse . foldr f ([], replicate i []); where f x (xs,y:ys) = ((x:y):xs,ys); f x (xs,[]) = let y:ys = reverse xs in ([x:y],ys); ``` – oisdk Mar 14 '20 at 19:01
  • 1
    @ReinHenrichs this is far from being the first time this trick makes an appearance in an SO answer. I myself used it in an answer or two (or was it in comments?). Since I didn't come up with it myself, I always point out I first saw it in an F# (or was it ML?) answer by user "Ed'ka" -- I remember being quite blown away by it. His code was for a two-tuple of lists, swapping the tuple at each step; it was a nice exercise to come up with 3-tuples generalization, as I recall, :) to figure out the correct tuple rotation direction (which can then be used for any n, of course). – Will Ness Mar 14 '20 at 20:23
  • @ReinHenrichs [this](https://stackoverflow.com/a/7945580/849891). (turns out I even gave that answer a bounty :) ) – Will Ness Mar 14 '20 at 20:47
  • and it turns out it's in the haskellwiki [since forever](https://wiki.haskell.org/index.php?title=Blow_your_mind&oldid=2630). :) – Will Ness Mar 14 '20 at 21:08
3

Define

f n = transpose . chunksOf n
g xs = let [xs1, xs2, xs3, xs4] = f 4 xs in (xs1, xs2, xs3, xs4) 

Where f is the general solution. This gives

Prelude> g ['a' .. 'z']
("aeimquy","bfjnrvz","cgkosw","dhlptx") 

Signatures

Prelude> :t g
g :: [a] -> ([a], [a], [a], [a])

and

Prelude> :t f
f :: Int -> [a] -> [[a]]

Info on:

Elmex80s
  • 3,428
  • 1
  • 15
  • 23
  • 1
    `map (take 5) . transpose . chunksOf 3 $ [1..]` diverges for some reason. :) must slap a `take 3` on top that `transpose` for the proper laziness. – Will Ness Mar 15 '20 at 09:39