21

In python zip function accepts arbitrary number of lists and zips them together.

>>> l1 = [1,2,3]
>>> l2 = [5,6,7]
>>> l3 = [7,4,8]
>>> zip(l1,l2,l3)
[(1, 5, 7), (2, 6, 4), (3, 7, 8)]
>>> 

How can I zip together multiple lists in haskell?

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
Pratik Deoghare
  • 35,497
  • 30
  • 100
  • 146

9 Answers9

37

A generalization of zip can be achieved using Applicative Notation. It's a bit unpleasant to use because of the newtype wrapping/unwrapping, but if you are doing something that can't be done with a zipWithn for reasonably small n, you are probably already at a high enough level of abstraction where the notational pains are absent anyway.

The type is ZipList a, and its applicative instance zips together lists. For example:

(+) <$> ZipList [1,2] <*> ZipList [3,4] == ZipList [4,6]

This generalizes to functions of arbitrary arity and type using partial application:

(+) <$> ZipList [1,2]  :: ZipList (Int -> Int)

See how (+) is partially applied here?

If you don't like adding ZipList and getZipList everywhere, you could recreate the notation easily enough:

(<$>) :: (a -> b) -> [a] -> [b]
(<$>) = map

(<*>) :: [a -> b] -> [a] -> [b]
(<*>) = zipWith ($)

Then the notation for zipWith f a b c d ... is:

f <$> a <*> b <*> c <*> d <*> ...

Applicative notation is a very powerful and general technique that has much wider scope than just generalized zipping. See the Typeclassopedia for more on Applicative notation.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • 3
    To help address the newtype wrapping/unwrapping issue, see the newtype package http://hackage.haskell.org/package/newtype – Tony Morris Jun 08 '11 at 23:54
  • The Typeclassopedia link is giving a 404. – sean Jan 15 '13 at 21:52
  • How would I go for zipping a `[ZipList]` then? Getting right the types is hard... – Sebastian Graf Mar 07 '14 at 10:20
  • @Sebastian, use [Data.Functor.Compose](http://hackage.haskell.org/package/transformers-0.2.2.0/docs/Data-Functor-Compose.html). You would be zipping `Compose ZipList ZipList a`s – luqui Apr 23 '14 at 22:22
  • So if I want to work with multidimensional ZipLists all I need to do is combine the ZipLists using Compose? – CMCDragonkai Sep 23 '15 at 13:50
  • Actually I'm not understanding how to use Compose with nested zip lists. I have 2d list, and do both dimensions need to be zipped? – CMCDragonkai Sep 23 '15 at 14:23
  • I suggest making a new question with your attempts and what you're struggling with. – luqui Sep 23 '15 at 17:40
33

You can transpose a list of lists:

>>> import Data.List
>>> transpose [l1,l2,l3]
[[1,5,7],[2,6,4],[3,7,8]]
newacct
  • 119,665
  • 29
  • 163
  • 224
  • 3
    Works only if all the arguments are having the same type, and that's also the reason why it seems to be so hard to implement a general zipN. – tux21b Mar 18 '10 at 09:01
  • there's difference in behaviour: transpose [[1,2,3],[4,5,6,7]] => [[1,4],[2,5],[3,6],[7]]. zip doesn't include that last element. But appart from that gotcha it's a winner! Definitely joins my toolbox. – Emmanuel Touzery Feb 02 '13 at 07:39
  • I have written this function to avoid the different behaviour in case the lengths of the lists don't match: `zipLists list = takeWhile (\x -> length x == length list) $ transpose list` – Emmanuel Touzery Feb 16 '13 at 17:59
11

Looks like there is also a zip3 (doc) and a zip4 (doc) function in Haskell. But the zipn seems to be complicated because of the strong type system. Here is a good discussion I've found during my research.

tux21b
  • 90,183
  • 16
  • 117
  • 101
  • The corresponding examples for `zip3`: http://www.zvon.org/other/haskell/Outputprelude/zip3_f.html – kennytm Mar 18 '10 at 08:04
  • Do you mean its just IMPOSSIBLE in haskell?? – Pratik Deoghare Mar 18 '10 at 08:10
  • I don't know. Maybe someone other knows a trick. But I think the chances are low, because there wasn't a good answer on the mailing list and the standard library has implemented all those zip2, zip3 etc. functions instead... – tux21b Mar 18 '10 at 08:22
  • 1
    Generalized `zipWithN`: http://www.reddit.com/r/haskell/comments/b9qyp/generalized_zipwithn_with_a_pretty_implementation/ – Josh Lee Mar 18 '10 at 08:49
  • 10
    Data.List goes up to `zip7`. Arguably 7 items in a tuple is already too many for an easily understood, maintainable program. – dave4420 Mar 18 '10 at 09:08
6

GHC also supports parallel list comprehensions:

{-# LANGUAGE ParallelListComp #-}

[(x,y) | x <- [1..3]
       | y <- ['a'..'c']
       ]

==> [(1,'a'),(2,'b'),(3,'c')]

I just tested it up to 26 parallel variables, which should be enough for all practical purposes.

It's a bit hacky (and non-standard) though, so in case you're writing something serious, ZipList may be the better way to go.

David
  • 8,275
  • 5
  • 26
  • 36
  • The main difference between this and the normal list comprehension is that x is no longer in scope in y's part of the comprehension right? – CMCDragonkai Sep 23 '15 at 13:58
4

I think it's probably the least elegant solution suggested, but for the sake of completeness it should be added that such things should be possible with Template Haskell.

This was in fact covered in what I think is the original Template Haskell paper (search zipn in the text): http://research.microsoft.com/en-us/um/people/simonpj/Papers/meta-haskell/meta-haskell.pdf

But I think that code never in fact worked, see this: http://www.haskell.org/pipermail/template-haskell/2003-July/000126.html (pattern slices are not implemented).

That was not implemented in 2003, but it's still not implemented today: http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/template-haskell.html (pattern slices are not supported)

However there is an implementation of zipWithN using template haskell: http://www.haskell.org/haskellwiki/Template_Haskell#zipWithN

I have verified that it works with this test program:

{-# LANGUAGE TemplateHaskell #-}
import Zipn

main = do
    let l1 = [1,2,3]
    let l2 = [5,6,7]
    let l3 = [7,4,8]
    print $ $(zipWithN 3) (,,) l1 l2 l3

In the Zipn module, I pasted the zipn, just renamed zipWithN for clarity (and remember to add the pragma TemplateHaskell at the top). Note that the N is in fact harcoded twice here, because I had to give (,,) as the "with" function. You'd have to change the number of commas depending on N.

(,,) stands for \a b c -> (a,b,c)

I guess someone with good Template Haskell skills (which is not my case at this point) could make a straight zipN using Template Haskell.

Emmanuel Touzery
  • 9,008
  • 3
  • 65
  • 81
3

Generalizing zipping is actually quite easy. You just have to write specialized versions of the Applicative combinators for ZipList:

z :: [a -> b] -> [a] -> [b]
z = zipWith ($)

infixl 4 `z`

Now you can zip as many lists as you want:

f <$> xs `z` ys `z` zs

or alternatively:

repeat f `z` xs `z` ys `z` zs
ertes
  • 4,430
  • 1
  • 18
  • 23
3

It's non-trivial, but it is doable. See this blog post. I dont know whether this made into some library.

Here is another version, which is simplier. This one could actually be cut-n-pasted here:

{-# LANGUAGE MultiParamTypeClasses
           , FunctionalDependencies
           , FlexibleInstances
           , UndecidableInstances
           #-}

-- |
-- Module      :  Data.List.ZipWithN
-- Copyright   :  Copyright (c) 2009 wren ng thornton
-- License     :  BSD3
-- Maintainer  :  wren@community.haskell.org
-- Stability   :  experimental
-- Portability :  non-portable (MPTCs, FunDeps,...)
--
-- Provides a polyvariadic 'map'/'zipWith' like the @map@ in Scheme.
-- For more details on this style of type hackery, see:
--
--    * Chung-chieh Shan, /A polyvariadic function of a non-regular/
--      /type (Int->)^N ([]^N e)->.../
--      <http://okmij.org/ftp/Haskell/polyvariadic.html#polyvartype-fn>
----------------------------------------------------------------
module Data.List.ZipWithN (ZipWithN(), zipWithN) where

-- | This class provides the necessary polymorphism. It is only
-- exported for the sake of giving type signatures.
--
-- Because we can't do functor composition without a lot of noise
-- from newtype wrappers, we use @gr@ and @kr@ to precompose the
-- direct/list functor with the reader functor and the return type.
class ZipWithN a gr kr | kr -> gr a where
    _zipWithN :: [a -> gr] -> [a] -> kr

instance ZipWithN a b [b] where
    _zipWithN = zipWith ($)

instance ZipWithN b gr kr => ZipWithN a (b -> gr) ([b] -> kr) where
    _zipWithN = (_zipWithN .) . zipWith ($)


-- | Polyadic version of 'map'/'zipWith'. The given type signature
-- isn't terribly helpful or intuitive. The /real/ type signature
-- is:
--
-- > zipWithN :: {forall a}^N. ({a->}^N  r) -> ({[a]->}^N  r)
--
-- Note that the @a@ type variables are meta and so are independent
-- from one another, despite being correlated in N across all
-- repetitions.
zipWithN :: (ZipWithN a gr kr) => (a -> gr) -> [a] -> kr
zipWithN = _zipWithN . repeat

If you are just starting to learn Haskell, postpone understanding it for some time :)

ADEpt
  • 5,504
  • 1
  • 25
  • 32
  • I can't understand a word. I have only very basic knowledge of Haskell. Can you please extract that function from that post and post it here? – Pratik Deoghare Mar 18 '10 at 10:02
1

If all your data is of the same type you could do:

import Data.List (transpose)

zipAllWith :: ([a] -> b) -> [[a]] -> [b]
zipAllWith _ []  = []
zipAllWith f xss = map f . transpose $ xss

zipAll = zipAllWith id

Example:

> zipAll [[1, 2, 3], [4, 5, 6], [7, 8]]
[[1,4,7],[2,5,8],[3,6]]
Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
1

For a specific number of lists, you can so something like this:

> let l1 = [1,2,3]
> let l2 = "abc"
> let l3 = [10.0, 11.0, 12.0]
> let l4 = [True, False, False]

> [ (e1,e2,e3,e4) | (((e1,e2),e3),e4) <- zip (zip (zip l1 l2) l3) l4 ]
[(1,'a',10.0,True),(2,'b',11.0,False),(3,'c',12.0,False)]

It's not a generic function, but a pattern you can apply to a different number of lists.

camh
  • 40,988
  • 13
  • 62
  • 70