for
Proviamo a calcolare la somma degli elementi di un vettore
Come fare?
Proviamo a calcolare la somma degli elementi di un vettore
s = 0;
??? % Non sappiamo "spostarci" su un vettore
s = s + ??? % E neanche accedere ad un elemento
Ancora non ci siamo: vediamo di rimediare...
Abbiamo visto come costruire un vettore in Octave:
[1, 3, 5]
Quando si implementano algoritmi può essere utile:
In Octave lo si può fare utilizzando la notazione:
<vettore>(<espressione>)
<vettore>
è una espressione che denota un vettore<espressione>
è deve denotare un indice (un intero)Gli elementi del vettore (o celle dell'array) sono numerate:
1
Per ottenere il numero di elementi si può usare la funzione:
length(<array>)
length
fa cose diverse se invoca su altri tipi di datoGli elementi del vettore (o celle dell'array) sono numerate:
1
In alternativa, si può accedere direttamente all'ultimo elemento con:
<array>(end)
end
funziona solo se usato come indiceGli elementi del vettore vengono trattati come variabili
Supponiamo di avere:
x = [1,2,3,5,6,7]
Possiamo ottenere il valore di un element con:
x(4) % denota il valore 5
E modificare un elemento con:
x(4) = 2 % assegna il valore 2 alla 4° cella
Sempre assumendo di avere:
x = [1,2,3,5,6,7]
Se usiamo un indice < 1
, otteniamo un errore:
x(0) % errore
x(0) = 2 % errore
Se usiamo un indice > length(x)
, invece:
x(9) % ancora un errore
x(9) = 1 % Otteniamo x = [1,2,3,5,6,7,0,0,1]
0
le celle intermedieIl simbolo []
denota un vettore/matrice vuoto
Possiamo definire un vettore vuoto:
x = [] % x è vuoto, length(x) è 0
Possiamo estendere un vettore vuoto:
x(3) = 10 % otteniamo x = [0, 0, 10]
Possiamo cancellare un elemento assegnandovi []
:
x(2) = [] % otteniamo x = [0, 10]
L'estensione e []
consentono di manipolare la lunghezza
Vediamo come usare queste informazioni per il nostro algoritmo
v
v = % il nostro vettore
s = 0;
<facciamo variare una variabile ii da 1 a length(v)>
s = s + v(ii)
ii
avrà raggiunto il valore length(v)
...s
conterrà la sommaCi rimane da capire come gestire l'indice ii
for
Octave ci permette di farlo utilizzando una istruzione
Un esempio:
Vediamo ora un nuovo tipo di istruzione:
for
La nostra prima istruzione di iterazione si chiama "ciclo for
"
Sintassi:
for <variabile> = <vettore>
<corpo>
end % o anche endfor (incompatibile con matlab)
<corpo>
è una sequenza di istruzioni<vettore>
Ad ogni ripetizione (i.e. iterazione):
<variabile>
assume il valore di un elemento di vettore
Nota: il simbolo "=
" in questo caso non ha il solito significato...
for
Un esempio semplice:
for v = [2, 4, 6]
v % denota il valore di "v"
end
Quando inserite una istruzione for
nella finestra dei comandi:
end
Solo allora l'istruzione for
è completa (e quindi eseguibile)
for
e Somma di un VettoreIl ciclo for
ci permette di codificare il nostro algoritmo:
Supponendo che il nostro vettore si chiami v
s = 0;
for ii = 1:length(v)
s = s + v(ii)
end
Oppure, in modo ancora più semplice:
s = 0;
for w = v % w assume uno per uno i valori di v
s = s + w
end
La funzione predefinita sum
in Octave fa (più o meno) questo
Si può usare un vettore per accedere ad elementi multipli:
La sintassi è:
<vettore>(<vettore di indici>)
Per esempio:
x = [2, 4, 6, 8];
ii = [2, 3]; % "ii" per evitare di rendere
% inaccessibile l'unità immaginaria
x(ii) % denota [4, 6]
x
, agli indici specificati da ii
Supponiamo di voler sommare le celle adiacenti di:
a = [1, 2, 3, 4, 5]
Possiamo usare:
a(1:end-1) + a(2:end)
a(1:end-1)
denota [1, 2, 3, 4]
a(2:end)
denota [2, 3, 4, 5]
Il risultato è:
[3, 5, 7, 9] % [1, 2, 3, 4] + [2, 3, 4, 5]
Oppure, supponiamo di voler sommare le celle pari e dispari:
a = [1, 2, 3, 4, 5, 6]
Possiamo usare:
a(1:2:end) + a(2:2:end)
a(1:2:end)
denota [1, 3, 5]
a(2:2:end)
denota [2, 4, 6]
Il risultato è:
[3, 7, 11]
L'accesso mediante vettori funziona anche in assegnamento!
Sintassi:
<vettore>(<vettore di indici>) = <vettore>
Per esempio:
x = [2, 4, 6, 8];
ii = [2, 3];
x(ii) = [10, 11] % otteniamo x = [2, 10, 11, 8]
Se le dimensioni non combaciano, Octave fa broadcasting:
x(ii) = 0 % otteniamo x = [2, 0, 0, 8]
Alziamo un po' il livello di difficoltà
Supponiamo di voler sommare gli elementi di una matrice
Un possibile algoritmo:
M = % la nostra matrice
s = 0
<per ogni elemento della matrice>
s = s + <un elemento della matrice>
Come iterare sugli elementi di una matrice?
for
...Vediamo cosa succede iterando con un for
su una matrice:
M = [1, 2, 3; 4, 5, 6; 7, 8, 9]; % [1, 2, 3]
% [4, 5, 6]
% [7, 8, 9]
for w = M
w % giusto per stampare w
end
La variabile w
assume i valori:
[1; 4; 7]
, poi [2; 5; 8]
, poi [3; 6; 9]
Ossia le colonne di M
!
Come possiamo fare per iterare sugli elementi?
Una soluzione semplice: usare due cicli for
M = [1, 2, 3; 4, 5, 6; 7, 8, 9];
for c = M % itero sulle colonne
for v = c % poi sugli elementi della colonna
v
end % fine ciclo interno
end % fine ciclo esterno
for
è una istruzionefor
può contenere un altro for
!I due cicli si dicono innestati
end
si riferisce sempre all'ultimo for
non ancora chiusoEcco quindi un possibile codice per il nostro algoritmo:
M = % la nostra matrice
s = 0
for c = M
for v = c
s = s + v
end
end
Notate come il corpo di ogni for
è leggermente indentato?
In alternativa, possiamo usare la funzione sum
Ci vuole però qualche accortezza:
M = [1, 2; 3, 4] % [1, 2]
% [3, 4]
sum(M) % denota [4, 6]
sum
calcola la somma colonna per colonnaPer avere la somma di tutti gli elementi:
sum(sum(M)) % denota 10
sum
ottiene la somma totaleE se volessi iterare riga per riga?
M = % la nostra matrice
s = 0
for ii = 1:<numero di righe>
for jj = 1:<numero di colonne>
s = s + <elemento in posizione i,j>
end
end
Occorre un modo per:
Accedere ad elemento arbitrario di una matrice è facile:
Sintassi:
<matrice>(<indice riga>,<indice colonna>)
Per esempio:
a = [2, 4; 6, 8]; % corrisponde a [2, 4]
% [6, 8]
a(1,2) % denota 4
a(2,2) % denota 8
a(1, 1) = 0 % assegna 0 nella posizione 1,1
Per ottenere le dimensioni di una matrice invece si usa:
rows(<matrice>) % denota il numero di righe
columns(<matrice>) % denota il numero di colonne
size(<matrice>) % denota entrambi, in un vettore
Qualche esempio:
a = [2, 4; 6, 8]; % corrisponde a [2, 4]
% [6, 8]
rows(a) % denota 2
columns(a) % denota 2
size(a) % denota [2, 2]
Qualche osservazione interessante:
a = [2, 4; 6, 8];
b = [2, 4];
c = 2;
size(a) % denota [2, 2]
size(b) % denota [1, 2]
size(c) % denota [1, 1]
Per noi, parlare di scalari/vettori/matrici sarà sufficiente
Se vogliamo accedere ad una sola riga/colonna:
Usiamo il simbolo :
al posto dell'indice non specificato
<matrice>(2,:) % accede alla seconda riga
<matrice>(:,2) % accede alla seconda colonna
Si può cancellare una una riga/colonna assegnandovi []
<matrice>(1,:) = [] % cancella la prima riga
Qualche esempio:
a = [2, 4; 6, 8]; % corrisponde a [2, 4]
% [6, 8]
a(1,:) % denota [2, 4]
a(:,1) % denota [4; 8]
a(1,:) = [1, 1] % otteniamo [1, 1]
% [6, 8]
a(:,2) = [0; 0] % otteniamo [1, 0]
% [6, 0]
a(1,:) = [] % cancella la prima riga,
% otteniamo [6, 0]
Per accedere a righe/colonne multiple, usiamo vettori di indici
Sintassi:
<matrice>(<vettore righe>, <vettore colonne>)
Qualche esempio:
a = [1, 2, 3; 4, 5, 6; 7, 8, 9]; % [1, 2, 3]
% [4, 5, 6]
% [7, 8, 9]
a([1,3], :) % denota % [1, 2, 3]
% [7, 8, 9]
a([1,3], [1,3]) % denota % [1, 3]
% [7, 9]
Se accediamo ad una matrice con un indice unico:
(abcd)⟷(acbd)
Permette di accedere ad un gruppo arbitrario di elementi:
a = [2, 4; 6, 8]; % [2, 4]
% [6, 8]
a(1) % denota 2
a(2) % denota 6
a(3) % denota 4
a(4) % denota 8
Una conseguenza importante:
La notazione:
<matrice>(:)
Restituisce tutti gli elementi, in forma di vettore
Possiamo usarla per calcolare la somma:
M = [1, 2; 3, 4];
V = M(:) % ottiene V = [1, 3, 2, 4]
sum(V)
Oppure direttamente sum(M(:))
Supponiamo di voler calcolare il massimo di un vettore
Cosa riusciamo a scrivere con le istruzioni che conosciamo?
V = % il nostro vettore
y = V(1) % primo tentativo: max = il primo elemento
for ii = 2:length(V) % iteriamo sul resto del vettore
<se V(ii) è maggiore di y, allora y = V(ii)>
end
Abbiamo bisogno di un costrutto che:
V(ii)
è maggiore di y
y = V(ii)
Questa funzionalità è fornita dalle istruzioni condizionali
if
Noi utilizzare una sola istruzione condizionale, chiamata if
Sintassi:
if <espressione>
<corpo1>
else % opzionale
<corpo2> % opzionale
end % oppure endif (incompatibile con matlab)
<espressione>
può denotare "vero" o "falso"<corpo1>
e <corpo2>
sono sequenze di istruzioni<corpo1>
esegue se <espressione>
denota il valore "vero"<corpo2>
esegue se <espressione>
denota il valore "falso"Ma cosa vuol dire "denotare vero o falso"?
Octave infatti supporta anche valori logici:
Sintassi:
true % vero
false % falso
Vengono restituiti dagli operatori di confronto:
Sintassi e semantica:
<dato> == <dato> % "true" se i dati sono uguali
<dato> ~= <dato> % "true" si i dati sono diversi
<dato> != <dato> % come ~=, incompatibile con matlab
<, <=, >, >= % fanno quello che vi aspettate :-)
Servono a verificare se due dati soddisfino una condizione
Qualche esempio:
c = 2;
c < 3 % denota true
c ~= 2 % denota false
Nel nostro algoritmo:
V = % il nostro vettore
y = V(1)
for ii = 2:length(V)
if V(ii) > y
y = V(ii)
end
end
Per esprimere condizioni complesse usiamo gli operatori logici
Sintassi e semantica:
<dato> & <dato> % true sse i due dati sono true
<dato> | <dato> % true sse almeno un dato è true
~ <dato> % true sse il dato è falso
Qualche esempio:
(x >= 3) & (x < 5) % true se x è tra 3 e 5
(x == 0) | (x > 2)
&&
, ||
...I valori logici sono assimilabili ai numeri 1
("vero") e 0
("falso")
0
corrisponde a "falso"0
corrisponde a veroUn errore da manuale:
if x = 5 % l'operatore di uguaglianza è "=="!
y = y + 1
end
x = 5
denota 5
, che corrisponde a true
!Gli operatori di confronto e logici:
Se applicati a vettori o matrici, operano elemento per elemento
a = [1, 2; 3, 4]; % [1, 2]
% [3, 4]
b = [1, 2, 3];
a < 3 % denota [1, 1]
% [0, 0]
(b > 1) & (b < 3) % [0, 1, 1] & [1, 1, 0]
% quindi [0, 1, 0]
Ci sono due funzioni principali per farlo: all
e any
all
e any
Le funzioni all
e any
hanno il comportamento seguente:
all(<vettore>) % true se tutti gli elementi sono true
any(<vettore>) % true se almeno un elemento è true
0
è true
Qualche esempio:
a = [1, 2, 3];
all(a < 3) % all([1, 1, 0]) --> false
any(a < 2) % any([1, 0, 0]) --> true
Se applicate a matrici, all
ed any
operano colonna per colonna
sum
Possiamo accedere ad un vettore/matrice usando valori logici
Sintassi:
<vettore/matrice A>(<vettore/matrice I>)
Semantica:
A
...I
contiene il valore true
A che serve? Vediamo qualche esempio...
Supponiamo di avere:
a = [1, 2, 3; 4, 5, 6; 7, 8, 9]; % [1, 2, 3]
% [4, 5, 6]
% [7, 8, 9]
Proviamo a selezionare i valori maggiori di 3:
ii = (a > 3) % denota [0, 0, 0]
% [1, 1, 1]
% [1, 1, 1]
a(ii) % denota [4, 5, 6, 7, 8, 9]
In forma compatta:
a(a > 3)
Supponiamo di voler calcolare la somma della serie:
ζ(s)=∞∑n=11ns
Supponiamo di voler calcolare la somma della serie:
ζ(s)=∞∑n=11ns
Proviamo ad abbozzare un algoritmo:
z = 0;
for n = 1:???? % Il problema è qui
z = 1 / n.^s
end
Supponiamo di voler calcolare la somma della serie:
ζ(s)=∞∑n=11ns
Tipicamente: ci si ferma ad un certo livello di precisione
ζk(s)
la nostra approssimazione dopo k
iterazioni|ζk(s)−ζk−1(s)|<ε
Però ancora non sappiamo a priori il numero di iterazioni...
while
Ci serve una istruzione di iterazione capace di:
Una istruzione di questo tipo è il ciclo while
Sintassi:
while <espressione> % vera o falsa
<corpo>
end % oppure endwhile (incompatibile con matlab)
<espressione>
denota true
Possiamo usare while
per scrivere un algoritmo per ζ(s)
:
ζ(s)=∞∑n=11ns
tol = 1e-9; % tolleranza
z = 0; % val. della somma
n = 1;
old_z = -Inf; % vecchio z
while abs(z - old_z) > tol % abs = valore assoluto
old_z = z; % memorizzo il vecchio z
z = z + 1 ./ n.^s;
n = n + 1; % incremento n
end
Inf
è un valore speciale che denota ∞
Il ciclo while
non fornisce garanzie di terminazione
Per esempio:
n = 1;
s = 0;
while n < 10
s = s + n;
end
n
non viene incrementato!Se vi capita, niente panico: basta premere [ctrl+c]
break
e continue
In alcuni casi può essere utile interrompere un ciclo
Lo si può fare con l'istruzione break
...
while true % Di base, non smetto mai di iterare
old_z = z;
z = z + 1 ./ n.^s;
n = n + 1;
if abs(z - old_z) < tol
break
end
end
Le istruzioni condizionali e di iterazione:
Quasi tutti i linguaggi si basano su questi tre metodi di composizione
Riguardiamo un altro il nostro codice per ζ(s)
:
tol = 1e-9;
z = 0;
n = 1;
old_z = -Inf;
while abs(z - old_z) > tol
old_z = z;
z = z + 1 ./ n.^s;
n = n + 1;
end
Per fortuna si può! Anzi, è il modo normale di lavorare in Octave
In Octave, si chiama file di script:
.m
...In sostanza, è un programma scritto in Octave
Un file di script può essere eseguito con la sintassi:
<nome del file senza ".m">[invio]
zeta.m
si esegue con zeta
+ [invio]Eseguire uno script equivale a scrivere le sue istruzioni sul prompt
Scegliete il nome con un filo di attenzione
pi.m
...pi
+[invio]...π
!Soluzione: aggiungete sempre un prefisso/suffisso al nome del file
chXX_nomescript.m
È possibile inserire commenti nel codice:
Sintassi:
% Ecco un commento!
% Finalmente è chiaro cosa siano questi "cosi" :-)
%
viene ignoratoCommentare aiuta a ragionare e ricordare
% When I wrote this, only God and I knew
% what I was doing
% Now, God only knows
Mantenete il codice leggibile
sum(all(a(1:2:end, :)(:) * b')) % UNA BRUTTA IDEA
...
"a = [1, 2, 3, 4, 5, 6, 7, 8, ...
9, 10, 11, 12, 13, 14]
Supponiamo di avere il nostro algoritmo per ζ(s)
in uno script:
s = 3;
tol = 1e-9;
z = 0;
old_z = -1;
n = 1;
while abs(z - old_z) > tol
old_z = z;
z = z + 1 ./ n.^s;
n = n+1;
end
Supponiamo di avere il nostro algoritmo per ζ(s)
in uno script:
s = 3; % <-- il problema è qui
tol = 1e-9;
z = 0;
old_z = -1;
n = 1;
while abs(z - old_z) > tol
old_z = z;
z = z + 1 ./ n.^s;
n = n+1;
end
s = 3
s
, dobbiamo modificare lo scriptζ(s)
va valutata spesso, diventa molto scomodoGli script in Octave sono utili per:
Non sono adatti a definire un algoritmo generico!
Per tali casi possiamo definire una nuova funzione
Per definire una nuova funzione si usa la sintassi:
function <ret> = <nome funz.>(<p1>, <p2>, ...)
<corpo>
end % oppure endfunction (incompatibile con Matlab)
<fn>
è il nome della funzione<pXX>
sono nomi di variabili (si chiamano parametri formali)<ret>
è il nome della variabile da restituireUsiamo come esempio la nostra Zeta di Riemann:
function z = zeta(s)
...
end
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
z
vale circa 1.2
)Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
<ret>
viene copiato nel chiamanteCosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
Cosa succede quando eseguiamo (e.g.) zeta(3)
+[invio]?
ans
(zeta è stata chiamata dal prompt)Alcune osservazioni
1) Se la funzione non definisce una variabile di nome <ret>
:
2) Le variabili definite dalla funzione:
3) Il corpo di una funzione può contenere una chiamata a fuzione
zeta(s)
per esempio chiama gli operatori aritmeticiAbbiamo visto che:
Entrambi si chiamano ambienti
È possibile ispezionare l'ambiente corrente utilizzando i comandi:
who % stampa i nomi delle variabili definite
whos % stampa i nomi + altre informazioni
È possibile eliminare una variabile utilizzando:
clear <nome variabile>
È possibile eliminare tutte le variabili con:
clear all
Ci sono diverse alternative, ma ne considereremo solo una:
Una funzione va definita all'interno di un file di funzione
Un file di funzione è un file di testo che:
.m
Per esempio:
% nel file "zeta.m":
function z = zeta(s)
...
end
Quando proviamo ad invocare (e.g.) zeta(3)
:
zeta.m
"La ricerca avviene all'interno di una serie di cartelle:
Attenzione ai nomi!
sum
in "sum.m
"sum
di Octave