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

78

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

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

Quindi spesso ho qualcosa di simile, 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 diversi modi per ottenere questo risultato in particolare, ma mi interessa un metodo generale di pensiero, se esiste.

    
posta turnip 26.08.2014 - 14:02
fonte

3 risposte

89

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

Ma NumPy capovolge tutto questo: l'approccio migliore è esprimere l'algoritmo come una sequenza di operazioni dell'intero array , per ridurre al minimo la quantità di tempo trascorso nell'interprete Python lento e massimizzare il tempo quantità di tempo trascorso in routine NumPy veloci compilate.

Ecco l'approccio generale che prendo:

  1. Conserva la versione originale della funzione (che sei sicuro sia corretta) in modo che tu possa testarla con le tue versioni migliorate sia per correttezza che per velocità.

  2. Lavora dall'interno verso l'esterno: cioè, inizia con il ciclo più interno e vedi se può essere vettorializzato; poi, quando hai finito, sposta un livello e continua.

  3. Trascorri molto tempo a leggere la documentazione di NumPy . Ci sono un sacco di funzioni e operazioni in là e non sono sempre brillantemente nominati, quindi vale la pena conoscerli. In particolare, se ti trovi a pensare, "se solo ci fosse una funzione che ha fatto questo e così", allora vale la pena spendere dieci minuti per cercarlo. Di solito è lì da qualche parte.

Non c'è alcun sostituto per la pratica, quindi ho intenzione di darti alcuni problemi di esempio. L'obiettivo di ogni problema è di riscrivere la funzione in modo che sia completamente vettorializzato : cioè, in modo che esso sia costituito da una sequenza di operazioni NumPy su interi array, senza loop Python nativi (no for o while istruzioni, nessun iteratore o comprensione).

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 qui sotto. Otterrai i migliori risultati se ti avventuri 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 == missing, value, x)

    
risposta data 26.08.2014 - 16:16
fonte
8

Per rendere le cose più veloci devi leggere le strutture dei dati e utilizzare quelle appropriate.

Per dimensioni non banali di piccoli array e grandi array (diciamo small = 100 elementi e big = 10.000 elementi) un modo per farlo è ordinare l'array piccolo, quindi scorrere su big-array e usare una ricerca binaria per trova gli elementi corrispondenti nel piccolo array.

Ciò renderebbe la massima complessità del tempo, O (N log N) (e per piccoli array di piccole dimensioni e grandi big-array è più vicino a O (N)) dove la soluzione del ciclo nidificato è O (N ^ 2)

Tuttavia. quali strutture dati sono più efficienti dipende in larga misura dal problema reale.

    
risposta data 26.08.2014 - 15:08
fonte
-3

È possibile utilizzare un dizionario per ottimizzare significativamente le prestazioni

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
    
risposta data 12.10.2016 - 11:48
fonte

Leggi altre domande sui tag