2012-03-19 11 views
42

Qual è l'analogo della funzione zipWith di Haskell in Python?zipCon analogico in Python?

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

Potresti spiegare come funziona? Ci sono molte persone che conoscono Python, ma solo qualcuno conosce Haskell abbastanza bene. – Tadeck

+3

@Tadeck: 'zipWith' è come' map', eccetto che attraversa due liste in parallelo, applicando una funzione agli elementi corrispondenti da ciascuna lista. Se una lista è più lunga, gli elementi extra vengono ignorati. Ad esempio, 'zipWith (*) [1, 2, 3] [7, 8] == [7, 16]'. – hammar

risposta

35

È possibile creare la vostra, se lo si desidera, ma in Python che per lo più fare

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

come Python non è di per sé funzionale. Succede solo per supportare alcuni idiomi di convenienza.

+17

Se vuoi essere un po 'più elegante, potresti fare '' [f (* list_c) per list_c in zip (lista_a, lista_b)] '' - usando l'operatore '' splat'' per decomprimere la tupla piuttosto che dichiararla due volte. Questo ha anche il vantaggio di poter aggiungere solo altri argomenti alla funzione zip e funzionerà felicemente se necessario. –

+5

Nota che se stai usando Python 2.x, il codice sopra non è pigro: 'zip()' costruirà una lista che sarà usata una volta. In Python 2.x puoi usare 'itertools.izip()' per la valutazione lazy; in Python 3 ottieni una valutazione lenta con il 'zip()' incorporato. – steveha

+1

@steveha ma si noti che non è veramente pigro in (il Python più vicino deve) il senso di Haskell anche usando 'izip' invece di' zip', dato che è dato come una comprensione di lista piuttosto che un'espressione di generatore. – lvc

9

È possibile utilizzare la mappa:

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

Sebbene LC con zip() è di solito utilizzato.

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

+1 Programmazione funzionale di Yessss per il salvataggio. –

+10

Nota: questo comportamento è diverso rispetto a 'zipWith' quando le lunghezze degli elenchi non corrispondono. 'ZipWith' di Haskell tronca gli elenchi di input per la lunghezza del più breve, mentre' map' di Python passa 'Nessuno' al posto degli elementi mancanti dalla lista più corta. Ad esempio, 'zipWith (+) [1, 2], [3, 2, 1] == [4, 4]', mentre 'map (operator.add, [1, 2], [3, 2, 1 ]) 'genera un'eccezione dal tentativo di aggiungere un intero e' Nessuno'. – hammar

+5

@hammar: stai parlando di 'map' in Python 2.x. 'map' in Python 3.x (così come' imap' in 2.x) si ferma dopo la fine dell'elenco più breve. Inoltre, 'zip' si ferma dopo che la lista più breve termina con 2.xe 3.x – newacct

4

In genere, come altri hanno menzionato, map e zip possono aiutarti a replicare la funzionalità di zipWith come in Haskel.

In genere è possibile sia applicare un operatore binario definito o qualche funzione binaria su due esempio list.An per sostituire un Haskel zipWith con Python mappa/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] 
>>> 

ci sono altre variazioni di zipWith aka zipWith3, zipWith4 .... zipWith7. Per replicare questi funzionalisti potresti voler usare izip e imap invece di zip e 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] 

Come si può vedere, è possibile utilizzare qualsiasi numero di lista che si desidera e si può ancora utilizzare la stessa procedura.

+3

' map() 'può prendere qualsiasi numero arbitrario di sequenze. –

+0

La tua prima comprensione di lista non ha bisogno di usare 'operator.add', può essere solo' [x + y per x, y in ...] '. Allo stesso modo, non ha molto senso scrivere un codice come '[x per x in imap ...]' - è un po 'più chiaro scrivere 'list (imap ...)', ma allora si potrebbe anche usare 'map'. – lvc

6

Un pigro zipWith con itertools:

import itertools 

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

Questa versione generalizza il comportamento di zipWith con qualsiasi numero di iterables.

0

So che questa è una vecchia questione, ma ...

E 'già stato detto che il tipico modo di pitone sarebbe qualcosa di simile

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

e così vedere una linea del genere nel codice la maggior parte dei pitonidi capirà bene.

C'è anche già stato un (credo) esempio puramente pigro mostrato:

import itertools 

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

ma credo che Starmap restituisce un iteratore, in modo da non essere in grado di indicizzare, o passare attraverso più volte quello che quella funzione tornerà.

Se non siete particolarmente interessati con la pigrizia e/o necessità di indice o ad anello attraverso il nuovo elenco più volte, questo è probabilmente come general purpose come si potrebbe ottenere:

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

Non che si couldn Lo faccio con la versione lenta, ma potresti anche chiamare quella funzione in questo modo se hai già creato il tuo elenco di elenchi.

results = zipWith(func, *lists) 

o semplicemente come normale come:

results = zipWith(func, list1, list2) 

In qualche modo, che chiamata di funzione sembra proprio più semplice e più facile da Grok rispetto alla versione di lista.


Osservando che, questo sembra strano che ricorda di un'altra funzione di supporto io scrivo spesso:

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

che potrebbero poi essere scritta come:

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

Non proprio una versione migliore, ma trovo sempre interessante il modo in cui quando si scrivono funzioni generiche in uno stile funzionale, spesso mi ritrovo a dire "Oh. È solo una forma più generale di una funzione che ho già scritto in precedenza".

+0

Wow, ho guardato di nuovo e potresti semplicemente fare 'zipWith = map' Non l'avevo notato prima. –

+0

Se vuoi un iteratore perché non solo 'results = (f (a, b) per a, b in izip (list1, list2))'? – Elmex80s

+0

@ Elmex80s, gli iteratori non sono un valore immutabile. Qualcuno proveniente da un linguaggio puramente funzionale come Haskell sarebbe stato usato per l'immutabilità di default. Quindi la mia risposta presuppone che * non * voglia un iteratore. –

Problemi correlati