Căutarea într-o matrice

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 
  • Ca și în macrocomanda anterioară, linia 1 conține declarația Option Explicit pentru a forța declararea explicită a variabilelor, iar linia 2 conține declarația Option Base 1 pentru ca numerele index dim matrici să înceapă de la 1 în loc de 0. Aceste două declarații sunt scrise la începutul codului, înaintea oricărei proceduri. Linia 3 este goală.
  • Pe linia 4 începe procedura Cautare_Liniara_In_Matrice. Linia 5 este goală.
  • Pe linia 6 se află un comentariu care anunță declararea matricii și a variabilelor de lucru folosite în cod. Linia 7 declară matricea intArray cu 10 elemente, de tip Integer. Linia 8 declară variabila i de tip Integer (de obicei, programatorii folosesc numele i pentru variabilele din buclă - i de la incrementare sau iterație).
  • Linia 9 declară variabila varUserNumber de tip Variant, care este folosită de cod pentru a stoca valoarea introdusă de utilizator în caseta de dialog. Linia 10 declară variabila strMsg de tip String. Linia 11 este goală.
  • Procedura declară variabila varUserNumber ca Variant și nu ca Integer. Astfel, Visual Basic nu va opri executarea macrocomenzii și nu va afișa un mesaj de eroare dacă utilizatorul introduce altceva (de exemplu, text) în caseta de dialog.
  • Liniile 12 și 13 conțin comentarii despre codul din liniile de la 14 până la 17.

(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.)

  • Linia 14 începe o buclă For... Next care se va repeta de 10 ori: de la i = 1 până la i = 10. Linia 15 atribuie elementului curent din matricea intArray rezultatul obținut din înmulțirea unui număr aleatoriu cu 10: intArray(i) = Int(Rnd * 10). (Funcția Rnd generează un număr aleatoriu între 0 și 1, cu mai multe zecimale. Altfel zis, procedura înmulțește numărul aleatoriu cu 10 pentru a obține un număr între 0 și 10, apoi preia doar partea întreagă a rezultatului înmulțirii. Comanda Int elimină partea zecimală a numărului.) Pe linia 16 se folosește metoda Print a obiectului Debug pentru a afișa elementul curent al matricii intArray în fereastra Immediate. Aceasta este o metodă rapidă folosită de programatori pentru a examina valorile generate aleatoriu pentru matrice. Utilizatorul nu va vedea fereastra Immediate. Linia 17 încheie bucla cu instrucțiunea Next i. Linia 18 este goală.
  • Linia 19 conține o etichetă (label), numită Loopback și folosită pentru a întoarce execuția procedurii în acest moment din cod dacă valoarea introdusă de utilizator nu îndeplinește condițiile cerute (dacă numărul nu se află în intervalul 1 - 10).
  • Linia 20 atribuie variabilei varUserNumber de tip Variant valoarea introdusă de utilizator. O casetă de dialog (ca în Figura 7.7) cere utilizatorului să introducă o valoare din intervalul 1 - 10.
  • Linia 21 compară conținutul variabilei varUserNumber cu șirul vid—rezultatul obținut dacă utilizatorul ar fi închis fereastra de dialog cu clic pe butonul Cancel sau pe butonul OK dar fără a introduce nicio valoare în fereastra de dialog. Dacă variabila varUserNumber este șirul vid, instrucțiunea End încheie executarea procedurii.
  • Linia 22 folosește funcția IsNumeric pentru a verifica dacă valoarea din variabila varUserNumber este de tip numeric. Dacă nu, instrucțiunea GoTo Loopback determină executarea etichetei Loopback, după care fereastra de dialog este afișată din nou. Linia 23 verifică dacă valoarea din varUserNumber este mai mica decât 1 sau mai mare decât 10. Dacă este îndeplinită condiția, o altă instrucțiune GoTo Loopback determină executarea etichetei Loopback, iar fereastra de dialog este afișată din nou. Linia 24 este goală.

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.

  • Linia 25 atribuie variabilei strMsg de tip String un mesaj preliminar în cazul în care valoarea (pe care o specifică) nu a fost găsită în matrice. (Atunci când codul găsește valoarea în matrice, modifică mesajul înainte de a-l afișa.) Linia 26 este goală.
  • Liniile de la 27 până la 32 conțin partea de căutare din procedură. Linia 27 începe o buclă For... Next care rulează de la i = 1 până la i = UBound(intArray)—o dată pentru fiecare element din matrice. Linia 28 compară intArray(i) cu variabila varUserNumber; dacă ele sunt egale, linia 28 atribuie variabilei strMsg textul care îi spune utilizatorului la ce poziție din matrice a găsit valoarea, iar linia 29 folosește o instrucțiune Exit For pentru a ieși din bucla For… Next. (Dacă pe linia 28 valorile nu sunt egale, instrucțiunea Next i de pe linia 32 reia bucla de la început.)
  • Linia 33 este goală. Linia 34 afișează un mesaj care conține valoarea variabilei strMsg care transmite utilizatorului rezultatul operației de căutare liniară. Figura 7.8 prezintă rezultatul unei căutări de succes. Linia 35 este goală, iar linia 36 încheie procedura.

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:

  • 0.4542087
  • 0.3570321
  • 0.1499181
  • 0.7043598
  • 0.9287686

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 
    • Linia 1 conține declarația Option Explicit pentru a forța declararea explicită a variabilelor, iar linia 2 conține instrucțiunea Option Base 1 pentru a face ca numerotarea să înceapă de la 1 în loc de 0. Aceste două instrucțiuni apar în partea cu declarații a codului, înainte de orice procedură.
    • Linia 3 este goală. Linia 4 declară procedura de căutare binară în matrice - Cautare_Binara_In_Matrice, iar linia 5 este un alt spațiu gol.
    • Pe linia 6 este un comentariu înaintea declarării matricii (matricea intThousand, cu 1000 de elemente de tip întreg, declarată pe linia 7) și celelalte variabile folosite de procedură: variabilele de tip Integer i (linia 8), intTop (linia 9), intMiddle (linia 10) și intBottom (linia 11); variabila de tip Variant varUserNumber (linia 12) și variabila de tip String strMsg (linia 13). Linia 14 este goală.
    • Pe linia 15 este un comentariu care anunță că pe liniile 16 - 18 se populează matricea cu numere de la 1 până la 1000 în ordine crescătoare. Acest lucru se face folosind bucla For... Next care este executată de la i = 1 până la i = 1000, atribuind valoarea curentă a lui i la elementul matricii cu indexul i - altfel zis, atribuind fiecărui element numărul care corespunde poziției sale în matrice. Linia 19 este goală.
    • Pe linia 20 este un comentariu înaintea secțiunii de cod (liniile 21 până la 24) care folosește o casetă de dialog pentru introducerea numărului și verifică introducerea numărului. Ca și în macrocomanda anterioară, această parte din cod verifică dacă utilizatorul nu a introdus nimic în caseta de dialog (linia 23), în acest caz terminând execuția procedurii. Folosește și o etichetă numită Loopback (în linia 21), la care codul se întoarce în cazul în care un utilizator nu a introdus în caseta de intrare (la linia 22) un număr – verificare pe linia 24. Deoarece de data aceasta știți ce numere va conține matricea, nu este necesar să verificați dacă utilizatorii vor introduce o valoare corespunzătoare. Dacă doresc, ei pot să introducă o valoare care nu apare în matrice.
    • Linia 25 este goală, iar pe linia 26 este un comentariu înaintea secțiunii de cod care caută elementul introdus de utilizator. Linia 27 atribuie variabilei intTop limita superioară a matricei și Linia 28 atribuie intBottom limita inferioară. Linia 29 este goală.
    • Liniile de la 30 până la 37 conțin o buclă Do... Loop Until care realizează căutarea binară. Iată detalii:
    • Linia 30 începe bucla Do... Loop Until cu cuvântul cheie Do, iar linia 37 încheie bucla cu cuvintele cheie Loop Until și condiția ((varUserNumber = intThousand(intMiddle)) Or (intBottom > intTop)). O buclă Do… Loop Until rulează o dată și apoi evaluează condiția din instrucțiunea Loop Until pentru a determina dacă ar trebui să se încheie sau să ruleze din nou. Condiția de aici specifică faptul că bucla continuă până la valoarea indexului din matricea identificată de intMiddle - intThousand(intMiddle) - corespunde valorii din varUserNumber sau dacă valoarea din intBottom este mai mare decât valoarea intTop (intBottom > intTop).
    • Linia 31 setează valoarea variabilei intMiddle de tip Integer la suma valorilor intTop și intBottom împărțită la 2: (intTop + IntBottom) / 2. Făcând acest lucru se obține valoarea de mijloc pentru împărțirea în două a matricei. De exemplu, în matricea de o mie de indecși, intTop are valoarea 1000 la prima iterație a buclei, iar intBottom are valoarea 0, deci intMiddle primește valoarea 500 (1000 împărțită la 2).
    • Linia 32 testează dacă varUserNumber este mai mare decât valoarea stocată în indexul identificat de intMiddle - intThousand(intMiddle), punctul de mijloc a secțiunii curente a matricii. Dacă este, căutarea se va face în jumătatea superioară a matricii, deci linia 33 va reseta/modifica valoarea intBottom la intMiddle + 1. Dacă nu este, se folosește instrucțiunea Else de pe linia 34, iar linia 35 modifică valoarea intTop la intMiddle - 1 astfel încât căutarea se va face în jumătatea de jos a matricii.
    • Linia 36 încheie instrucțiunea If, iar linia 37 testează condiția și continua sau termină bucla, după cum este cazul.
    • Linia 38 este goală. Pe linia 39 se află un comentariu pe două linii care descrie codul de pe liniile 40 - 44, care stabilește dacă elementul căutat este găsit și atribuie informația variabilei strMsg de tip String. Linia 40 compară varUserNumber cu intThousand(intMiddle); dacă se potrivesc, linia 41 atribuie la strMsg un text care spune utilizatorului unde a fost găsit elementul în matrice. Dacă nu se potrivesc, linia 43 atribuie variabilei un text care spune utilizatorului că elementul căutat nu a fost găsit. Linia 45 este goală, iar la linia 46 va fi afișat un mesaj pentru utilizator cu rezultatul căutării. Mai jos sunt afișate mesajele pentru utilizator – în cazul în care elementul a fost găsit (stânga) sau nu (dreapta).
    • Linia 47 este goală, iar linia 48 încheie procedura.

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.