În cadrul episodului VIII am văzut că putem întrerupe execuția unei funcții folosind instrucțiunea return. Uneori, s-ar putea să avem nevoie de întreruperea unei bucle. Dacă suntem în interiorul unei funcții și este OK să se înterupă execuția întregii funcții, putem folosi return. Dar, în multe situații vrem să continuăm cu intrucțiunile care urmează după buclă.
Să vedem un exemplu: o modalitate simplă (dar ineficientă) de a verifica dacă un număr este prim este numărarea divizorilor săi. Dacă sunt exact doi, atunci numărul este prim. O funcție care afișează dacă un număr primit ca parametru este prim, ar putea arăta astfel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def prim(n: Int): Unit = { var divizori = 0 var d = 1 while (d <= n) { if (n % d == 0) { divizori += 1 } d += 1 } if (divizori == 2) { println(n + " este prim!") } else { println(n + " nu este prim!") } } |
Avem o variabilă divizori pe care o folosim pentru a număra divizorii și o variabilă d care va conține potențialii divizori (toate numerele cuprinse între 1 și n). La fiecare pas verificăm dacă n se împarte exact la valoarea curentă a lui d. Dacă da, creștem numărul divizorilor. Indiferent dacă d a fost sau nu divizor a lui n, trecem la următorul potențial divizor incrementând valoarea d. Ne oprim doar atunci când d devine mai mare decât n (cu alte cuvinte, continuăm cât timp d este mai mic sau egal cu n). La sfârșit verificăm dacă numărul divizorilor este exact 2 și scriem un mesaj corespunzător ( n este sau nu prim).
Funcționează pentru orice număr pozitiv; dar, putem observa destul de ușor că, dacă am ajuns să avem trei divizori, am vrea să ne oprim. Nu putem folosi instrucțiunea return fiindcă s-ar întrerupe execuția întregii funcții și nu am mai afișa mesajul de la final. Funcția ar putea arăta așa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def prim(n: Int): Unit = { var divizori = 0 var d = 1 while (d <= n) { if (n % d == 0) { divizori += 1 } if (divizori == 3) { return } d += 1 } if (divizori == 2) { println(n + " este prim!") } else { println(n + " nu este prim!") } } |
Mesajul nu va mai fi afișat decât pentru numerele prime (și pentru zero sau unu). Am putea să modificăm variabilele implicate în evaluarea condiției și să forțăm astfel întreruperea. De exemplu, am putea modifica linia 9 astfel încât d să devină mai mare decât n:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def prim(n: Int): Unit = { var divizori = 0 var d = 1 while (d <= n) { if (n % d == 0) { divizori += 1 } if (divizori == 3) { d = n + 1 } d += 1 } if (divizori == 2) { println(n + " este prim!") } else { println(n + " nu este prim!") } } |
Execuția buclei nu se oprește imediat; incrementarea din linia 11 se va execută. În cazul nostru această incrementare este inofensivă ( d va deveni n + 2 și condiția de continuare a buclei nu va mai fi îndeplinită). De fapt, puteam pune doar d = n în linia 9 și incrementarea din linia 11 ar fi făcut în așa fel încât condiția să nu mai fie îndeplinită. Dar, nu este deloc bine să ne bazăm pe astfel de efecte colaterale.
Dar, în unele situații instrucțiunile care urmează după momentul în care dorim întreruperea ar putea avea efecte nedorite. Poate vrem ca execuția buclei să se oprească de tot.
De obicei, întrerupem execuția în cazul îndeplinirii unei condiții, deci avem un if. Putem pune tot ce urmează în else și atunci totul va funcționa așa cum ne dorim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def prim(n: Int): Unit = { var divizori = 0 var d = 1 while (d <= n) { if (n % d == 0) { divizori += 1 } if (divizori == 3) { d = n + 1 } else { d += 1 } } if (divizori == 2) { println(n + " este prim!") } else { println(n + " nu este prim!") } } |
Soluția este acceptabilă în cazul în care corpul buclei nu are o structură foarte complicată. Dar, există și altfel de situații: poate condiția de înterupere este verificată în interiorul unui alt if și după acest if exterior există alte instrucțiuni. Soluția noastră cu else nu ar funcționa; am avea nevoie de alte "artificii" pentru a evita executarea acestor instrucțiuni.
Din fericire, avem la dispoziție o constructie care face exact ce vrem: breakable ... break. Codul nostru, acum arata cam asa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import scala.util.control.Breaks._ def prim(n: Int): Unit = { var divizori = 0 var d = 1 breakable({ while (d <= n) { if (n % d == 0) { divizori += 1 } if (divizori == 3) { break } d += 1 } }) if (divizori == 2) { println(n + " este prim!") } else { println(n + " nu este prim!") } } |
De fapt, toată bucla noastră este transmisă ca parametru funcției breakable, iar în locul în care dorim să întrerupem execuția buclei, folosim instrucțiunea break. Pentru a putea folosi această construcție, avem nevoie de instrucțiunea import scala.util.control.Breaks._. Acest fel de instrucțiuni vor fi prezentate în alt episod dedicat limbajului Scala.
Atât pentru varianta aceasta cât și pentru unele dintre cele anterioare putem face niște modificări care să îmbunătățească puțin performanța. Nu are niciun rost să verificăm dacă am ajuns la trei divizori decât dacă tocmai am incrementat numărul acestora. Funcția rescrisă ar putea fi următoarea:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import scala.util.control.Breaks._ def prim(n: Int): Unit = { var divizori = 0 var d = 1 breakable({ while (d <= n) { if (n % d == 0) { divizori += 1 if (divizori == 3) { break } } d += 1 } }) if (divizori == 2) { println(n + " este prim!") } else { println(n + " nu este prim!") } } |
Există numeroase alte optimizări care pot fi făcute; așa cum am spus la început, această modalitate de verificare a primalității unui număr este ineficientă.
Ce se întâmplă dacă avem bucle imbricate? Instrucțiunea break are efect doar în interiorul buclei din care face parte; buclele "exterioare" nu sunt afectate. Dacă dorim să întrerupem toate buclele trebuie să căutăm alte soluții.
Uneori nu dorim să întrerupem execuția buclei, ci doar să oprim execuția iterației curente (să sărim direct la evaluarea condiției). Acest lucru se realizează tot cu ajutorul construcției breakable ... break. Ea este utilă dacă la fiecare pas executăm anumite operațiuni, dar atunci când o anumită condiție este îndeplinită nu are rost să le continuăm.
Având la dispoziție o funcție prim ușor modificată (returnează o valoarea booleană care ne spune dacă un număr este sau nu prim), am putea dori să afișăm suma și produsul numerelor prime cuprinse între 1 și 20. Putem găsi multe soluții, printre care și una care folosește breakable ... break:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import scala.util.control.Breaks._ object Main extends App { def prim(n: Int): Boolean = { var divizori = 0 var d = 1 breakable({ while (d <= n) { if (n % d == 0) { divizori += 1 if (divizori == 3) { break } } d += 1 } }) if (divizori == 2) { return true } else { return false } } var suma = 0 var produs = 1 for (i <- 1 to 20) { breakable({ if (!prim(i)) { break } suma += i produs *= i }) } println("Suma: " + suma) println("Produsul: " + produs) } |
De această dată conținutul buclei for se transmite ca parametru funcției breakable.
Ca și o concluzie, instrucțiunea break întrerupe execuția blocului transmis ca parametru funcției breakable.
Putem face câteva observații, care nu sunt neapărat legate de breakable ... break:
- Datorită faptului că prim returnează o valoare booleană și rezultatul evaluării unei condiții este o astfel de valoare, codul din liniile 17 - 21 poate fi înlocuit cu un simplu return divizori == 2.
- Noua funcție prim returnează doar valoarea booleană, fără a mai afișa nimic; din acest motiv ar fi fost corect ca în linia 11 să folosim direct return false pentru a întrerupe execuția funcției, fără să ne mai intereseze bucla (evident, și execuția ei ar fi fost întreruptă); la sfârșit am fi putut presupune că numărul este prim dacă nu este zero sau unu și am fi putut avea în linia 17 return n > 1.
- Am calculat o sumă și un produs de mai multe numere; calculul se efectuează pas cu pas, adăugând sau înmulțind de fiecare dată câte un număr. Valorile inițiale sunt date de elementele neutre ale operațiilor (zero pentru adunare și unu pentru înmulțire). Folosind acest model puteți încerca să scrieți secvențe de cod care efectuează diverse calcule:
- suma numerelor impare cuprinse între 1 și 100
- suma pătratelor numerelor cuprinse între 1 și 1000
- produsul numerelor pare cuprinse între 1 și 10
- diferența dintre suma pătratelor numerelor pare cuprinse între 1 și 100 și suma pătratelor numerelor impare cuprinse între 1 și 100.
Va urma
Am văzut în cadrul acestui episod că putem lucra cu mai multe numere, dar o variabilă poate conține la un moment dat unul singur. Dar, în episodul II am văzut că un string conține mai multe caractere. În cadrul următorului episod vom vedea cum poate fi extins acest concept și pentru alte tipuri.