zipWith analogue in Python?

Question:

What is the analogue of Haskell’s zipWith function in Python?

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
Asked By: Yrogirg

||

Answers:

You can use map:

>>> x = [1,2,3,4]
>>> y = [4,3,2,1]
>>> map(lambda a, b: a**b, x, y)
[1, 8, 9, 4]
Answered By: heinrich5991

You can create yours, if you wish, but in Python we mostly do

list_c = [ f(a,b) for (a,b) in zip(list_a,list_b) ] 

as Python is not inherently functional. It just happens to support a few convenience idioms.

Answered By: dsign

map()

map(operator.add, [1, 2, 3], [3, 2, 1])

Although a LC with zip() is usually used.

[x + y for (x, y) in zip([1, 2, 3], [3, 2, 1])]

Generally as others have mentioned map and zip can help you replicate the functionality of zipWith as in Haskel.

Generally you can either apply a defined binary operator or some binary function on two list.An example to replace an Haskel zipWith with Python’s map/zip

Input: zipWith (+) [1,2,3] [3,2,1] 
Output: [4,4,4] 

>>> map(operator.add,[1,2,3],[4,3,2])
[5, 5, 5]
>>> [operator.add(x,y) for x,y in zip([1,2,3],[4,3,2])]
[5, 5, 5]
>>> 

There are other variation of zipWith aka zipWith3, zipWith4 …. zipWith7. To replicate these functionalists you may want to use izip and imap instead of zip and map.

>>> [x for x in itertools.imap(lambda x,y,z:x**2+y**2-z**2,[1,2,3,4],[5,6,7,8],[9,10,11,12])]
>>> [x**2+y**2-z**2 for x,y,z in itertools.izip([1,2,3,4],[5,6,7,8],[9,10,11,12])]
[-55, -60, -63, -64] 

As you can see, you can operate of any number of list you desire and you can still use the same procedure.

Answered By: Abhijit

A lazy zipWith with itertools:

import itertools

def zip_with(f, *coll):
    return itertools.starmap(f, itertools.izip(*coll))

This version generalizes the behaviour of zipWith with any number of iterables.

Answered By: Taurus Olson

I know this is an old question, but …

It’s already been said that the typical python way would be something like

results = [f(a, b) for a, b in zip(list1, list2)]

and so seeing a line like that in your code, most pythonistas will understand just fine.

There’s also already been a (I think) purely lazy example shown:

import itertools

def zipWith(f, *args):
    return itertools.starmap(f, itertools.izip(*args))

but I believe that starmap returns an iterator, so you won’t be able to index, or go through multiple times what that function will return.

If you’re not particularly concerned with laziness and/or need to index or loop through your new list multiple times, this is probably as general purpose as you could get:

def zipWith(func, *lists):
    return [func(*args) for args in zip(*lists)]

Not that you couldn’t do it with the lazy version, but you could also call that function like so if you’ve already built up your list of lists.

results = zipWith(func, *lists)

or just like normal like:

results = zipWith(func, list1, list2)

Somehow, that function call just looks simpler and easier to grok than the list comprehension version.


Looking at that, this looks strangely reminiscent of another helper function I often write:

def transpose(matrix):
    return zip(*matrix)

which could then be written like:

def transpose(matrix):
    return zipWith(lambda *x: x, *matrix)

Not really a better version, but I always find it interesting how when writing generic functions in a functional style, I often find myself going, “Oh. That’s just a more general form of a function I’ve already written before.”

Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.