Tutorial VBA Recursie

  1. inleiding
  2. berekening met faculteit
  3. spaties tellen
  4. bestandsnamen uitlezen uit folder inclusief alle subfolders
  5. samenvatting

1. INLEIDING

Recursieve functies of procedures zijn procedures of functies die zichzelf weer aanroepen. Een voordeel van het recursief programmeren kan zijn dat de programmacode korter en eleganter wordt. Daarnaast zijn er een aantal situaties waarbij het noodzakelijk is om gebruik te maken van recursief aanroepen. In deze tutorial zal recursie uitgelegd worden aan de hand van een paar voorbeelden met VBA.

 

2. BEREKENING FACULTEIT

VBA heeft geen ingebouwde functie om de faculteit van een getal te berekenen. Het is eenvoudig om hiervoor een functie te maken. Die functie kan er als volgt uitzien:

Function FactorialLoop(x As Byte) As Double
    Dim i As Byte
    FactorialLoop = 1
    For i = 1 To x
        FactorialLoop = FactorialLoop * i
    Next i
End Function

Door middel van een loopconstructie wordt de faculteit uitgerekend. Maar deze functie kan ook als recursieve functie geprogrammeerd worden. De VBA-code ziet er dan bijvoorbeeld als volgt uit:

Function FactorialRecursive(x As Byte) As Double
    FactorialRecursive = 1
    If x > 1 Then FactorialRecursive = x * FactorialRecursive(x - 1)
End Function

In deze functie is te zien dat de functie zichzelf weer aanroept. De functie maakt gebruikt van de eigenschap: n!=n*(n-1)!

Bij bijvoorbeeld 4! worden achtereenvolgens de volgende berekeningen uitgevoerd:

4 x (4-1)!

4 x 3!

4 x 3 x (3-1)!

4 x 3 x 2!

4 x 3 x 2 x (2-1)!

4 x 3 x 2 x 1!

4 x 3 x 2 x 1

Bij bovenstaande functies is overigens geen foutcontrole ingebouwd. Er wordt vanuit gegaan dat de gebruiker geldige getallen invoert.

 

3. SPATIES TELLEN

Nu volgt een voorbeeld van een functie om spaties te tellen in tekststring/zin. Het is een erg omslachtige manier om dat met een recursieve functie te doen. In feite hoeft de functie maar uit één coderegel te bestaan:

Function CountSpaces(TextString As String)
    CountSpaces = Len(TextString) - Len(Replace(TextString, " ", ""))
End Function

Maar onderstaande recursieve variant geeft wel mooi aan hoe je optionele variabelen in combinatie met recursieve functies kunt gebruiken. De functie voor het spaties tellen ziet er dan als volgt uit:

Function CountSpaces(TextString As String, Optional Start As Integer, Optional SpaceCount As Integer) As Integer
    Dim i As Long
    'bij eerste keer aanroepen functie Start instellen op 1
    If Start = 0 Then Start = 1
    i = InStr(Start, TextString, " ")
    If i > 0 Then
        SpaceCount = SpaceCount + 1
        'recursief aanroepen van de functie
        CountSpaces TextString, i + 1, SpaceCount
    End If
    CountSpaces = SpaceCount
End Function

In deze functie worden 2 optionele variabelen gebruikt. Met de variabele Start wordt ingesteld vanaf welke positie in het woord of de zin gezocht moet worden naar de volgende spatie. Bij het recursief aanroepen wordt deze waarde telkens ingesteld op één positie na de laatst gevonden spatie. Met de optionele variabele SpaceCount wordt de telling van het aantal spaties bijgehouden.

 

4. BESTANDSNAMEN UITLEZEN UIT FOLDER INCLUSIEF ALLE SUBFOLDERS

Nu volgt een voorbeeld waarbij recursief programmeren vaak gebruikt wordt. Stel alle bestandsnamen uit een bestaande map inclusief die uit de onderliggende submappen moeten worden uitgelezen. Omdat van te voren niet bekend is hoeveel submappen er zijn, kan dit goed opgelost worden door het recursief aanroepen van de procedure:

Sub ReadFilesRecursive(MapName As String)
    Dim FileName As String, PathName As String
    Dim Subfolders() As String, SubFolderCount As Integer
    Dim i As Integer
    'zeker stellen dat mapnaam altijd met \ eindigt
    If Right(MapName, 1) <> "\" Then MapName = MapName & "\"
    FileName = Dir(MapName & "*.*", vbDirectory)
    While Len(FileName) <> 0
        If Left(FileName, 1) <> "." Then 'huidige map
            PathName = MapName & FileName
            If GetAttr(PathName) = vbDirectory Then
                'in array opslaan van gevonden subfolders
                ReDim Preserve Subfolders(0 To SubFolderCount)
                Subfolders(SubFolderCount) = PathName
                SubFolderCount = SubFolderCount + 1
            Else
                Debug.Print MapName & FileName
            End If
        End If
        FileName = Dir()
    Wend
    'uitlezen van array met subfolders en recursief procedure aanroepen
    For i = 0 To SubFolderCount - 1
        ReadFilesRecursive Subfolders(i)
    Next i
End Sub

Deze procedure kan bijvoorbeeld vanuit een andere procedure als volgt aangeroepen worden: ReadFilesRecursive (“C:\Users\Betterwin\Documents\Data\Office”)
In deze procedure worden eerst alle files en submappen uit de opgegeven map uitgelezen. Als dit een submap betreft dan wordt deze submap weggeschreven in een array. Als het een bestandsnaam betreft, dan wordt deze met DEBUG.PRINT weggeschreven in het Venster Direct. Na het uitlezen van deze map wordt de procedure recursief aangeroepen, waarbij de array met opgeslagen submapnamen als argument van de functie wordt gebruikt. De gegevens uit deze submappen worden op dezelfde wijze behandeld als de gegevens uit de opgegeven map. Dus onderliggende mappen van de submap worden toegevoegd aan de array en bestandsnamen worden weergegeven in het Venster Direct. De procedure loopt net zolang totdat de gehele array is uitgelezen en daarmee alle bestanden uit de opgegeven map inclusief alle onderliggende submappen zijn weggeschreven naar het Venster Direct.

Maar toch is ook het mogelijk om bestanden uit een folder en de onderliggende subfolders uit te lezen zonder een recursieve procedure. Dit is een voorbeeld van zo’n procedure in Excel:

Sub ReadFilesNOTRecursive()
    Dim arr() As String
    arr = Split(CreateObject("wscript.shell").exec("cmd /c Dir ""C:\Users\Betterwin\Documents\Data\Office\"" /b /s").stdout.readall, vbCrLf)
    Range("A1").Resize(UBound(arr) + 1) = Application.Transpose(arr)
End Sub

Er wordt in deze procedure onder andere handig gebruik gemaakt van twee attributen van de DIR-functie, te weten /b en /s. Attribuut /b zorgt ervoor dat geen samenvattinginformatie en kolomkoppen worden gegenereerd en attribuut /s zorgt ervoor dat alle informatie uit de gekozen folder inclusief alle subfolders wordt gepresenteerd. De bestandsgegevens worden in deze procedure weergegeven in kolom A van de actieve sheet. Een nadeel van deze procedure is dat ook de mapnamen tussen de bestandsnamen staan. Dit kan voorkomen worden door alleen bestanden met een bepaalde extensie uit te lezen. Vervang bijvoorbeeld C:\Users\Betterwin\Documents\Data\Office\ door C:\Users\Betterwin\Documents\Data\Office\*.xlsx en alle normale Excel-bestanden worden weergegeven, zonder dat de mapnamen er ook tussen staan. Als alle bestandsnamen zonder de mapnamen moeten worden weergegeven dan kan dat met de recursieve procedure.

 

5. SAMENVATTING

Recursie is in sommige situaties een onmisbaar hulpmiddel. Vooral wanneer niet vooraf bekend is hoe vaak een procedure doorlopen moet worden. Bijvoorbeeld bij het analyseren van een boomstructuur, waarbij vooraf niet bekend is hoeveel vertakkingen er zijn. De grootste valkuil bij recursie is dat dit eindigt in een oneindige loop. Daarom is het van groot belang om goed na te denken over het eindigen van de procedure. Er moet altijd een uitweg zijn uit de procedure.

 

Interesse in andere tutorials? Ga dan naar ons tutorials-overzicht of neem een kijkje op onze pagina met tips&trucs.

Reacties op onze tutorials en tips&trucs zijn van harte welkom. Dat kan door hieronder een reactie toe te voegen of door rechtstreeks contact met ons op te nemen.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd.