Trouble using recursion to process folders/files

blackduck603

Member
Joined
Sep 17, 2008
Messages
21
Location
NH
Programming Experience
3-5
I am trying to write code to save (in a text file) a list of folders/files that are contained in a specified folder. I want the list to be formatted to illustrate the folder tree structure by indenting sub-folders and there related files according to their relationship to the base folder.

I found and incorporated a recursive function for listing file & folders. This works fine for generating the list, but I am having difficulty with code to determine the sub-directory level relative to the base folder. the code that I have included below sort of works.......The problem seeme to be when going from a folder that is multiple levels up? from the base to one that is only one level from the base. Refer to my sample output.

This should not be as difficult as I seemed to have made it, but recursion kinda blows my mind.



VB.NET:
[COLOR="DarkGreen"][I]' The following function call is in a button handler [/I][/COLOR]
GetFilesFolders(txtBaseFolder.Text)

    [B]Public Sub GetFilesFolders(ByVal path As String)[/B]        
        Write2Log("-------------------------------------------------------------")
        Write2Log(" F O L D E R    C O N T E N T S : ")
        Write2Log("-------------------------------------------------------------")

         If File.Exists(path) Then
            ' This path is a file 
            ProcessFile(path, 0)
        ElseIf Directory.Exists(path) Then
            ' This path is a directory 
            ProcessDirectory(path, 0) 
        End If
    End Sub

    Public Sub [B]ProcessDirectory(ByVal targetDirectory As String, ByVal mySubDirLevel As Integer)[/B]
[COLOR="DarkGreen"][I]        ' Process all files/folders in the directory passed in, 
        ' recurse on any subdirectories that are found, and process the files they contain. 
        ' Write the file and folder info to a file 
        ' - indent based on Directory Level to simulate a Tree Hierarchy[/I][/COLOR]

        [COLOR="darkgreen"][I]' Spacing is used for indentation[/I][/COLOR]        
        Dim sSpacing As String = ""
        If mySubDirLevel > 0 Then
            For nIdx As Integer = 0 To mySubDirLevel
                sSpacing = sSpacing & "-"
            Next nIdx
        End If
       [COLOR="darkgreen"][I] ' Write the info to a file[/I][/COLOR]        
        Write2Log(sSpacing & "[" & targetDirectory & "]")
        Dim fileEntries As String() = Directory.GetFiles(targetDirectory)
       [COLOR="darkgreen"][I] ' If there are files in the folder, get the info and write it to the output file[/I][/COLOR]        
        For Each fileName As String In fileEntries
            ProcessFile(fileName, sSpacing)
        Next

       [COLOR="darkgreen"][I] ' Recurse into subdirectories of this directory. [/I][/COLOR]        
        Dim subdirectoryEntries As String() = Directory.GetDirectories(targetDirectory)

        If subdirectoryEntries.Length > 0 Then
            For Each subdirectory As String In subdirectoryEntries
                Dim nSubDirLevel As Integer
               [COLOR="darkgreen"][I] ' Get the subDir Level[/I][/COLOR]                
[B]nSubDirLevel = GetDirLevel(subdirectory, Me.txtFolderToWatch.Text)[/B]
                [COLOR="darkgreen"][I]' RECURSE - Process the Directory[/I][/COLOR]
                [B]ProcessDirectory(subdirectory, nSubDirLevel)[/B] 
            Next
        End If
    End Sub



[COLOR="Blue"]' Global used to keep track of previous Dir Level
    Dim nDirLevelCounter As Integer = 1[/COLOR]

    Public Function [B]GetDirLevel(ByVal myPath As String, ByVal sBaseDir As String) [/B]As Integer
       [COLOR="darkgreen"][I] ' This function is used to determine the relative SubDir level based on the BaseDir that is passed in
        ' For example: 
        ' if C:\Tmp\Base is the base Dir
        ' then 
        ' C:\Tmp\Base                           should return 0
        ' C:\Tmp\Base\SubDir1                   should return 1
        ' C:\Tmp\Base\SubDir1\Subdir1a\SubDirX  should return 3[/I][/COLOR]        
Dim sParentDir As String
        Dim diDir As System.IO.DirectoryInfo
        diDir = Directory.GetParent(myPath)
        sParentDir = diDir.FullName()
        If sParentDir = sBaseDir Then
            Return [B]nDirLevelCounter[/B]
        Else
            [B]nDirLevelCounter = nDirLevelCounter + 1[/B]
            Return [B]GetDirLevel(sParentDir, sBaseDir)[/B]        
End If
    End Function

    Public Sub Write2Log(ByVal Msg As String)
        Dim sAppname As String
        Dim sLogFolder As String
        If My.Settings.LogFilePath = "" Then
            ' Use App Folder
            sLogFolder = CurrentDirectory
        Else
            sLogFolder = My.Settings.LogFilePath
        End If
        sAppname = Application.ProductName
        Dim objStreamWriter As StreamWriter
        objStreamWriter = File.AppendText(sLogFolder & "\" & sAppname & "-Log.txt")
        'Precede the Message with a Date/Time stamp
        objStreamWriter.WriteLine(Msg)
        'Close the stream
        objStreamWriter.Close()
    End Sub


Output file (my notes depicted with *)
VB.NET:
-------------------------------------------------------------
 F O L D E R    C O N T E N T S : 
-------------------------------------------------------------
[C:\tmp\fsw]
----> 1TextDoc.txt
----> Import1125.txt
--[C:\tmp\fsw\Fldr1Lvl1]                        [COLOR="Red"]** Level 1 (correct)[/COLOR]
------> WaveLvl0.wav
------> ZipLvl0.zip
--[C:\tmp\fsw\Fldr2Lvl1]                       [COLOR="red"] ** Level 1 (correct)[/COLOR]
------> BitmapLvl1.bmp
[COLOR="darkred"]---[/COLOR][C:\tmp\fsw\Fldr2Lvl1\Fldr1Lvl2]         [COLOR="red"] ** Level 2 (correct)[/COLOR]
-------> BitmapLvl2.bmp
-----[C:\tmp\fsw\Fldr2Lvl1\Fldr1Lvl2\Fldr1Lvl3]   [COLOR="red"]** Level 3 (correct)[/COLOR]
---------> WaveLvl3.wav
[COLOR="DarkRed"]-----[/COLOR][C:\tmp\fsw\Fldr3Lvl1]                       [COLOR="red"]** (wrong) Should be Level 1[/COLOR]
---------> Wave3-1.wav

I appreciate any assistance that can be provided.
 
I dont understand why you have a procedure for calculating a the subdirectory level. Surely it's just +1 of whatever youre in now, so when youre processing a directory, you should call:

If (current thing is a directory) Then
ProcessDirectory(thing, currentLevel + 1)


Your level starts at 0, and searches the dir.. So every folder that is found during the search will be called with 0+1
This 0+1 (=1) becomes the subDirLevel for the recursed call
Then every folder found during that search would be (currentSubDirLevel of 1)+1 =2

And so on


Some other tips to unclutter your code:

String has a constructor that takes a character, and the number of times to repeat it. This would be preferable to using a & to concatenate '-' on hundreds or thousands of times:

VB.NET:
        ' Spacing is used for indentation        
        Dim sSpacing As String = New String("-"c, mySubDirLevel)


[COLOR="Red"]        If mySubDirLevel > 0 Then
            For nIdx As Integer = 0 To mySubDirLevel
                sSpacing = sSpacing & "-"
            Next nIdx
        End If[/COLOR]



VB.NET:
        If subdirectoryEntries.Length > 0 Then
            For Each subdirectory As String In subdirectoryEntries

                ' RECURSE - Process the Directory
                ProcessDirectory(subdirectory, [B]mySubDirLevel + 1[/B]) 
            Next
        End If
    End Sub

' Global used to keep track of previous Dir Level
Dim nDirLevelCounter As Integer = 1
'global? No no, do not use anything global when recursing. The whole idea of recursion is that you pass everything you want to use when youre using it, that way the recursion can start and finish in any logical state. This is probably why your other code was faulty, because your global variable is being set incorrectly.


Hopefully you can see how calculating the level relative to the base is pointless compared to simply declaring that base = 0 and every tiem you recurse, just passing in currentLevel + 1
 
Last edited:
Most recursive problems can actually be solved using loops which may be easier. In the case of directory searching you would use a Stack or a Queue collection.

Stack is LastIn-FirstOut like the stack of plates in a restaurant
Queue is FirstIn-FirstOut like the queue of people getting their food in the restaurant

You put one directory into the Queue to start with then start a loop that runs While Queue Length Is Greater Than Zero,
scan the current queued directory for more directories and push them into the queu, then process the files

Obviously the loop keeps running while more directories are pushed in. To work out the directory depth in this way you'd probably have to count the number of \ in the path


If you used a stack, then the search would dig down before it went across, if you use a queue it goes across before it goes down. A simple recursive search is more like a stack, you'd have to get a bit more complicated with recursion to do a breadth-first search

-

If you can say what is confusing you about recursion I can possibly explain it
 
Back
Top