How to zip multiple lists in Haskell?
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, 开发者_JAVA百科6, 4), (3, 7, 8)]
>>>
How can I zip
together multiple lists in haskell?
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.
You can transpose a list of lists:
>>> import Data.List
>>> transpose [l1,l2,l3]
[[1,5,7],[2,6,4],[3,7,8]]
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.
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.
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.
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 :)
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
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]]
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.
精彩评论