2012-03-19 12 views
42

¿Cuál es el análogo de la función zipWith de Haskell en Python?zip¿Con análogo en Python?

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] 
+5

¿Podría explicarme cómo funciona? Hay mucha gente que conoce Python, pero solo algunos conocen a Haskell lo suficiente. – Tadeck

+3

@Tadeck: 'zipWith' es como' map', excepto que atraviesa dos listas en paralelo, aplicando una función a los elementos correspondientes de cada lista. Si una lista es más larga, los elementos adicionales se ignoran. Por ejemplo, 'zipWith (*) [1, 2, 3] [7, 8] == [7, 16]'. – hammar

Respuesta

35

Usted puede crear el suyo, si lo desea, pero en Python que en su mayoría hacer

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

como Python no es inherentemente funcional. Simplemente sucede que admite algunos modismos de conveniencia.

+17

Si quieres ser un poco más elegante, puedes hacer '' [f (* list_c) para list_c en zip (list_a, list_b)] '' - usando el operador '' splat'' para descomprimir la tupla en lugar de indicarlo dos veces. Esto también tiene la ventaja de que puedes agregar más argumentos a la función zip y funcionará felizmente si es necesario. –

+5

Tenga en cuenta que si usa Python 2.x, el código anterior no es flojo: 'zip()' creará una lista que se usará una vez. En Python 2.x puede usar 'itertools.izip()' para la evaluación diferida; en Python 3 obtienes evaluación diferida con el 'zip()' incorporado. – steveha

+1

@steveha, pero tenga en cuenta que no es realmente vago (el Python más cercano tiene) el sentido Haskell incluso usando 'izip' en lugar de' zip', ya que se da como una comprensión de lista en lugar de una expresión de generador. – lvc

9

Puede usar el mapa:

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

map()

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

Aunque una LC con zip() se utiliza generalmente.

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

+1 Programación funcional de Yessss al rescate. –

+10

Nota: Esto tiene un comportamiento diferente en comparación con 'zipWith' cuando las longitudes de las listas no coinciden. El 'zipWith' de Haskell trunca las listas de entrada a la longitud de la más corta, mientras que el' map' de Python pasa 'None' en lugar de los elementos faltantes de la lista más corta. Por ejemplo, 'zipWith (+) [1, 2], [3, 2, 1] == [4, 4]', mientras 'map (operator.add, [1, 2], [3, 2, 1] ]) 'arroja una excepción de intentar agregar un entero y' Ninguno'. – hammar

+5

@hammar: estás hablando de 'map' en Python 2.x. 'map' en Python 3.x (así como' imap' en 2.x) se detiene después de que termina la lista más corta. Además, 'zip' se detiene después de que la lista más corta termina en 2.x y 3.x – newacct

4

general como otros han mencionado mapa y postal puede ayudar a replicar la funcionalidad de zipWith como en Haskel.

Generalmente usted puede aplicar un operador binario definido o alguna función binaria en dos list.An ejemplo para reemplazar un zipWith Haskel con Python mapa/postal

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] 
>>> 

Hay otra variación de zipWith aka zipWith3, zipWith4 .... zipWith7. Para replicar estos funcionalistas es posible que desee utilizar izip y imap en lugar de zip y mapa.

>>> [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] 

Como puede ver, puede operar con cualquier cantidad de listas que desee y puede seguir utilizando el mismo procedimiento.

+3

' map() 'puede tomar cualquier cantidad arbitraria de secuencias. –

+0

Tu primera comprensión de lista no necesita usar 'operator.add', solo puede ser' [x + y para x, y en ...] '. Del mismo modo, no tiene mucho sentido escribir código como '[x para x en imap ...]' - es un poco más claro escribir 'list (imap ...)', pero entonces también podrías usar 'mapa'. – lvc

6

Un perezoso zipWith con itertools:

import itertools 

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

Esta versión generaliza el comportamiento de zipWith con cualquier número de iterables.

0

Sé que esto es una vieja pregunta, pero ...

Ya se ha dicho que la forma de pitón típica sería algo así como

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

y así ver una línea como la que, en su código , la mayoría de los pythonistas lo entenderán bien.

También ha ya sido un (creo) ejemplo puramente perezoso se muestra:

import itertools 

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

pero creo que starmap devuelve un iterador, por lo que no será capaz de índice, o pasar por varias veces lo esa función volverá.

Si no está particularmente preocupado por la pereza y/o la necesidad de índice o bucle a través de la nueva lista varias veces, esto es probablemente como propósito general como usted podría conseguir:

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

No es que no pudiste No lo hagas con la versión perezosa, pero también podrías llamar a esa función como si ya hubieras creado tu lista de listas.

results = zipWith(func, *lists) 

o simplemente como normal como:

results = zipWith(func, list1, list2) 

De alguna manera, dicha llamada de función sólo se ve más simple y más fácil de asimilar que la versión lista por comprensión.


En cuanto a eso, esto parece extraña reminiscencia de otra función auxiliar A menudo escribo:

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

que luego podría ser escrita como:

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

No es realmente una versión mejor, pero siempre me parece interesante cómo cuando escribo funciones genéricas en un estilo funcional, a menudo me encuentro yendo, "Oh. Esa es solo una forma más general de una función que ya he escrito antes".

+0

Guau, volví a mirar esto y simplemente podías hacer 'zipWith = map'. Tampoco lo había notado antes. –

+0

Si desea un iterador, ¿por qué no simplemente 'results = (f (a, b) para a, b en izip (list1, list2))'? – Elmex80s

+0

@ Elmex80s, los iteradores no son un valor inmutable. Alguien procedente de un lenguaje puramente funcional como Haskell estaría acostumbrado a la inmutabilidad por defecto. Entonces mi respuesta asume que * no * quieres un iterador. –

Cuestiones relacionadas