2009-12-29 2 views
33

Questo è relativo a quanto segue: (In codice Python)Quando dovrei usare una mappa invece di un ciclo For?

for i in object: 
    doSomething(i) 

contro

map(doSomething, object) 

Entrambi sono facili da capire, e breve, ma v'è alcuna differenza di velocità? Ora, se doSomething aveva un valore di ritorno, dovevamo controllare che fosse restituito come una lista dalla mappa, e nel ciclo for potremmo creare il nostro elenco o controllarne uno alla volta.

for i in object: 
    returnValue = doSomething(i) 
    doSomethingWithReturnValue(returnValue) 

contro

returnValue = map(doSomething, object) 
map(doSomethingWithReturnValue, returnValue) 

Ora, mi sento i due divergono un po '. Le due funzioni doSomethingWithReturnValue possono essere diverse in base a se controllarle al volo mentre attraversiamo il ciclo o se verificarle tutte in una volta alla fine producono risultati diversi. Sembra anche che il ciclo for funzionerebbe sempre, forse più lentamente, dove la mappa funzionerebbe solo in determinati scenari. Certo, potremmo fare contorsioni per fare entrambi i lavori, ma il punto è evitare questo tipo di lavoro.

Quello che sto cercando è uno scenario in cui la funzione di mappatura brilla veramente in confronto a un ben fatto per il ciclo di prestazioni, leggibilità, manutenibilità o velocità di implementazione. Se la risposta è davvero non c'è una grande differenza, quindi mi piacerebbe sapere quando in pratica le persone usano l'una o l'altra o se è davvero completamente arbitraria e imposta dagli standard di codifica in base alla propria istituzione.

Grazie!

+2

È possibile utilizzare la comprensione elenco/dizionario/set o un generatore anziché una mappa, a seconda di cosa fa doSomething(). –

+2

non provate a modificare le prestazioni preliminari. Prenderò sempre l'opzione migliore leggibile. Il runtime successivo mostrerà se le prestazioni di showstopper sono un problema e hai migliorato la velocità o l'utilizzo delle risorse. –

risposta

22

map è utile quando si desidera applicare la funzione a ogni elemento di un iterabile e restituire un elenco dei risultati. Questo è più semplice e più conciso rispetto all'uso di un ciclo for e alla costruzione di un elenco.

for è spesso più leggibile per altre situazioni, e in Lisp c'erano molti costrutti di iterazione che sono stati scritti basicamente usando le macro e la mappa. Pertanto, nei casi in cui map non si adatti, utilizzare un ciclo for.

In teoria, se disponessimo di un compilatore/interprete abbastanza intelligente da utilizzare più cpus/processori, è possibile implementare più rapidamente map in quanto le diverse operazioni su ciascun elemento potrebbero essere eseguite in parallelo. Al momento non credo che questo sia il caso.

+0

PLINQ (C#) può farlo però. –

+12

Perché il passato? Lisp è vivo e vegeto. – Svante

+3

In realtà 'map' batte' for' nelle prestazioni anche in thread singolo perché il ciclo è scritto in C. Vedi il mio post per il test di velocità. – iamamac

7

basta usare le list comprehensions: sono più pitoni. Hanno anche una sintassi simile alle espressioni del generatore che rende facile passare da uno all'altro. Non è necessario modificare nulla durante la conversione del codice in py3k: map restituisce un iterabile in py3k e dovrai modificare il codice.

se non ci si preoccupa dei valori di ritorno, basta non nominare il nuovo elenco, è necessario utilizzare i valori restituiti una volta nel codice per passare alle espressioni del generatore e una sola comprensione di lista alla fine.

0

EDIT: Non mi rendevo conto che map uguale itertools.imap dopo Python 3.0. Quindi la conclusione qui potrebbe non essere corretta. Rieseguirò il test su python 2.6 domani e posterò i risultati.

Se doSomething è molto "piccolo", map può essere molto più veloce di for loop o un elenco-di comprensione:

# Python 3.1.1 (r311:74483, Aug 17 2009, 17:02:12) [MSC v.1500 32 bit (Intel)] on win32 

from timeit import timeit 

do = lambda i: i+1 

def _for(): 
    for i in range(1000): 
    do(i) 

def _map(): 
    map(do, range(1000)) 

def _list(): 
    [do(i) for i in range(1000)] 

timeit(_for, number=10000) # 2.5515936921388516 
timeit(_map, number=10000) # 0.010167432629884843 
timeit(_list, number=10000) # 3.090125159839033 

Questo perché map è scritto in C, mentre for loop e esecuzione di list-comprehension in una macchina virtuale python.

+1

Non so da dove prendi i tuoi numeri, ma nel mio caso (Python 2.6) il ciclo for è più veloce di circa il 5%. Il tuo codice non è nemmeno corretto poiché _list fa meno iterazioni. Le enormi differenze che hai indicano che qualcosa è seriamente sbagliato nella tua configurazione. – interjay

+0

questo è semplicemente ridicolo. i tuoi codici non sono affatto equivalenti. leggi la mia risposta anche se il codice è diverso, è un vero peccato che non lo vedi – SilentGhost

+0

Spiacente c'è un errore di battitura quando ho copiato il codice e ho formattato. Corro Python 3.1.1 sul mio PC Core Duo 4300, 'map' batte in modo significativo gli altri due. – iamamac

10

Conoscete il modulo timeit? Di seguito sono riportati alcuni tempi. -s esegue una configurazione unica, quindi il comando viene ripetuto e il miglior tempo registrato.

1> python -m timeit -s "L=[]; M=range(1000)" "for m in M: L.append(m*2)" 
1000 loops, best of 3: 432 usec per loop 

2> python -m timeit -s "M=range(1000);f=lambda x: x*2" "L=map(f,M)" 
1000 loops, best of 3: 449 usec per loop 

3> python -m timeit -s "M=range(1000);f=lambda x:x*2" "L=[f(m) for m in M]" 
1000 loops, best of 3: 483 usec per loop 

4> python -m timeit -s "L=[]; A=L.append; M=range(1000)" "for m in M: A(m*2)" 
1000 loops, best of 3: 287 usec per loop  

5> python -m timeit -s "M=range(1000)" "L=[m*2 for m in M]" 
1000 loops, best of 3: 174 usec per loop 

Nota che sono tutti simili tranne gli ultimi due. È la funzione chiama (L.append, o f (x)) che influisce gravemente sui tempi. Nel n. 4 la ricerca di L.append è stata eseguita una volta nel setup. In # 5 viene utilizzato un elenco comp con chiamate senza funzione.

+0

Penso che tu ti stia riferendo al mio post. Sì, ho trovato il grave problema che 'map' restituisce gli iteratori in py3k, ma non penso che ci sia qualcosa di sbagliato in' timeit', 'range' restituisce gli iteratori quindi non c'è un piccolo impatto che non lo metta nella fase di setup. – iamamac

+0

> python3 -m timeit "[m per m in range (1000)]" 10000 loop, migliore di 3: 114 usec per loop > python3 -m timeit -s M = elenco (intervallo (1000)) "[ m per M in M] " 10000 loop, meglio di 3: 83 usec per loop C'è una differenza significativa nella costruzione della lista solo una volta. –