Come mi allontano dalla scuola di pensiero "for-loop"?


79

Questa è una domanda piuttosto concettuale, ma speravo di ottenere qualche buon consiglio in merito. Gran parte della programmazione che faccio è con array ( NumPy ); Spesso devo abbinare gli elementi in due o più array di dimensioni diverse e la prima cosa a cui vado è un for-loop o, peggio ancora, un for-loop nidificato. Voglio evitare il più possibile i for-loop, perché sono lenti (almeno in Python).

So che per molte cose con NumPy ci sono comandi predefiniti che devo solo ricercare, ma voi (come programmatori più esperti) avete un processo di pensiero generale che viene in mente quando dovete iterare qualcosa?

Quindi ho spesso qualcosa del genere, che è orribile e voglio evitarlo:

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]

So che ci sono molti modi diversi per raggiungere questo obiettivo in particolare, ma sono interessato a un metodo di pensiero generale, se esiste.


10
Stai cercando una programmazione funzionale : espressioni lambda, funzioni di ordine superiore, generazione di espressioni ecc. Google quelle.
Kilian Foth,

42
I want to avoid for-loops as much as possible because they are slow (at least in Python).Sembra che tu stia risolvendo il problema sbagliato qui. Se devi iterare su qualcosa, devi iterare su qualcosa; subirai un simile colpo di prestazione, indipendentemente dal costrutto Python che usi. Se il tuo codice è lento non è perché hai dei forloop; è perché stai facendo un lavoro non necessario o facendo un lavoro sul lato Python che potrebbe essere fatto sul lato C. Nel tuo esempio stai facendo un lavoro extra; avresti potuto farlo con un loop invece di due.
Doval,

24
@Doval Purtroppo no - in NumPy . Un Python per loop che esegue un'aggiunta elementwise può essere facilmente più volte (!) Più lento dell'operatore NumPy vettorializzato (che non è solo scritto in C ma usa istruzioni SSE e altri trucchi) per dimensioni di array realistiche.

46
Alcuni dei commenti sopra sembrano fraintendere la domanda. Durante la programmazione in NumPy, si ottengono i migliori risultati se è possibile vettorializzare il calcolo , ovvero sostituire i cicli espliciti in Python con operazioni a matrice intera in NumPy. Questo è concettualmente molto diverso dalla normale programmazione in Python e richiede tempo per imparare. Quindi penso che sia ragionevole per l'OP chiedere consigli su come imparare a farlo.
Gareth Rees,

3
@PieterB: Sì, esatto. La "vettorializzazione" non equivale alla "scelta dell'algoritmo migliore". Sono due fonti separate di difficoltà nel trovare implementazioni efficienti e quindi è meglio pensarci una alla volta.
Gareth Rees,

Risposte:


89

Questa è una difficoltà concettuale comune quando si impara a usare NumPy in modo efficace. Normalmente, l'elaborazione dei dati in Python è espressa al meglio in termini di iteratori , per mantenere basso l'uso della memoria, massimizzare le opportunità di parallelismo con il sistema I / O e fornire riutilizzo e combinazione di parti di algoritmi.

Ma NumPy capovolge tutto: l'approccio migliore è quello di esprimere l'algoritmo come una sequenza di operazioni a matrice intera , per ridurre al minimo il tempo impiegato nell'interprete Python lento e massimizzare il tempo impiegato nelle routine NumPy compilate rapidamente.

Ecco l'approccio generale che seguo:

  1. Conservare la versione originale della funzione (di cui si è certi che sia corretta) in modo da poterla testare rispetto alle versioni migliorate sia per correttezza che velocità.

  2. Lavora da dentro: cioè inizia con il ciclo più interno e vedi se può essere vettorializzato; poi quando lo hai fatto, vai fuori di un livello e continua.

  3. Trascorrere molto tempo a leggere la documentazione di NumPy . Ci sono molte funzioni e operazioni lì dentro e non sempre hanno un nome brillante, quindi vale la pena conoscerle. In particolare, se ti ritrovi a pensare "se solo ci fosse una funzione che ha fatto cose del genere", allora vale la pena spendere dieci minuti per cercarla. Di solito è lì da qualche parte.

Non c'è sostituto per la pratica, quindi ti darò alcuni esempi di problemi. L'obiettivo di ogni problema è di riscrivere la funzione in modo che sia completamente vettorializzata : cioè, in modo che consista in una sequenza di operazioni NumPy su interi array, senza loop Python nativi (no foro whileistruzioni, senza iteratori o comprensioni).

Problema 1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

Problema 2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

Problema 3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

Spoiler di seguito. Otterrai i migliori risultati se provi te stesso prima di guardare le mie soluzioni!

risposta 1

np.sum (x) * np.sum (y)

Risposta 2

np.sum (np.searchsorted (np.sort (x), y))

Risposta 3

np.where (x == mancante, valore, x)


Aspetta, c'è un errore di battitura nell'ultima risposta o NumPy modifica il modo in cui Python interpreta il codice?
Izkata,

1
@Izkata Non modifica nulla di per sé, ma le operazioni logiche applicate agli array sono definite per restituire array booleani.
sapi

@sapi Ah, mi sono perso quello che sta succedendo nei doctest, ho pensato che fossero semplicilist
Izkata,

Forse dovrebbe esserci un modo per incorporare APL?

Adoro come fai i compiti.
Koray Tugay,

8

Per rendere le cose più veloci devi leggere le tue strutture dati e usare quelle appropriate.

Per dimensioni non banali di array piccolo e array grande (diciamo piccolo = 100 elementi e grande = 10.000 elementi) un modo per farlo è quello di ordinare l'array piccolo, quindi iterare sull'array grande e utilizzare una ricerca binaria per trovare elementi corrispondenti nel piccolo array.

Ciò renderebbe la massima complessità temporale, O (N log N) (e per piccoli array piccoli e array molto grandi è più vicino a O (N)) dove la soluzione del loop nidificato è O (N ^ 2)

Però. quali strutture di dati siano più efficienti dipende fortemente dal problema reale.


-3

È possibile utilizzare un dizionario per ottimizzare le prestazioni in modo significativo

Questo è un altro esempio:

locations = {}
for i in range(len(airports)):
    locations[airports["abb"][i][1:-1]] = (airports["height"][i], airports["width"][i])

for i in range(len(uniqueData)):
    h, w = locations[uniqueData["dept_apt"][i]]
    uniqueData["dept_apt_height"][i] = h
    uniqueData["dept_apt_width"][i] = w

2
Questo chiaramente usa ancora la scuola di pensiero "for-loop".
8bittree,
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.