Just recently playing around with one of the excellent Advent of Code problems (specifically 2015 question 6 ) a possible solution involved splitting a list into two parts where the first element goes into the first part, the second element into the second part and so on. For example

- [1, 2, 3, 4, 5, 6] -> [1, 3, 5], [2, 4, 6]

So, in terms of function signatures we can write splitList :: [a] -> ([a], [a]) Notice there’s no constraint on the type in the list, it’s a very general function.

One way of doing this is is to make use of pattern matching and build up a solution recursively. i.e.

1 2 3 4 5 6 7 8 |
-- -- splitList :: [a] -> ([a], [a]) splitList xys = go [] [] xys where go xs ys [] = (xs, ys) go xs ys [x] = (xs ++ [x], ys) go xs ys [x, y] = (xs ++ [x], ys ++ [y]) go xs ys (x:y:rest) = go (xs ++ [x]) (ys ++ [y]) rest |

Here the *splitList* function calls another function that returns the solution in the ‘correct shape’. This *go* function uses pattern matching for the possible cases of the input list.

- An empty input list get split into two empty lists.
- Splitting a list of one item gives a list with that item and an empty list.
- Similarly for a list of two items the result is two lists with one item in each.
- The more general case pattern matches the input to two items and ‘the rest’, it then builds up the result lists and calls itself.

This certainly works fine – here are some sample runs.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- -- λ-> splitList [] ([],[]) *Main λ-> splitList [1] ([1],[]) *Main λ-> splitList [1,2] ([1],[2]) *Main λ-> splitList [1,2,3,4] ([1,3],[2,4]) *Main λ-> splitList [1,2,3,4,5] ([1,3,5],[2,4]) *Main λ-> splitList [1,2,3,4,5,6] ([1,3,5],[2,4,6]) |

And looking at the code it is fairly clear as to what’s happening. However it is a bit verbose and I think Haskell can provide a ‘better’ solution!

We have a list containing elements of type *a*. We know nothing about *a* so trying to apply a filter to the list won’t really work. However the list could be morphed into another list such that the original data is still there but there’s extra information that can be used as leverage. The *zip* function takes two lists and returns a list of pairs. i.e.
zip :: [a] -> [b] -> [(a, b)] and if one list is longer than the other then zip terminates when the shortest list has ben processed. This allows us to use an infinite list when zipping. So we can *zip* up the original list and in effect tag the data in such a way that it will allow us to extract alternate elements.

For example

1 2 3 |
-- λ-> zip ['a', 'b', 'c', 'd'] [0..] [('a',0),('b',1),('c',2),('d',3)] |

We can now filter [('a',0),('b',1),('c',2),('d',3)] by taking only those elements whose second value is odd (or even) i.e. filtering by odd:

1 2 3 |
-- λ-> filter (odd . snd) [('a',0),('b',1),('c',2),('d',3)] [('b',1),('d',3)] |

We now have the alternate elements but they are tagged – so we need to remove the tags by taking just the first element of each entry in the list of pairs.

1 2 3 |
-- λ-> map fst [('b',1),('d',3)] "bd" |

Using the above ideas in an alternate implementation of *splitList* we have

1 2 3 4 5 |
-- -- splitList :: [a] -> ([a], [a]) splitList xs = (f 1 xs, f 0 xs) where f n a = map fst . filter (odd . snd) . zip a $ [n..] |

here the pair of lists are created from the alternate elements simply by zipping up with tags starting at different values.

A very succinct one-liner using function composition. I find little things like this in Haskell very, very pleasing and immensely satisfying! 🙂

Here are a few sample runs.

1 2 3 4 5 6 7 8 9 10 11 12 |
-- λ-> splitList ['a'..'z'] ("acegikmoqsuwy","bdfhjlnprtvxz") *Main λ-> splitList ['a'..'z'] ("acegikmoqsuwy","bdfhjlnprtvxz") *Main λ-> splitList ['A'..'Z'] ("ACEGIKMOQSUWY","BDFHJLNPRTVXZ") *Main λ-> splitList [-9..9] ([-9,-7,-5,-3,-1,1,3,5,7,9],[-8,-6,-4,-2,0,2,4,6,8]) |

Thanks for reading…!

(\(a,b) -> transpose [a,b]) . (uncurry $ splitAt . (

`div`

2)) . (length &&& id)Interesting but it doesn’t give the same results as splitList.

λ-> sl [1..10]

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

*Main

λ-> splitList [1..10]

([1,3,5,7,9],[2,4,6,8,10])

where sl is your function.

ack sorry

transpose . takeWhile (not . null) . unfoldr (return . splitAt 2)

Nice!

a little cleaner:

transpose . unfoldr (uncurry (*>) . (guard . not . null . fst &&& pure) . splitAt 2)

🙂

I think I prefer the other…

transpose . takeWhile (not . null) . unfoldr (return . splitAt 2)

Could be done easily with list comprehension too

f xs = ([x | (i, x) <- zip [1..] xs, odd i], [x | (i, x) <- zip [1..] xs, even i])

Nice! So many ways to choose from!