Laden...

Verzeichnislisting aller leeren Verzeichnisse noch mit unliebsamen Seiteneffekt

Erstellt von MrFluffy vor 12 Jahren Letzter Beitrag vor 12 Jahren 4.026 Views
M
MrFluffy Themenstarter:in
32 Beiträge seit 2011
vor 12 Jahren
Verzeichnislisting aller leeren Verzeichnisse noch mit unliebsamen Seiteneffekt

Tach,

eigentlich ist das Problem trivial aber ich habe einen Seiteneffekt, bei dem ich nicht weiß wie ich das geschickt lösen kann.


public void traverseDirsAndRemoveEmpty(DirectoryInfo dinfo, List<Dir> dirList)
        {
            foreach (DirectoryInfo dSubInfo in dinfo.GetDirectories())
            {
                    traverseDirsAndRemoveEmpty(dSubInfo, dirList);
            }
            if (dinfo.GetFiles().Length == 0)
            {
                Dir dir = new Dir();
                dir.Dir_fullname = dinfo.FullName;
                dir.Dir_name = dinfo.Name;
                dirList.Add(dir);
            }
        }

Wenn ich das auf diese Verzeichnisstruktur anwende:
C:\hans\peter\johannes
C:\hans\eugen\heinz\gustav\max

C:\hans\1\1.1\x.jpg
C:\hans\2\2.1\x.jpg

Ergibt sich das Problem, dass er im zweiten Teil beim Verzeichnis X abbricht, da es hier Dateien gibt. Das aber bedeutet das er den kompletten Pfad z. B. C:\hans\1\1.1 nicht auflisten darf, da diese ja somit nicht leer sind

Irgendwelche Vorschläge?

276 Beiträge seit 2007
vor 12 Jahren

Hallo MrFluffy,

was möchtest du denn machen?

Alle Verzeichnisse löschen, die leer sind? Wieso willst du dann die mit den Files auflisten?

Gruss

nitro

===

EDIT:
In etwa so? (ungetestet)


public static void TraverseFileSystem(String strDir, List<string> dirList)
        {
            try
            {
                string[] a = Directory.GetDirectories(strDir);

                // Verzeichnis hat Unterverzeichnisse
                if (a.Length > 0)
                {
                    foreach (String strSubDir in Directory.GetDirectories(strDir))
                    {
                        TraverseFileSystem(strSubDir, dirList);
                    }
                }
                // Verzeichnis ist leer
                else
                {
                    dirList.Add(strDir);
                }
            }
            catch (Exception)
            {
                // 3. Statt Console.WriteLine hier die gewünschte Aktion
                Console.WriteLine("error: " + strDir);
            }
        }

abgeschrieben von

[Snippet] Verzeichnisse und Dateien rekursiv durchlaufen

Gruss

nitro

M
MrFluffy Themenstarter:in
32 Beiträge seit 2011
vor 12 Jahren

was möchtest du denn machen?
Alle Verzeichnisse löschen, die leer sind? Wieso willst du dann die mit den Files auflisten?

Ich möchte alle Verzeichnisebenen ausgeben die leer sind. Um leer zu sein dürfen keine Dateien enthalten sein. Außerdem muss logischerweise der komplette Verzeichnisbaum durchlaufen werden.

Das Problem ist am Ende des Durchlaufs gibt es zwei Fälle
a) Im letzten Subordner sind Dateien enthalten
b) Im letzten Subordner sind keine Dateien enthalten

Im Fall a) darf der komplette Baum bis zum letzten Subordner nicht angezeigt werden
Im Fall b) darf der komplette Baum gelöscht werden

In etwa so?

Rein logisch betrachtet, verstösst dieser Code gegen das "leer" Kriterium im Else Zweig können nämlich noch Dateien sein.

Wenn man diesen Fall abfängt, werden die letzten leeren Subordner folglich angezeigt.

Was man nun tun könnte wäre das Pferd von hinten aufzuspannen, d. h. solange im
Pfad zurückspringen bis entweder das "leer" Kriterium verletzt wird oder wir uns im Startpfad befinden.

Da ich ohnehin schon eine gigantische Ordnerstruktur habe auf das ich das ganze anwenden möchte. Wäre es sehr blöd wenn mir der Stack um die Ohren fliegt.

Deshalb frage ich mich ob das nicht einfacher geht.

Zur Veranschaulichung habe ich mal meinen Code sinngemäß angepasst und die Dateien berrücksichtigt


 public void traverseDirsAndRemoveEmpty(DirectoryInfo dinfo, List<Dir> dirList)
        {
            if (dinfo.GetDirectories().Length > 0)
            {
                foreach (DirectoryInfo dSubInfo in dinfo.GetDirectories())
                {
                    traverseDirsAndRemoveEmpty(dSubInfo, dirList);
                }
            }
            else
            {
                if (dinfo.GetFiles().Length == 0)
                {
                    Dir dir = new Dir();
                    dir.Dir_fullname = dinfo.FullName;
                    dir.Dir_name = dinfo.Name;
                    dirList.Add(dir);
                }
                
            }


Nochmals zu Verdeutlichung

hans\franz\dampf\terminkalender.xls -> Verstoss gegen das Leer Kriterium, da dampf nicht leer ist

peter\maier\sigi\bla\ -> Verzeichnis bla ist leer, wenn Parant Dir ebenfalls leer ist kann es auch entfernt werden und dessen Parent Dir bis zum Startdir

wuff\bg\fdg\fgdfg\
und wuff\bg\file.xls
und wuff\bg\file2.xls
-> Verzeichnis fgdfg ist leer, fdg ist leer, bg beinhaltet Dateien es ist nicht leer -> Abbruch

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo MrFluffy,

ich hab mich mit der Problemstellung nicht befasst, aber

Da ich ohnehin schon eine gigantische Ordnerstruktur habe auf das ich das ganze anwenden möchte. Wäre es sehr blöd wenn mir der Stack um die Ohren fliegt.

Statt der Rekursion kann auch ein Stack<T> verwendet werden und somit gehts du der StackOverflowException aus dem Weg.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

D
14 Beiträge seit 2010
vor 12 Jahren

Hallo MrFluffy,

du rufst ja die Procedure rekursiv auf, um von einer Verzeichniswurzel in die Tiefe zu gehen. Was fehlt ist die Rückmeldung ob eine Datei gefunden wurde, so dass du für das Parentverzeichnis abschließend entscheiden kannst, ob es in Liste der "leeren Verzeichnisse" mit aufgenommen werden kann.
Zudem sollte immer - also unabhängig vor der Existenz von Subdirs - geprüft werden, ob eine Datei vorliegt.

Folgende Möglichkeiten bestehen:

  1. Du überwachst, ob der Liste ein Element hinzugefügt wurde,
  2. Du ergänzt die Argumentliste um ein boolesches out-Element FileFound
  3. Du machst aus der void eine Funktion, die ein Bool zurückgibt.

Hier mal Ansatz Nr. 3:


public bool traverseDirsAndRemoveEmpty(DirectoryInfo dinfo, List<Dir> dirList)
        {
            bool FileFound = false;
            if (dinfo.GetDirectories().Length > 0)
            {
                foreach (DirectoryInfo dSubInfo in dinfo.GetDirectories())
                {
                    FileFound = FileFound | traverseDirsAndRemoveEmpty(dSubInfo, dirList);
                }
            }
            if (!FileFound)
            {
                if (dinfo.GetFiles().Length == 0)
                {
                    Dir dir = new Dir();
                    dir.Dir_fullname = dinfo.FullName;
                    dir.Dir_name = dinfo.Name;
                    dirList.Add(dir);
                    return false;
                }

            } 
            return true;
        }

Nicht getestet!

Gruß Dani

M
MrFluffy Themenstarter:in
32 Beiträge seit 2011
vor 12 Jahren

Statt der Rekursion kann auch ein Stack<T> verwendet werden und somit gehts du der StackOverflowException aus dem Weg.

Stimmt, jetzt wo Du es sagst 😉

Danke für den Hinweis

du rufst ja die Procedure rekursiv auf, um von einer Verzeichniswurzel in die Tiefe zu gehen. Was fehlt ist die Rückmeldung ob eine Datei gefunden wurde, so dass du für das Parentverzeichnis abschließend entscheiden kannst, ob es in Liste der "leeren Verzeichnisse" mit aufgenommen werden kann.

Genau 😉

ich muss sagen sehr elegante Lösung, insbesondere die Verwendung des Bit Or die ich noch nie verwendete übt eine gewisse Faszination auf mich aus.

So einfach und so effektiv 👍

Hatte ein schönes "AHA" Erlebnis, vielen Dank dafür

771 Beiträge seit 2009
vor 12 Jahren

Hi,

auf die Variable kann man sogar verzichten, da man mit


if (traverseDirsAndRemoveEmpty(dSubInfo, dirList))
    return true;

einfach aus der Schleife springen kann, sobald man ein nicht-leeres Verzeichnis gefunden hat.
Außerdem würde ich die Abfrage auf die Dateien zuerst durchführen, damit man nicht erst die Unterordner iterieren muss.

D
14 Beiträge seit 2010
vor 12 Jahren

Hi Cat,

auf die Variable kann man sogar verzichten, da man ... einfach aus der Schleife springen kann, sobald man ein nicht-leeres Verzeichnis gefunden hat.
Außerdem würde ich die Abfrage auf die Dateien zuerst durchführen, damit man nicht erst die Unterordner iterieren muss.

Wenn du bei der erstbesten Gelegenheit herausspringst, hast du aber nicht untersucht, ob es noch "leere" Verzeichnisse in der tieferen Struktur gibt. Dann dürfte das Listing aber nur sehr kurz und unvollständig sein.

Gruß Dani

771 Beiträge seit 2009
vor 12 Jahren

Hast Recht - ich hatte die Aufgabenstellung nicht genau genug durchgelesen.

107 Beiträge seit 2011
vor 12 Jahren

Oder um den nicht-rekursiven Ansatz von Günni weiterzuverfolgen:


  public void getDirectories(String path, List<String> dirList)
        {
            directoryStack.Push(path);
            
            while (directoryStack.Count > 0)
            {
                String  currentDirectory = directoryStack.Pop();

                foreach (String successorDirectory in Directory.GetDirectories(currentDirectory))
                {
                    directoryStack.Push(successorDirectory);
                }

                if (Directory.GetFiles(currentDirectory).Length == 0 
                    && Directory.GetDirectories(currentDirectory).Length == 0)
                 {

                     dirList.Add(currentDirectory);

                    String predecessorDirectory = currentDirectory;
                    bool validParent = true;

                    while (Directory.GetFiles(predecessorDirectory).Length == 0 && validParent)
                    {
                        dirList.Add(predecessorDirectory);

                        List<DirectoryInfo> nextParent = Directory.GetParent(predecessorDirectory).GetDirectories().ToList();
                        
                        int i = nextParent.FindAll((x) =>
                        {
                            if (dirList.Contains(x.FullName) | x.GetFiles().Length == 0)
                            {
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        }
                        ).Count;

                        if (i == nextParent.Count)
                        {
                            predecessorDirectory = Directory.GetParent(predecessorDirectory).FullName;
                        }
                        else
                        {
                            validParent = false;
                        }
                    }
                }
            }
        }

Der Hauptvorteil von nicht-rekursiven Methoden ist, dass sie oft deutlich inituitiver sind als ihre rekursiven Pendants.
Ich versuche eigentlich immer, alle rekursiven Methoden sofort in iterative Methoden umzubauen, damit andere Entwickler den Code leichter verstehen.

q.e.d.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo,

Der Hauptvorteil von nicht-rekursiven Methoden ist, dass sie oft deutlich inituitiver sind als ihre rekursiven Pendants.

Das kann auch andersrum sein. Üblicherweise werden die rekursiven Varianten als inituitiver angesehen. Aber das wurde im Forum schon diskutiert. Siehe Rekursion grundsätzlich langsamer? und Gibt es Rekursionen die sich nicht in eine Iteration umwandeln lassen?. Von daher bitte hier keine Diskussion darüber starten.

Der Hauptvorteil ist dass keine StackoverflowException auftritt.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

M
MrFluffy Themenstarter:in
32 Beiträge seit 2011
vor 12 Jahren
                         
                        int i = nextParent.FindAll((x) =>  
                        {  
                            if (dirList.Contains(x.FullName) | x.GetFiles().Length == 0)  
                            {  
                                return true;  
                            }  
                            else  
                            {  
                                return false;  
                            }  
                        }  
                        ).Count;  
  

Kleine Rückfrage diesbezüglich. Wie nennt man dieses Sprachkonstrukt mit dem => Operator in der C# Welt?

Da er mir nicht geläufig ist wollte ich mir diesbezüglich gerne Informationen einholen, aber das ist etwas schwierig ohne das passende Stichwort.

Danke

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo MrFluffy,

Lambda-Operator. Siehe auch LinQ und Lambda-Ausdrücke.

In Visual Studio, falls du das hast, F1 drücken wirkt oft Wunder 😉

Das Zitierte ist übrigens was für Coding-Style-Horror, da es viel einfacher und kürzer geht:


                            if (dirList.Contains(x.FullName) | x.GetFiles().Length == 0)
                            {
                                return true;
                            }
                            else
                            {
                                return false;
                            }

entspricht


return (dirList.Contains(x.FullName) || x.GetFiles().Length == 0); // das OR hab ich auch noch Shortcut gemacht

Siehe auch [Tipp] Anfängerfehler == true / == false

Und insgesamt kann das noch verkürzt werden zu:


int i = nexParent.Count(x => dirList.Contains(x.FullName) || x.GetFiles().Length == 0);

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo zusammen,

Statt der Rekursion kann auch ein Stack<T> verwendet werden und somit gehts du der StackOverflowException aus dem Weg.

die maximale Rekursionstiefe beim Durchsuchen einer Verzeichnisstruktur entspricht - vom Startverzeichnis aus gemessen - der maximalen Tiefe der Verzeichnisstruktur (mit anderen Worten der Anzahl der Verzeichnisse im längsten (relativen) Pfad, wobei die Länge hier nicht in Anzahl Zeichen, sondern in Anzahl Verzeichnisse gemessen wird). Die Breite der Verzeichnisstruktur und die Gesamtzahl der zu durchsuchenden Verzeichnisse spielen für den Stackverbrauch überhaupt keine Rolle. Üblicherweise beträgt die Tiefe selbst bei umfangreichen Verzeichnisstrukturen nicht mehr als 10. Ich habe noch nie eine Verzeichnisstruktur tiefer als 20 gesehen. Aber selbst wenn man pessimistisch von einer Tiefe von maximal 100 oder sogar 1000 ausgeht, ist man von einer StackOverflowException Lichtjahre entfernt. Es gibt daher keinen Grund, eine nicht rekursive Implementierung zu verwenden, um eine StackOverflowException zu vermeiden.

Selbst wenn man bis zum theoretischen Maximum geht (bei NTFS ist die maximale Länge eines Dateipfades 32767 Zeichen. Jedes Verzeichnis verbraucht zwei Zeichen, eins für den Namen, eins fürs Trennzeichen. Die maximale Rekursionstiefe beträgt also 16383) ist man von einer StackOverflowException weit entfernt. Danke an MarsStein für diese ergänzendes Berechnung.

herbivore