6.6. Indicizzazione e slicing

Un ndarray puo essere indirizzato in quattro modi: indici singoli, slice, maschere booleane e le forme di assegnazione di ciascuno.

6.6.1. Singoli elementi

L’indicizzazione con parentesi quadre restituisce il valore nella posizione specificata:

a = np.arange(10, dtype=np.uint8)
print(a[0], a[-1])      # 0 9
print(a[1], a[-2])      # 1 8

Gli indici negativi contano dalla fine, come per una list Python. Un indice fuori intervallo solleva IndexError.

Per array di rango superiore, ogni asse riceve un indice. Gli indici vanno all’interno di un’unica coppia di parentesi, separati da virgole:

m = np.arange(9, dtype=np.uint8).reshape((3, 3))
print(m[1, 1])          # 4
print(m[2, 0])          # 6

Quando vengono forniti meno indici degli assi, gli assi non indicizzati restano intatti. Il risultato e una vista a rango ridotto della sorgente:

print(m[0])             # the first row, as a 1-D view of m

6.6.2. Slice

Una slice start:stop:step restituisce una vista dell’array. La vista condivide il buffer di dati sottostante con la sorgente; scrivere attraverso la vista scrive sulla sorgente:

a = np.arange(10, dtype=np.uint8)
v = a[::2]              # array([0, 2, 4, 6, 8], dtype=uint8)
v[0] = 99
print(a)
# array([99, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

Quando serve un buffer indipendente, copy() ne produce uno esplicitamente.

Lo slicing si estende naturalmente alle dimensioni superiori. Ogni asse riceve la propria slice:

m = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]], dtype=np.uint8)

m[0]            # first row
m[0, :2]        # first two elements of row 0
m[:, 0]         # column 0 (still 2-D in ulab)
m[-1]           # last row
m[::2, ::2]     # every other row, every other column

Mescolare un intero (indice singolo, elimina l’asse) e una slice (mantiene l’asse) e consentito ed e il modo in cui si scrive normalmente l’accesso a una singola riga o colonna.

6.6.3. Maschere booleane

Un array booleano della stessa forma della sorgente seleziona gli elementi dove la maschera e True. L’indicizzazione booleana funziona attualmente su array 1-D; gli input di rango superiore sollevano NotImplementedError

a = np.arange(9, dtype=np.float)
mask = a < 5
print(a[mask])

Output:

array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)

La maschera e un normale ndarray di tipo bool, quindi funziona qualsiasi espressione che ne produca uno:

b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)
a = np.arange(9, dtype=np.uint8)
print(a[a * a > np.sin(b) * 100.0])

L’indicizzazione booleana restituisce una copia. Gli elementi selezionati si trovano in qualunque posizione la maschera sia True – non a un passo regolare attraverso la sorgente – quindi non esiste alcun descrittore che una vista potrebbe usare per indirizzarli, e il risultato viene materializzato nel proprio buffer.

6.6.4. Indicizzazione con array di interi

Passando una lista o un array di indici tra parentesi si selezionano quegli elementi in un solo passaggio:

a = np.array([10, 20, 30, 40, 50], dtype=np.uint8)
a[[0, 2, 4]]
# array([10, 30, 50], dtype=uint8)

Il risultato e una copia; gli elementi selezionati non condividono piu lo spazio di memoria con la sorgente. La stessa forma funziona alla sinistra di un’assegnazione:

a[[0, 2, 4]] = 0
# array([0, 20, 0, 40, 0], dtype=uint8)

take() (trattata in Selezione e riorganizzazione) e la forma funzionale della stessa operazione e accetta una parola chiave out= per un uso senza allocazioni in un ciclo di streaming.

6.6.5. Assegnazione di slice

Le slice e le maschere compaiono alla sinistra di un’assegnazione cosi come alla destra. Il lato destro puo essere uno scalare, un altro array o una vista:

m = np.zeros((3, 3), dtype=np.uint8)
m[0]      = 1            # whole row 0 set to 1
m[:, 2]   = 3            # whole column 2 set to 3
m[1, 1:3] = [7, 8]       # row 1, columns 1 and 2

Le maschere booleane alla sinistra sostituiscono gli elementi che soddisfano la condizione:

a = np.arange(9, dtype=np.uint8)
a[a < 3] = 99
# array([99, 99, 99, 3, 4, 5, 6, 7, 8], dtype=uint8)

a = np.arange(9, dtype=np.uint8)
b = np.array(range(9)) + 12
a[b < 15] = b[b < 15]
# array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)

6.6.6. Perche l’assegnazione di slice e importante su una camera

L’assegnazione di slice scrive attraverso un array gia esistente. Nessun nuovo array viene allocato. Questa e la differenza tra:

out = a + b              # makes a new array the size of a
out = out * 2            # makes another new array

e:

out[:] = a               # writes into the existing out
out   += b               # in place
out   *= 2               # in place

La prima versione chiede alla camera RAM per due nuovi array; la seconda versione non chiede nulla. Su un microcontrollore con RAM limitata quella differenza e spesso la differenza tra uno script che gira comodamente e uno che esaurisce la memoria.

Prestazioni tratta lo schema in dettaglio. La regola importante per ora e che l’assegnazione di slice, gli operatori aritmetici in-place (+=, *=, …) e la parola chiave out= sulle funzioni universali sono i tre strumenti che rendono possibili gli aggiornamenti senza allocazioni.