O altă sarcină care trebuie uneori realizată cu o matrice este căutarea unei anumite valori în ea. Acest lucru este similar cu răsfoirea unei cărți de bucate până se găsește o anumită rețetă.
Următoarele secțiuni vă prezintă două metode de sortare - o căutare liniară, care se poate efectua fie pe o matrice sortată, fie pe o matrice nesortată și o căutare binară, care este mai rapidă, dar care funcționează numai pe o matrice sortată. De obicei, viteza de lucru pentru executarea unei macrocomenzi, este rareori o problemă.
Realizarea unei căutări liniare într-o matrice
Căutarea liniară este un tip simplu de căutare: începe la începutul matricii și verifică fiecare element până când găsește ținta sau până când ajunge la sfârșitul matricii și raportează că nu a găsit nimic.
Înainte de a executa acest cod, se afișează fereastra Immediate în Editor apăsând pe Ctrl+G sau selectând View > Immediate Window. Această procedură afișează informații în fereastra Immediate, astfel încât să se poată vedea ce se întâmplă și să se determine dacă codul funcționează conform cu scopul dorit.
Fereastra Immediate se folosește pentru a verifica valorile obținute la un moment dat și este preferabilă afișării casetelor de mesaj, ca în secțiunea anterioară. Cu fereastra Immediate, nu se mai face clic pe casetele de mesaje, iar fereastra poate fi derulată, pentru a afișa cât mai multe informații.
O căutare liniară simplă
1. Option Explicit
2. Option Base 1
3.
4. Sub Cautare_Liniara_In_Matrice()
5.
6. 'declara matricea si variabilele de lucru
7. Dim intArray(10) As Integer
8. Dim i As Integer
9. Dim varUserNumber As Variant
10. Dim strMsg As String
11.
12. 'adauga in matrice numere aleatorii din intervalul 0 - 10
13. 'si le afiseaza in fereastra Immediate pentru verificare
14. For i = 1 To 10
15. intArray(i) = Int(Rnd * 10)
16. Debug.Print intArray(i)
17. Next i
18.
19. Loopback:
20. varUserNumber = InputBox _
("Introduceti un numar in intervalul 1-10 pentru cautare:", _
"Cautare liniara ")
21. If varUserNumber = "" Then End
22. If Not IsNumeric(varUserNumber) Then GoTo Loopback
23. If varUserNumber < 1 Or varUserNumber > 10 Then GoTo Loopback
24.
25. strMsg = "Valoarea " & varUserNumber & _
" nu a fost gasita in matrice."
26.
27. For i = 1 To UBound(intArray)
28. If intArray(i) = varUserNumber Then
29. strMsg = "Valoarea " & varUserNumber & _
" a fost gasita la pozitia " & i & " in matrice."
30. Exit For
31. End If
32. Next i
33.
34. MsgBox strMsg, vbOKOnly + vbInformation, "Rezultatul cautarii liniare "
35.
36. End Sub
(Aceste două linii pot fi combinate într-o linie logică prin adăugarea unui caracter de continuare la sfârșitul primei linii și omiterea apostrofului de la începutul liniei următoare, dar codul este mai ușor de citit atunci când a doua linie începe tot cu un caracter de comentariu - apostrof.)
VBA este Flexibil
Aici se poate observa flexibilitatea VBA: Codul cere utilizatorului să introducă un număr și verifică dacă numărul introdus este între 1 și 10 (inclusiv). Chiar dacă acel număr este stocat într-o variabilă de tip Variant în loc de Integer, VBA poate face verificările necesare.
Cum se generează numerele aleatoare
Se poate observa că uneori în matrice apare un element cu valoarea 0 și că nu apare niciodată elementul cu valoarea 10. Cu alte cuvinte, codul Int (Rnd * 10) produce în mod aleator cifre cuprinse între 0 și 9. (Acesta este un produs secundar al rotunjirii efectuate de comanda Int.) Dar comanda Rnd se poate folosi pentru a produce exact intervalul de dorit.
Atunci când se face o cerere către VBA pentru un număr aleatoriu, se poate specifica limita superioară a intervalului de numere, apoi se înmulțește acest număr cu Rnd. De exemplu, pentru a simula un joc de zaruri, este nevoie de valori aleatorii de la 1 la 6, deci 6 este limita superioară. Rezultatul obținut cu Rnd se înmulțește cu 6. Apoi se adună 1 în cod, pentru ca intervalul să înceapă de la 1. (Dacă nu se adună 1, intervalul va fi între 0 și limita superioară, ca în codul din Listing 7.2, care a furnizat numere de la 0 la 9, nu de la 1 la 10.)
Se folosește și funcția Int deoarece Rnd doar fracții. Iată câteva rezultate tipice când se execută funcția Rnd:
Aceste rezultate sunt fracții subunitare, de aceea trebuie înmulțite cu numărul care dă limita superioară, pentru a obține și partea întreagă. Apoi, comanda Int preia doar partea întreagă a numărului obținut. Iată cum arată formula de obținere a unui număr aleatoriu de la 1 la 50:
X = Int(Rnd * 50 + 1)
Pentru a obține intervalul de la 0 până la limita superioară, se specifică ca limită superioară un număr cu 1 mai mare decât limita superioară și nu se mai adună 1 la final. Iată cum se poate obține un număr aleatoriu de la 0 la 50:
X = Int(Rnd * 51)
Căutarea binară într-o matrice
După cum am prezentat în secțiunea anterioară, o căutare liniară este ușor de efectuat, dar este destul de simplă și lentă - începe căutarea la începutul matricei și apoi verifică fiecare element în parte. Această abordare funcționează bine pentru căutări mici, cum ar fi o matrice cu 10 elemente, dar nu și în cazul matricilor foarte mari. Pentru astfel de căutări, este nevoie de o abordare mai inteligentă.
În scopuri convenționale, căutarea binară este o modalitate bună de a aborda căutarea unei serii sortate. Căutarea binară imită abordarea folosită la căutarea telecomenzii TV. De obicei ea se află într-o locație dată - undeva în camera de zi, probabil lângă canapea - astfel încât căutarea se face în zona relevantă. (Prin comparație, o căutare liniară se face peste tot în casă, de la intrare până la ultima cameră, fără a încerca restrângerea în mod inteligent a zonei de căutare.)
Tehnica de căutare binară (denumită tehnic algoritm) determină cea mai probabilă zonă țintă împărțind matricea sortată la jumătate, stabilind ce jumătate va conține elementul de căutare și apoi repetând procedura de divizare și interogare până când acesta găsește elementul de căutare sau ajunge la ultima unitate subdivizibilă a matricei. De reținut că această matrice este presortată, deci dacă algoritmul caută numărul 12 dintr-o listă de la 1 la 20, zona de căutare trebuie să fie în a doua jumătate a listei.
Iată un alt exemplu. Presupunând o căutare binară a valorii 789,789 într-o matrice de milioane de subcategorii care conține numerele de la 1 la 1,000,000 în ordine ascendentă. Se împarte matricea în două jumătăți, fiecare conținând o jumătate de milion de indecși. Se stabilește dacă elementul de căutare este în prima jumătate sau a doua jumătate și apoi se îngustează căutarea la jumătatea corespunzătoare și o împarte în jumătăți noi. Se stabilește dacă elementul de căutare se află în prima dintre aceste jumătăți sau cea de-a doua și apoi se concentrează asupra acelei jumătăți, împărțind-o în jumătăți - și așa mai departe, până când găsește termenul sau ajunge la un singur indice.
Acesta este un exemplu simplu, dar un milion este un număr mare. Pentru ca lucrurile să fie și mai simple, se pot folosi serii de câte o mie de indecși care conțin în ordine numerele de la 1 la 1000. Primul indice conține numărul 1, al doilea indice conține numărul 2 și așa mai departe până la 1000.
Exemplul următor este nerealist, dar ne ajută să vedem ce se întâmplă în cod.
1. Option Explicit
2. Option Base 1
3.
4. Sub Cautare_Binara_In_Matrice()
5.
6. 'se declara matricea și variabilele de lucru
7. Dim intThousand(1000) As Integer
8. Dim i As Integer
9. Dim intTop As Integer
10. Dim intMiddle As Integer
11. Dim intBottom As Integer
12. Dim varUserNumber As Variant
13. Dim strMsg As String
14.
15. 'se populeaza matricea cu numere de la 1 pana la 1000, ordine crescatoare
16. For i = 1 To 1000
17. intThousand(i) = i
18. Next i
19.
20. 'se cere utilizatorului sa introduca elementul cautat
21. Loopback:
22. varUserNumber = InputBox _
("Introduceti un numar in intervalul 1 - 1000 pentru a-l cauta:", "Demonstrare Cautare Binara")
23. If varUserNumber = "" Then End
24. If Not IsNumeric(varUserNumber) Then GoTo Loopback
25.
26. 'cautarea elementului
27. intTop = UBound(intThousand)
28. intBottom = LBound(intThousand)
29.
30. Do
31. intMiddle = (intTop + intBottom) / 2
32. If varUserNumber > intThousand(intMiddle) Then
33. intBottom = intMiddle + 1
34. Else
35. intTop = intMiddle - 1
36. End If
37. Loop Until (varUserNumber = intThousand(intMiddle)) _
Or (intBottom > intTop)
38.
39. 'stabileste daca cautarea a descoperit elementul cautat
'sau nu si adauga informatia corespnzatoare la strMsg
40. If varUserNumber = intThousand(intMiddle) Then
41. strMsg = "Cautarea a gasit elementul cautat, " _
& varUserNumber & ", la pozitia " & intMiddle _
& " din matrice."
42. Else
43. strMsg = "Cautarea nu a gasit elementul cautat, " _
& varUserNumber & "."
44. End If
45.
46. MsgBox strMsg, vbOKOnly & vbInformation, "Rezultatul cautarii binare"
47.
48. End Sub
Partea cea mai complexă a procedurii are loc în buclă.
Codul poate fi descărcat de la adresa www.sybex.com/go/masteringvba2016.
Codul se copie în Visual Basic Editor (acest cod funcționează în orice aplicație care are VBAactiv). Se deschide modulul și se urmează etapele:
1. Se afișează fereastra Locals (View > Locals Window) pentru a putea urmări toate valorile variabilelor intTop, intMiddle și intBottom în timpul rulării procedurii.
2. Se setează un punct de întrerupere (breakpoint) în procedură la linia 22 cu click pe marginea din stânga, în dreptul comenzii varUserNumber = InputBox. (Atunci când instrucțiunea se întinde pe mai multe rânduri, Editorul Visual Basic afișează mai multe puncte roșii, în locul unuia singur în bara de indicatoare din stânga, pentru a indica punctul de întrerupere.)
3. Se apasă tasta F5 (sau se alege Run > Run Sub/UserForm) pentru a executa codul până la breakpoint. VBA crează și populează matricea, apoi se oprește la linia 22.
4. Se apasă tasta F8 pentru a parcurge următoarele instrucțiuni una câte una. Prima apăsare a tastei F8 afișează caseta de dialog pentru introducerea numărului. Se introduce, de exemplu, valoarea 67 și clic pe butonul OK.
5. În timp ce codul intră în bucla Do… și face câteva cicluri, se poate observa modificarea variabilelor intTop, intMiddle și intBottom din fereastra Locals. Modificarea se poate vedea în lista de mai jos:
La sfârșitul celei de-a zecea iterații a buclei, intThousand (intMiddle) este egală cu varUserNumber, astfel că bucla se termină. După cum se poate vedea, punctele de întrerupere, parcurgerea pas cu pas și fereastra Locale sunt instrumente excelente de depanare.