Utilizzo di Python Map e altri strumenti funzionali


127

Questo è abbastanza banale, ma sto cercando di imparare / comprendere la programmazione funzionale in Python. Il seguente codice:

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest(foo, bar):
    print foo, bar

map(maptest, foos, bars)

produce:

1.0 1
2.0 2
3.0 3
4.0 None
5.0 None

D. Esiste un modo per usare map o altri strumenti funzionali in Python per produrre quanto segue senza loop ecc.

1.0 [1,2,3]
2.0 [1,2,3]
3.0 [1,2,3]
4.0 [1,2,3]
5.0 [1,2,3]

Proprio come una nota a margine come cambierebbe l'implementazione se ci fosse una dipendenza tra foo e bar. per esempio

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3,4,5]

e stampa:

1.0 [2,3,4,5]
2.0 [1,3,4,5]
3.0 [1,2,4,5]
...

PS: So farlo ingenuamente usando if, loop e / o generatori, ma mi piacerebbe imparare come ottenere lo stesso usando strumenti funzionali. È solo il caso di aggiungere un'istruzione if a maptest o applicare un'altra mappa filtro alle barre internamente a maptest?


Grazie ragazzi. Devo ammettere che sto cercando di imparare i concetti di programmazione funzionale attraverso Python.

Risposte:


54

Il modo più semplice sarebbe non passare barsattraverso le diverse funzioni, ma accedervi direttamente da maptest:

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest(foo):
    print foo, bars

map(maptest, foos)

Con la tua maptestfunzione originale puoi anche usare una funzione lambda in map:

map((lambda foo: maptest(foo, bars)), foos)

cattivo quando le barre vengono dall'elenco
Phyo Arkar Lwin

59
Questa soluzione va direttamente contro i principi della programmazione funzionale che l'OP vuole tentare di apprendere. Una regola fondamentale nella programmazione funzionale è che ogni volta che chiamate una funzione con gli stessi argomenti, ottenete SEMPRE lo stesso output. Questo evita un nido di vipere di bug introdotti dallo stato globale. Poiché maptest dipende da una definizione esterna di barre, questo principio è infranto.
image_doctor,

3
Caro Stack Overflow, poiché ti piace chiudere le domande e moderatamente pesantemente, perché non deselezioni questa domanda come risposta e contrassegni la risposta corretta come risposta? Saluti, noi.
Bahadir Cambel,

1
@image_doctor, in FP è perfettamente accettabile accedere alla costante globale (lì si considera come nullary fuction)
Peter K,

1
@BahadirCambel Stack La moderazione dell'overflow può essere a volte pesante, ma il segno di spunta appartiene sempre e sempre all'OP.
wizzwizz4,

194

Conosci altri linguaggi funzionali? cioè stai cercando di imparare come funziona la programmazione funzionale di python o stai cercando di imparare a conoscere la programmazione funzionale e l'utilizzo di python come veicolo?

Inoltre, capisci la comprensione dell'elenco?

map(f, sequence)

è direttamente equivalente (*) a:

[f(x) for x in sequence]

In effetti, penso che map()una volta era previsto che fosse rimosso da Python 3.0 come ridondante (ciò non è accaduto).

map(f, sequence1, sequence2)

è principalmente equivalente a:

[f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]

(c'è una differenza nel modo in cui gestisce il caso in cui le sequenze sono di diversa lunghezza. Come hai visto, map()riempie Nessuno quando una delle sequenze si esaurisce, mentre si zip()ferma quando la sequenza più breve si ferma)

Quindi, per rispondere alla tua domanda specifica, stai provando a produrre il risultato:

foos[0], bars
foos[1], bars
foos[2], bars
# etc.

Puoi farlo scrivendo una funzione che accetta un singolo argomento e lo stampa, seguito da barre:

def maptest(x):
     print x, bars
map(maptest, foos)

In alternativa, è possibile creare un elenco simile al seguente:

[bars, bars, bars, ] # etc.

e usa il tuo maptest originale:

def maptest(x, y):
    print x, y

Un modo per farlo sarebbe quello di compilare esplicitamente l'elenco in anticipo:

barses = [bars] * len(foos)
map(maptest, foos, barses)

In alternativa, è possibile inserire il itertoolsmodulo. itertoolscontiene molte funzioni intelligenti che ti aiutano a eseguire la programmazione di valutazione pigra in stile funzionale in Python. In questo caso, vogliamo itertools.repeat, che produrrà il suo argomento a tempo indeterminato man mano che si procede su di esso. Quest'ultimo fatto significa che se lo fai:

map(maptest, foos, itertools.repeat(bars))

otterrai un output infinito, dal momento che map()continua fino a quando uno degli argomenti continua a produrre output. Tuttavia, itertools.imapè proprio come map(), ma si interrompe non appena si interrompe il più breve iterabile.

itertools.imap(maptest, foos, itertools.repeat(bars))

Spero che questo ti aiuti :-)

(*) È un po 'diverso in Python 3.0. Lì, map () restituisce essenzialmente un'espressione del generatore.


Quindi, sto capendo correttamente che, a differenza della mappa, itertools.imap(f, sequence1, sequence2)è davvero equivalente [f(x1, x2) for x1, x2 in zip(sequence1, sequence2)]?
Jon Coombs,

Provando un po ', vedo che restituisce un oggetto itertools.imap, quindi forse questo sarebbe più "equivalente":list(itertools.imap(f, sequence1, sequence2))
Jon Coombs il

Questa dovrebbe essere la risposta approvata.
Rob Grant,

30

Ecco la soluzione che stai cercando:

>>> foos = [1.0, 2.0, 3.0, 4.0, 5.0]
>>> bars = [1, 2, 3]
>>> [(x, bars) for x in foos]
[(1.0, [1, 2, 3]), (2.0, [1, 2, 3]), (3.0, [1, 2, 3]), (4.0, [1, 2, 3]), (5.0, [
1, 2, 3])]

Consiglio di usare una comprensione dell'elenco (la [(x, bars) for x in foos]parte) sull'uso di map poiché evita il sovraccarico di una chiamata di funzione su ogni iterazione (che può essere molto significativa). Se lo utilizzerai solo in un ciclo for, otterrai velocità migliori usando la comprensione del generatore:

>>> y = ((x, bars) for x in foos)
>>> for z in y:
...     print z
...
(1.0, [1, 2, 3])
(2.0, [1, 2, 3])
(3.0, [1, 2, 3])
(4.0, [1, 2, 3])
(5.0, [1, 2, 3])

La differenza è che la comprensione del generatore è pigramente caricata .

AGGIORNAMENTO In risposta a questo commento:

Ovviamente sai che non copi le barre, tutte le voci sono nello stesso elenco di barre. Quindi, se si modifica uno di essi (comprese le barre originali), si modificano tutti.

Suppongo che questo sia un punto valido. Ci sono due soluzioni a cui posso pensare. Il più efficiente è probabilmente qualcosa del genere:

tbars = tuple(bars)
[(x, tbars) for x in foos]

Poiché le tuple sono immutabili, ciò impedirà che le barre vengano modificate attraverso i risultati della comprensione di questo elenco (o comprensione del generatore se segui quel percorso). Se hai davvero bisogno di modificare tutti i risultati, puoi farlo:

from copy import copy
[(x, copy(bars)) for x in foos]

Tuttavia, questo può essere un po 'costoso sia in termini di utilizzo della memoria che in termini di velocità, quindi lo sconsiglio a meno che non sia necessario aggiungerlo a ciascuno di essi.


1
Ovviamente sai che non copi le barre, tutte le voci sono nello stesso elenco di barre. Quindi, se si modifica uno di essi (comprese le barre originali), si modificano tutti.
vartec,

20

La programmazione funzionale riguarda la creazione di codice privo di effetti collaterali.

map è un'astrazione di trasformazione dell'elenco funzionale. Lo usi per prendere una sequenza di qualcosa e trasformarla in una sequenza di qualcos'altro.

Stai cercando di usarlo come iteratore. Non farlo. :)

Ecco un esempio di come è possibile utilizzare la mappa per creare l'elenco desiderato. Ci sono soluzioni più brevi (userò solo le comprensioni), ma questo ti aiuterà a capire quale mappa fa un po 'meglio:

def my_transform_function(input):
    return [input, [1, 2, 3]]

new_list = map(my_transform, input_list)

Si noti a questo punto, hai fatto solo una manipolazione dei dati. Ora puoi stamparlo:

for n,l in new_list:
    print n, ll

- Non sono sicuro di cosa intendi con "senza loop". fp non si tratta di evitare i loop (non è possibile esaminare tutti gli elementi in un elenco senza visitarli). Si tratta di evitare effetti collaterali, quindi scrivere meno bug.


12
>>> from itertools import repeat
>>> for foo, bars in zip(foos, repeat(bars)):
...     print foo, bars
... 
1.0 [1, 2, 3]
2.0 [1, 2, 3]
3.0 [1, 2, 3]
4.0 [1, 2, 3]
5.0 [1, 2, 3]

11
import itertools

foos=[1.0, 2.0, 3.0, 4.0, 5.0]
bars=[1, 2, 3]

print zip(foos, itertools.cycle([bars]))

Questo è il più semplice e correttamente funzionante. per favore accettalo come risposta
Phyo Arkar Lwin,

1
Questo è solo codice. Non c'è spiegazione. Molti utenti non comprendono il significato di questa risposta. @PhyoArkarLwin
ProgramFast

6

Ecco una panoramica dei parametri della map(function, *sequences)funzione:

  • function è il nome della tua funzione.
  • sequencesè un numero qualsiasi di sequenze, che di solito sono elenchi o tuple. mapeseguirà l'iterazione simultaneamente su di essi e fornirà i valori correnti a function. Ecco perché il numero di sequenze dovrebbe essere uguale al numero di parametri per la tua funzione.

Sembra che tu stia cercando di iterare alcuni functionparametri, ma di mantenerne costanti altri e sfortunatamente mapnon lo supporta. Ho trovato una vecchia proposta per aggiungere una simile funzionalità a Python, ma il costrutto della mappa è così pulito e consolidato che dubito che qualcosa del genere verrà mai implementato.

Usa una soluzione alternativa come variabili globali o comprensioni di elenchi, come altri hanno suggerito.


0

Questo lo farebbe?

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest2(bar):
  print bar

def maptest(foo):
  print foo
  map(maptest2, bars)

map(maptest, foos)

1
Potresti voler chiamare il parametro per maptest2 () in qualche modo come 'bars'. Il singolare barimplica che sta ricevendo un valore ripetuto, quando in realtà vuoi l'intero elenco.
Nikhil Chelliah,

1
In realtà sta ricevendo un valore ripetuto credo.
Chris,

0

Cosa ne pensi di questo:

foos = [1.0,2.0,3.0,4.0,5.0]
bars = [1,2,3]

def maptest(foo, bar):
    print foo, bar

map(maptest, foos, [bars]*len(foos))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.