FileListToArray im gesamten Verzeichnisbaum aber nicht rekursiv

  • Ich weiß was ihr denkt: "Schon wieder, so wie jede Woche, eine weitere Funktion zur Auflistung aller Dateien und Ordner im gesamten Verzeichnisbaum..."
    Nun ... ja - aber nicht ganz.
    Alle bisherigen Ansätze welche ich bisher dazu gesehen habe basierten auf einem rekursiven Ansatz (wenn es doch etwas anderes gibt - berichtigt mich).
    Ich hab daher mal eine entsprechende Funktion geschrieben welche ohne Rekursion auskommt:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    $Array = _FL2Arr(@WindowsDir, '*.exe', 1)
    _ArrayDisplay($Array)

    [/autoit] [autoit][/autoit] [autoit]

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: _FL2Arr
    ; Description ...: Gibt ein Array mit Dateien bzw. Unterordnern im gesamten Verzeichnisbaum zurück
    ; Syntax.........: _FL2Arr($sStDir, [$sPat = '*', [$iFlag = 0]])
    ; Parameters ....: $sStDir - Startverzeichnis
    ; $sPat - Optional: Pattern zur Filterung (Stichwort: "WildCards")
    ; $iFlag - Optional: Flag ob nur Dateien, Ordner oder beides gesucht werden sollen
    ; |$iFlag=0(Default) Gibt beides zurück
    ; |$iFlag=1 Gibt nur Dateien zurück
    ; |$iFlag=2 Gibt nur Ordner zurück
    ; Return values .: @Error - 1 = Fehler beim erstellen der Queue
    ; |2 = Startverzeichnis existiert nicht.
    ; |3 = ungültiger Wert für $iFlag
    ; Author ........: AspirinJunkie
    ; Modified.......:
    ; Remarks .......: Benötigt .Net
    ; Related .......:
    ; Link ..........:
    ; Example .......: No
    ; Note ..........:
    ; ===============================================================================================================================
    Func _FL2Arr($sStDir, $sPat = '*', $iFlag = 0)
    Local $cSQueue = ObjCreate("System.Collections.Queue")
    If @error Then Return SetError(1, 1, -1)
    Local $cRetQu = ObjCreate("System.Collections.Queue")
    If @error Then Return SetError(1, 2, -1)
    Local $FFFF, $FFNF, $sDir

    [/autoit] [autoit][/autoit] [autoit]

    $sPat = StringReplace($sPat, "\", "\\")
    $sPat = StringReplace($sPat, ".", "\.")
    $sPat = StringReplace($sPat, "*", ".*")
    $sPat = '^' & $sPat & '$'

    [/autoit] [autoit][/autoit] [autoit]

    If StringRight($sStDir, 1) = '\' Then $sStDir = StringTrimRight($sStDir, 1)
    If Not FileExists($sStDir) Then Return SetError(2, 0, "")
    If Not ($iFlag = 0 Or $iFlag = 1 Or $iFlag = 2) Then Return SetError(3, 0, "")

    [/autoit] [autoit][/autoit] [autoit]

    $cSQueue.Enqueue($sStDir)

    [/autoit] [autoit][/autoit] [autoit]

    While $cSQueue.Count > 0
    $sDir = $cSQueue.Dequeue
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    $cSQueue.Enqueue($sDir & '\' & $FFNF)
    If $iFlag <> 1 And StringRegExp($FFNF, $sPat) Then $cRetQu.Enqueue($sDir & '\' & $FFNF)
    Else
    If $iFlag < 2 And StringRegExp($FFNF, $sPat) Then $cRetQu.Enqueue($sDir & '\' & $FFNF)
    EndIf
    Until 0
    EndIf
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    If $cRetQu.Count > 0 Then
    Return $cRetQu.ToArray
    Else
    Local $Ret[1] = [0]
    Return $Ret
    EndIf
    EndFunc ;==>_FL2Arr

    [/autoit]

    Das ist vor allem schön für die Speichernutzung da der obligatorische Stackframe für jeden Rekursionsfunktionsaufruf entfällt.
    Nun aber warum das ganze?:
    Ich wollte es mal hochstellen um mal einen anderen Ansatz zu demonstrieren und eine Vorlage für Erweiterungen, Verbesserungen etc. zu bieten.

    Beispielsweise könnte man alle Unterordner in einem Verzeichnis auflisten wenn man das Prinzip entsprechend nutzt:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    $Array = Ordner(@SystemDir)
    _ArrayDisplay($Array)

    [/autoit] [autoit][/autoit] [autoit]

    ;Gibt ein Array aller Ordner im Verzeichnisbaum zurück
    Func Ordner($sStDir)
    Local $cStack = ObjCreate("System.Collections.Stack")
    Local $cRetSt = ObjCreate("System.Collections.Stack")
    Local $oFS = ObjCreate("Scripting.FileSystemObject")

    [/autoit] [autoit][/autoit] [autoit]

    $cStack.Push($oFS.GetFolder($sStDir))

    [/autoit] [autoit][/autoit] [autoit]

    While $cStack.Count > 0
    $oSF = $cStack.Pop
    $cRetSt.Push($oSF.Path)

    [/autoit] [autoit][/autoit] [autoit]

    For $oF In $oSF.SubFolders
    $cStack.Push($oF)
    Next
    WEnd
    Return $cRetSt.ToArray
    EndFunc ;==>Ordner

    [/autoit]


    So ich hoffe der ein oder andere sieht darin eine brauchbare Alternative für seine Zwecke.

    Einmal editiert, zuletzt von AspirinJunkie (11. März 2010 um 13:37)

  • Verständnisfrage: Was ist der Unterschied zur bestehenden Funktion _FileListToArray ?

  • _FileListToArray gibt nur den Inhalt einer Verzeichnisebene zurück.
    Die Beispielfunktion hier gibt alle Dateien aller Unterebenen zurück.

    Der Unterschied zu anderen Funktionen welche ebenfalls alle Ebenenen abarbeiten liegt in der Arbeitsweise, welche hier auf einem iterativen statt auf einem rekursiven Ansatz beruht.

    Einmal editiert, zuletzt von AspirinJunkie (31. März 2010 um 12:25)

  • Diese Funktion ist genial.
    Leider ist das Pattern Case Sensitiv.

    D.h. Wenn ich nach *.jpg suche, findet er keine Dateien, die *.JPG heissen....

    Woran müsste ich schrauben, damit das auch noch klappt?

    Grüsse Veronesi

  • Dazu musst du der Funktion StringRegExp() sagen, dass sie case-insensitve matchen soll ("(?i)" voranstelllen). Hab das Beispiel mal angepasst. Interessant ist vor allem die Nutzung des Objects, war mir neu :).

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    $Array = _FL2Arr(@ScriptDir, '*.jpg', 1)
    _ArrayDisplay($Array)

    [/autoit] [autoit][/autoit] [autoit]

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: _FL2Arr
    ; Description ...: Gibt ein Array mit Dateien bzw. Unterordnern im gesamten Verzeichnisbaum zurück
    ; Syntax.........: _FL2Arr($sStDir, [$sPat = '*', [$iFlag = 0]])
    ; Parameters ....: $sStDir - Startverzeichnis
    ; $sPat - Optional: Pattern zur Filterung (Stichwort: "WildCards")
    ; $iFlag - Optional: Flag ob nur Dateien, Ordner oder beides gesucht werden sollen
    ; |$iFlag=0(Default) Gibt beides zurück
    ; |$iFlag=1 Gibt nur Dateien zurück
    ; |$iFlag=2 Gibt nur Ordner zurück
    ; Return values .: @Error - 1 = Fehler beim erstellen der Queue
    ; |2 = Startverzeichnis existiert nicht.
    ; |3 = ungültiger Wert für $iFlag
    ; Author ........: AspirinJunkie
    ; Modified.......:
    ; Remarks .......: Benötigt .Net
    ; Related .......:
    ; Link ..........:
    ; Example .......: No
    ; Note ..........:
    ; ===============================================================================================================================
    Func _FL2Arr($sStDir, $sPat = '*', $iFlag = 0,$iCaseSensitive = 0)
    Local $cSQueue = ObjCreate("System.Collections.Queue")
    If @error Then Return SetError(1, 1, -1)
    Local $cRetQu = ObjCreate("System.Collections.Queue")
    If @error Then Return SetError(1, 2, -1)
    Local $FFFF, $FFNF, $sDir

    [/autoit] [autoit][/autoit] [autoit]

    $sPat = StringReplace($sPat, "\", "\\")
    $sPat = StringReplace($sPat, ".", "\.")
    $sPat = StringReplace($sPat, "*", ".*")
    Switch $iCaseSensitive
    Case True
    $sPat = '^' & $sPat & '$'
    Case False
    $sPat = '^(?i)' & $sPat & '$'
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    If StringRight($sStDir, 1) = '\' Then $sStDir = StringTrimRight($sStDir, 1)
    If Not FileExists($sStDir) Then Return SetError(2, 0, "")
    If Not ($iFlag = 0 Or $iFlag = 1 Or $iFlag = 2) Then Return SetError(3, 0, "")

    [/autoit] [autoit][/autoit] [autoit]

    $cSQueue.Enqueue($sStDir)

    [/autoit] [autoit][/autoit] [autoit]

    While $cSQueue.Count > 0
    $sDir = $cSQueue.Dequeue
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    $cSQueue.Enqueue($sDir & '\' & $FFNF)
    If $iFlag <> 1 And StringRegExp($FFNF, $sPat) Then $cRetQu.Enqueue($sDir & '\' & $FFNF)
    Else
    If $iFlag < 2 And StringRegExp($FFNF, $sPat) Then $cRetQu.Enqueue($sDir & '\' & $FFNF)
    EndIf
    Until 0
    EndIf
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    If $cRetQu.Count > 0 Then
    Return $cRetQu.ToArray
    Else
    Local $Ret[1] = [0]
    Return $Ret
    EndIf
    EndFunc ;==>_FL2Arr

    [/autoit]
  • Super! Vielen Dank.

    Nun läuft es.
    Ich stehe mit den StringRegExp irgendwie auf Kriegsfuss! Ich habe zwar gesehen dass ich (?i) einfügen muss, doch ich habe es falsch gemacht!

    Dankeschön!

  • Ob die iterative oder rekursive Variante, ist reine Geschmacksache, aber mir gefällt die Idee mit den Objekten!

    Gruß,
    UEZ

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

  • veronesi
    Schön wenn dir die Funktion gefällt aber sie zu nutzen empfehle ich dir nicht.
    Dies war in erster Linie mal um zu demonstrieren wie man einfach Rekursionen umgehen kann.
    Das ganze basiert auf dem .Net-Objekt "Queue", welches zwar für diesen Zweck ideal von der Bedienung her ist, aber von der Performance nicht wirklich zufriedenstellend.
    Um die Iteration zu realisieren kann man aber andere Datentypen als Zwischencontainer benutzen.
    Beispielsweise das schnellere Dictionary aus der Windows Script Runtime oder, noch schneller, direkt Strings aus AutoIt.
    Dann würde das ganze für deinen Fall so aussehen:

    Spoiler anzeigen
    [autoit]

    #include <Array.au3>

    [/autoit] [autoit][/autoit] [autoit]

    $Array = FLwStr(@ScriptDir, '(?i).+\.jpg', 1, 2)
    _ArrayDisplay($Array)

    [/autoit] [autoit][/autoit] [autoit]

    ; #FUNCTION# ======================================================================================
    ; Name ..........: FLwStr()
    ; Description ...: Findet Dateien und/oder Ordner in einem Verzeichnisbaum
    ; Syntax ........: FLwStr($sSD, Const[ $sPat = '', Const[ $iF = 3]])
    ; Parameters ....: $sSD - Suchort (mehrere Suchorte durch | trennen)
    ; Const $sPat - [optional] regulärer Ausdruck für zu findende Datei/Ordnernamen (default:'')
    ; Const $iF - [optional] 1=nur Dateien, 2=nur Ordner, 3=Dateien+Ordner (default:3)
    ; Const $iSSFlag - [optional] 0=Rückgabearray mit Anzahl Elemente in $Array0, 2=Rückgabearray ohne Anzahl Elemente
    ; Return values .: Success - Return Array with Matches with $Array[0] = Count
    ; Failure - Return "" and set @error
    ; Author ........: AspirinJunkie
    ; Remarks .......: Funktionsweise ist iterativ - nicht rekursiv
    ; Example .......: Yes
    ; #include <Array.au3>
    ; $aTmp = FLwStr(@WindowsDir & '|' & @ProgramFilesDir, "^Temp$", 2)
    ; _ArrayDisplay($aTmp)
    ; =================================================================================================
    Func FLwStr($sSD, Const $sPat = '', Const $iF = 3, Const $iSSFlag = 0)
    ;by AspirinJunkie
    Local $sRet = "", $sSubD = ""
    For $i in StringSplit($sSD, '|', 2)
    If StringRight($i, 1) = '\' Then $i = StringTrimRight($i, 1)
    If Not FileExists($i) Then Return SetError(2, 0, "")
    $sSubD &= '|' & $i
    Next
    Local $FFFF, $FFNF, $sDir, $iC
    Local $aD, $hDLL = DllOpen('kernel32.dll')
    If Not ($iF = 3 Or $iF = 1 Or $iF = 2) Then Return SetError(3, 0, "")
    Do
    $iC = StringInStr($sSubD, '|', 2, -1)
    If @error Or $iC = 0 Then ExitLoop
    $iC = StringLen($sSubD) - $iC
    $sDir = StringRight($sSubD, $iC)
    $sSubD = StringTrimRight($sSubD, $iC + 1)
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    If BitAND(StringRegExp($FFNF, $sPat) * 2, $iF) Then $sRet &= $sDir & '\' & $FFNF & '\|'
    $aD = DllCall($hDLL, 'dword', 'GetFileAttributesW', 'wstr', $sDir & '\' & $FFNF)
    If @error And BitAND($aD[0],0x400) Then ContinueLoop
    $sSubD &= '|' & $sDir & '\' & $FFNF
    ElseIf BitAND(StringRegExp($FFNF, $sPat), $iF) Then
    $sRet &= $sDir & '\' & $FFNF & '|'
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Until 0
    DllClose($hDLL)
    Return StringSplit(StringTrimRight($sRet, 1), '|', $iSSFlag)
    EndFunc

    [/autoit]


    Also wie gesagt: Die Funktion Fl2Arr() war nur für Demozwecke.
    Für den produktiven Einsatz gibt es deutlich schnellere Lösungen.

  • Naja wenn es dir um die Performance geht dann sollte ich vielleicht dazu sagen das die rekursive Version der Funktion noch n Tick schneller ist:

    Spoiler anzeigen
    [autoit]

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: FLwStrRek
    ; Description ...: Gibt ein Array mit Dateien bzw. Unterordnern im gesamten Verzeichnisbaum zurück
    ; Syntax.........: FLwStrRek($sSD, [$sPat = '*', [$iF = 0]])
    ; Parameters ....: $sSD - Startverzeichnis (mehrere Verzeichnisse durch | trennen)
    ; $sPat - Optional: Pattern zur Filterung mit regulären Ausdrücken
    ; $iF - Optional: Flag ob nur Dateien, Ordner oder beides gesucht werden sollen
    ; |$iF=3(Default) Gibt beides zurück
    ; |$iF=1 Gibt nur Dateien zurück
    ; |$iF=2 Gibt nur Ordner zurück
    ; $flgSS - Art der Rückgabe (Siehe StringSplit()-Funktion)
    ; $bS - interner Rekursionsparameter - nicht verändern!
    ; Return values .: @Error: 1 = Startverzeichnis existiert nicht.
    ; |2 = ungültiger Wert für $iF
    ; Author ........: AspirinJunkie
    ; Modified.......: 12.04.2010
    ; Remarks .......:
    ; Related .......:
    ; Link ..........:
    ; Example .......: No
    ; Note ..........: basiert auf einem rekursiven Ansatz
    ; ===============================================================================================================================
    Func FlwStrRek($SSD, Const $sPat = "", Const $iF = 3, Const $flgSS = 2, Const $bS = True)
    ; By AspirinJunkie
    Local $FFFF, $FFNF, $aD
    Local Static $sRet, $hDll
    If $bS Then
    $hDll = DllOpen("kernel32.dll")
    If NOT ($iF = 3 Or $iF = 1 Or $iF = 2) Then Return SetError(1, DllClose($hDll), "")
    EndIf
    For $I In StringSplit($SSD, "|", 2)
    If StringRight($I, 1) = "\" Then $I = StringTrimRight($I, 1)
    If Not FileExists($I) Then ContinueLoop
    $FFFF = FileFindFirstFile($I & "\*")
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    If BitAND(StringRegExp($FFNF, $sPat) * 2, $iF) Then $sRet &= $I & "\" & $FFNF & "|"
    $aD = DllCall($hDll, "dword", "GetFileAttributesW", "wstr", $I & "\" & $FFNF)
    If @error Or BitAND($aD[0], 1024) Then ContinueLoop
    FlwStrRek($I & "\" & $FFNF, $sPat, $iF, 2, False)
    ElseIf BitAND(StringRegExp($FFNF, $sPat), $iF) Then
    $sRet &= $I & "\" & $FFNF & "|"
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Next
    If $bS Then
    DllClose($hDll)
    Local $aRet = StringSplit(StringTrimRight($sRet, 1), "|", $flgSS)
    $sRet = ""
    Return $aRet
    EndIf
    EndFunc ;==>FlwStrRek

    [/autoit]
    • Offizieller Beitrag

    Das kann ich so nicht bestätigen. Bei mir (Win7, 64Bit) ist die iterative Funktion schneller.

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>
    $iTimer = TimerInit()
    $Array = FLwStr(@ScriptDir, '', 3, 0)
    ConsoleWrite(TimerDiff($iTimer) & @CR)
    _ArrayDisplay($Array)

    [/autoit] [autoit][/autoit] [autoit]

    $iTimer = TimerInit()
    $Array = FlwStrRek(@ScriptDir, '', 3, 0)
    ConsoleWrite(TimerDiff($iTimer) & @CR)
    _ArrayDisplay($Array)

    [/autoit] [autoit][/autoit] [autoit]

    ; #FUNCTION# ======================================================================================
    ; Name ..........: FLwStr()
    ; Description ...: Findet Dateien und/oder Ordner in einem Verzeichnisbaum
    ; Syntax ........: FLwStr($sSD, Const[ $sPat = '', Const[ $iF = 3]])
    ; Parameters ....: $sSD - Suchort (mehrere Suchorte durch | trennen)
    ; Const $sPat - [optional] regulärer Ausdruck für zu findende Datei/Ordnernamen (default:'')
    ; Const $iF - [optional] 1=nur Dateien, 2=nur Ordner, 3=Dateien+Ordner (default:3)
    ; Const $iSSFlag - [optional] 0=Rückgabearray mit Anzahl Elemente in $Array0, 2=Rückgabearray ohne Anzahl Elemente
    ; Return values .: Success - Return Array with Matches with $Array[0] = Count
    ; Failure - Return "" and set @error
    ; Author ........: AspirinJunkie
    ; Remarks .......: Funktionsweise ist iterativ - nicht rekursiv
    ; Example .......: Yes
    ; #include <Array.au3>
    ; $aTmp = FLwStr(@WindowsDir & '|' & @ProgramFilesDir, "^Temp$", 2)
    ; _ArrayDisplay($aTmp)
    ; =================================================================================================
    Func FLwStr($sSD, Const $sPat = '', Const $iF = 3, Const $iSSFlag = 0)
    ;by AspirinJunkie
    Local $sRet = "", $sSubD = ""
    For $i In StringSplit($sSD, '|', 2)
    If StringRight($i, 1) = '\' Then $i = StringTrimRight($i, 1)
    If Not FileExists($i) Then Return SetError(2, 0, "")
    $sSubD &= '|' & $i
    Next
    Local $FFFF, $FFNF, $sDir, $iC
    Local $aD, $hDLL = DllOpen('kernel32.dll')
    If Not ($iF = 3 Or $iF = 1 Or $iF = 2) Then Return SetError(3, 0, "")
    Do
    $iC = StringInStr($sSubD, '|', 2, -1)
    If @error Or $iC = 0 Then ExitLoop
    $iC = StringLen($sSubD) - $iC
    $sDir = StringRight($sSubD, $iC)
    $sSubD = StringTrimRight($sSubD, $iC + 1)
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    If BitAND(StringRegExp($FFNF, $sPat) * 2, $iF) Then $sRet &= $sDir & '\' & $FFNF & '\|'
    $aD = DllCall($hDLL, 'dword', 'GetFileAttributesW', 'wstr', $sDir & '\' & $FFNF)
    If @error And BitAND($aD[0], 0x400) Then ContinueLoop
    $sSubD &= '|' & $sDir & '\' & $FFNF
    ElseIf BitAND(StringRegExp($FFNF, $sPat), $iF) Then
    $sRet &= $sDir & '\' & $FFNF & '|'
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Until 0
    DllClose($hDLL)
    Return StringSplit(StringTrimRight($sRet, 1), '|', $iSSFlag)
    EndFunc ;==>FLwStr

    [/autoit] [autoit][/autoit] [autoit]

    ; #FUNCTION# ====================================================================================================================
    ; Name...........: FLwStrRek
    ; Description ...: Gibt ein Array mit Dateien bzw. Unterordnern im gesamten Verzeichnisbaum zurück
    ; Syntax.........: FLwStrRek($sSD, [$sPat = '*', [$iF = 0]])
    ; Parameters ....: $sSD - Startverzeichnis (mehrere Verzeichnisse durch | trennen)
    ; $sPat - Optional: Pattern zur Filterung mit regulären Ausdrücken
    ; $iF - Optional: Flag ob nur Dateien, Ordner oder beides gesucht werden sollen
    ; |$iF=3(Default) Gibt beides zurück
    ; |$iF=1 Gibt nur Dateien zurück
    ; |$iF=2 Gibt nur Ordner zurück
    ; $flgSS - Art der Rückgabe (Siehe StringSplit()-Funktion)
    ; $bS - interner Rekursionsparameter - nicht verändern!
    ; Return values .: @Error: 1 = Startverzeichnis existiert nicht.
    ; |2 = ungültiger Wert für $iF
    ; Author ........: AspirinJunkie
    ; Modified.......: 12.04.2010
    ; Remarks .......:
    ; Related .......:
    ; Link ..........:
    ; Example .......: No
    ; Note ..........: basiert auf einem rekursiven Ansatz
    ; ===============================================================================================================================
    Func FlwStrRek($SSD, Const $sPat = "", Const $iF = 3, Const $flgSS = 2, Const $bS = True)
    ; By AspirinJunkie
    Local $FFFF, $FFNF, $aD
    Local Static $sRet, $hDll
    If $bS Then
    $hDll = DllOpen("kernel32.dll")
    If NOT ($iF = 3 Or $iF = 1 Or $iF = 2) Then Return SetError(1, DllClose($hDll), "")
    EndIf
    For $I In StringSplit($SSD, "|", 2)
    If StringRight($I, 1) = "\" Then $I = StringTrimRight($I, 1)
    If Not FileExists($I) Then ContinueLoop
    $FFFF = FileFindFirstFile($I & "\*")
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    If BitAND(StringRegExp($FFNF, $sPat) * 2, $iF) Then $sRet &= $I & "\" & $FFNF & "|"
    $aD = DllCall($hDll, "dword", "GetFileAttributesW", "wstr", $I & "\" & $FFNF)
    If @error Or BitAND($aD[0], 1024) Then ContinueLoop
    FlwStrRek($I & "\" & $FFNF, $sPat, $iF, 2, False)
    ElseIf BitAND(StringRegExp($FFNF, $sPat), $iF) Then
    $sRet &= $I & "\" & $FFNF & "|"
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Next
    If $bS Then
    DllClose($hDll)
    Local $aRet = StringSplit(StringTrimRight($sRet, 1), "|", $flgSS)
    $sRet = ""
    Return $aRet
    EndIf
    EndFunc ;==>FlwStrRek

    [/autoit]

    @ScriptDir ist bei mir ein Verzeichnis, in dem sich insgesamt 2196 Dateien/Verzeichnisse befinden (viele Unterverzeichnisse).
    Ich habe das obige Script mehrmals gestartet und stets ist die iterative Funktion schneller. Nicht viel (60 ms statt 67 ms), aber immerhin. :)

  • Interessanter Effekt:
    Auf meine HDD (99394 Dateien) losgelassen sind beide Funktionen ziemlich gleichauf (etwa 100ms auseinander - wie du schon sagst ist dabei die iterative schneller).
    Bei meiner SSD (125572 Dateien) ist die rekursive Funktion dann fast 3x so schnell wie die iterative 6,2s : 17,3s ...
    Keine Ahnung wie ich mir das erklären soll... :huh:

  • Ich habe deine Funktion (rek.) gegen meine laufen lassen - da sind ja Welten dazwischen!

    0,8 Sekunden (deine Funktion)
    7,4 Sekunden (meine Funktion)

    -=> Faktor 9,25!

    Krass!

    Well done AspirinJunkie! 8o

    Gruß,
    UEZ

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

  • Also ich kann auch absolut nichts logisches daran erkennen.
    Mag sein das es Zufall ist dass es bei mir gerade bei der SSD auftritt aber auf das Dateisystem wird bei beiden Funktionen auf die gleiche Art und Weise zugegriffen. Nur die Art und Weise wie die Unterordner abgearbeitet werden unterscheidet sich - mehr nicht.
    Also sollte die Hardware darunter eigentlich keinen Unterschied hervorrufen.
    Die Messung sollte auch in Ordnung sein.
    Mehrfache direkte Hintereinandermessung, wechseln der Reihenfolge etc.
    Wie gesagt - ich muss passen - ich kann es mir einfach nicht erklären aber es ist tritt genau so auf. ?(

  • Also sollte die Hardware darunter eigentlich keinen Unterschied hervorrufen.


    Theorie
    Bei der iterative Funktion werden weniger Resourcen genutzt, die rekursive Funktion muss ja immer neue Speicherbereiche für die Unterfunktionen eröffnen.

    Bei einer normalen HDD ist die Antwortgeschwindigkeit der Hardware so langsam, dass sie selber der limitierende Faktor bei der Gesamtgeschwindigkeit der Funktion ist.

    Bei einer SSD ist die Antwortgeschwindigkeit hingegen soviel höher, dass der Funktionsaufruf selber zum limitierenden Faktor bei der Gesamtgeschwindigkeit der Funktion wird.

  • Interessante Theorie - nur - es ist genau umgekehrt - die rekursive ist schneller als die iterative.
    Nach deiner Theorie sollte der limitierende Faktor dann also der sein das einen Pfad an einen String anfügen und einen Teilstring abschneiden länger dauert als der Overhead eines extra Funktionsaufrufes...
    Wäre die iterative schneller würde ich ebenfalls den fehlenden Overhead der Rekursionsaufrufe als Performance-Faktor vermuten (das war ehrlich gesagt auch der Grund warum ich überhaupt mal versucht habe das ganze iterativ zu schreiben).
    Aber hier ist es wie gesagt ja komplett anders herum - und das kann ich mir gerade überhaupt nicht erklären.

    Einmal editiert, zuletzt von AspirinJunkie (1. Februar 2011 um 23:26)

  • Interessante Theorie - nur - es ist genau umgekehrt - die rekursive ist schneller als die iterative.
    Nach deiner Theorie sollte der limitierende Faktor dann also der sein das einen Pfad an einen String anfügen und einen Teilstring abschneiden länger dauert als der Overhead eines extra Funktionsaufrufes...


    Tjaaaa, vielleicht sollten wir mal ein Testscript dazu schreiben, welches nicht auf die Festplatte zugreift, sondern nur die beiden Aufrufarten miteinander vergleicht :)? Ist gut möglich das die Stringoperationen langsamer werden als die Iterationen... ich hatte mal was davon gelesen, dass bei Stringoperationen der String im Speicher gedoppelt wird, bei sehr langen Strings natürlich sehr zeitintensiv. Vielleicht ist deshalb die Rekursion schneller? Sie belegt zwar auch immer neuen Speicher, verdoppelt aber nicht immer den gesamten Return. Ich überleg mir mal was :)...

    • Offizieller Beitrag

    Irgendwas stimmt nicht so ganz. Ich habe gerade mal die beiden Funktionen auf mein Systemlaufwerk "c:\" losgelassen (mehrmals, damit der Cache benutzt wird):

    Iterativ: ca. 19 sek, Ergebnis: 280736 Dateien/Verzeichnisse
    Rekursiv: ca. 9 sek, Ergebnis: 276358 Dateien/Verzeichnisse

    Vom Geschwindigkeitsunterschied her kommt das Deiner Messung mit der SSD recht nahe, aber die rekursive Funktion unterschlägt hier einige Dateien/Verzeichnisse. Welche kann ich nicht sagen, habe die Ergebnisse nicht im einzelnen verglichen. :D
    Meine eigene rekursive Funktion liefert aber auch 280736 Ergebnisse, von daher muss in Deiner rekursiven Funktion ein Fehler stecken. Kann es sein, dass die statische Variable diese Probleme verursacht?

    Allerdings benötigt meine rekursive Funktion auch nur etwas über 9 sek. Im Vergleich zu Deiner iterativen ist sie also ca. doppelt so schnell. Bei kleineren Verzeichnisbäumen zieht Deine iterative Funktion aber gnadenlos an meiner vorbei.
    Das heißt, Deine iterative Funktion hat noch Probleme mit großen Verzeichnisbäumen. :huh:

  • aber die rekursive Funktion unterschlägt hier einige Dateien/Verzeichnisse. Welche kann ich nicht sagen, habe die Ergebnisse nicht im einzelnen verglichen.

    Ich habe es mal verglichen und dabei festgestellt dass nur der Inhalt von einem Verzeichnis mit Unterordnern nicht von der rekursiven Version übernommen wird: "C:\Users\All Users\".
    Tja warum ausgerechnet nur das? - keine Ahnung bisher.

    . Bei kleineren Verzeichnisbäumen zieht Deine iterative Funktion aber gnadenlos an meiner vorbei.
    Das heißt, Deine iterative Funktion hat noch Probleme mit großen Verzeichnisbäumen.

    Ja das ist normal und sollte nicht groß veränderbar sein.
    Alle Verzeichnisse werden in einem String gespeichert und dann nacheinander abgearbeitet.
    Wenn ein String (hier der Verzeichnisname) angehängt wird muss, wenn dahinter kein Speicher mehr frei ist, der gesamte String in einen neuen, größeren Bereich umkopiert werden (ähnlich wie bei ReDim von Arrays).
    Je größer der String umso länger dauerts.
    Ideal wäre ein Stack direkt in AutoIt implementiert - da muss nichts umkopiert werden.
    Aber der .Net-Stack z.B. ist zu langsam wie wir oben sehen können.
    Daher hier der Ausweich auf einen String.
    Als Alternative dafür wäre z.B. das Dictionary-Objekt ganz vernünftig - damit sollte man bei vielen Verzeichnissen wieder ein paar Sekunden rausholen können:

    Spoiler anzeigen
    [autoit]

    ; #FUNCTION# ======================================================================================
    ; Name ..........: FLwStrDict()
    ; Description ...: Findet Dateien und/oder Ordner in einem Verzeichnisbaum
    ; Syntax ........: FLwStrDict($sSD, Const[ $sPat = '', Const[ $iF = 3]])
    ; Parameters ....: $sSD - Suchort (mehrere Suchorte durch | trennen)
    ; Const $sPat - [optional] regulärer Ausdruck für zu findende Datei/Ordnernamen (default:'')
    ; Const $iF - [optional] 1=nur Dateien, 2=nur Ordner, 3=Dateien+Ordner (default:3)
    ; Const $iSSFlag - [optional] 0=Rückgabearray mit Anzahl Elemente in $Array0, 2=Rückgabearray ohne Anzahl Elemente
    ; Return values .: Success - Return Array with Matches with $Array[0] = Count
    ; Failure - Return "" and set @error
    ; Author ........: AspirinJunkie
    ; Remarks .......: Funktionsweise ist iterativ - nicht rekursiv
    ; Example .......: Yes
    ; #include <Array.au3>
    ; $aTmp = FLwStr(@WindowsDir & '|' & @ProgramFilesDir, "^Temp$", 2)
    ; _ArrayDisplay($aTmp)
    ; =================================================================================================
    Func FLwStrDict($sSD, Const $sPat = '', Const $iF = 3, Const $iSSFlag = 0)
    ;by AspirinJunkie
    Local $sRet = "", $oSubD = ObjCreate('Scripting.Dictionary')
    For $i In StringSplit($sSD, '|', 2)
    If StringRight($i, 1) = '\' Then $i = StringTrimRight($i, 1)
    If Not FileExists($i) Then Return SetError(2, 0, "")
    $oSubD.add($i, 0)
    Next
    Local $FFFF, $FFNF
    Local $aD, $hDLL = DllOpen('kernel32.dll')
    If Not ($iF = 3 Or $iF = 1 Or $iF = 2) Then Return SetError(3, 0, "")

    [/autoit] [autoit][/autoit] [autoit]

    Do
    For $sDir In $oSubD.Keys
    $oSubD.Remove($sDir)
    $FFFF = FileFindFirstFile($sDir & '\*')
    If $FFFF <> -1 Then
    Do
    $FFNF = FileFindNextFile($FFFF)
    If @error Then ExitLoop
    If @extended Then
    If BitAND(StringRegExp($FFNF, $sPat) * 2, $iF) Then $sRet &= $sDir & '\' & $FFNF & '\|'
    $aD = DllCall($hDLL, 'dword', 'GetFileAttributesW', 'wstr', $sDir & '\' & $FFNF)
    If @error And BitAND($aD[0], 0x400) Then ContinueLoop
    $oSubD.add($sDir & '\' & $FFNF, 0)
    ElseIf BitAND(StringRegExp($FFNF, $sPat), $iF) Then
    $sRet &= $sDir & '\' & $FFNF & '|'
    EndIf
    Until 0
    FileClose($FFFF)
    EndIf
    Next
    Until $oSubD.Count = 0
    DllClose($hDLL)
    Return StringSplit(StringTrimRight($sRet, 1), '|', $iSSFlag)
    EndFunc

    [/autoit]