2013-07-23 18 views
7

Dzień dobry wszystkim, Mam folder zawierający tysiące podkatalogów na różnych głębokościach. Muszę wyświetlić wszystkie katalogi, które nie zawierają podkatalogów (przysłowiowy "koniec linii"). W porządku, jeśli zawierają pliki. Czy jest sposób na to zrobić z EnumerateDirectories?C# Lista wszystkich podkatalogów "liścia" z EnumerateDirectories

Na przykład, jeśli w pełni rekurencyjne EnumerateDirectories powrócił:

/files/ 
/files/q 
/files/q/1 
/files/q/2 
/files/q/2/examples 
/files/7 
/files/7/eb 
/files/7/eb/s 
/files/7/eb/s/t 

Jestem zainteresowany tylko:

/files/q/1 
/files/q/2/examples 
/files/7/eb/s/t 

Odpowiedz

14

To powinno działać:

var folderWithoutSubfolder = Directory.EnumerateDirectories(root, "*.*", SearchOption.AllDirectories) 
    .Where(f => !Directory.EnumerateDirectories(f, "*.*", SearchOption.TopDirectoryOnly).Any()); 
+3

przeciw głosowało usunąć -1 ... wydawało się działać dla mnie – Sayse

+0

+1, że właśnie czyni mnie dumnym ! –

+0

nice One-liners – m1m1k

3

Jeśli chcesz unikaj dzwonienia pod numer EnumerateDirectories() dwa razy dla każdego katalogu, możesz go zaimplementować w następujący sposób:

public IEnumerable<string> EnumerateLeafFolders(string root) 
{ 
    bool anySubfolders = false; 

    foreach (var subfolder in Directory.EnumerateDirectories(root)) 
    { 
     anySubfolders = true; 

     foreach (var leafFolder in EnumerateLeafFolders(subfolder)) 
      yield return leafFolder; 
    } 

    if (!anySubfolders) 
     yield return root; 
} 

Zrobiłem kilka testów czasowych, a dla mnie takie podejście jest ponad dwa razy szybciej niż przy użyciu podejścia Linq.

Uruchomiłem ten test, używając kompilacji wydania, uruchamianego poza dowolnym debugerem. Pobiegłem go na SSD zawierającej dużą liczbę folderów - łączna liczba folderów liść 25035.

moje wyniki za drugi przebiegu programu (pierwszy bieg był do podgrzewania cache dysku):

Calling Using linq. 1 times took 00:00:08.2707813 
Calling Using yield. 1 times took 00:00:03.6457477 
Calling Using linq. 1 times took 00:00:08.0668787 
Calling Using yield. 1 times took 00:00:03.5960438 
Calling Using linq. 1 times took 00:00:08.1501002 
Calling Using yield. 1 times took 00:00:03.6589386 
Calling Using linq. 1 times took 00:00:08.1325582 
Calling Using yield. 1 times took 00:00:03.6563730 
Calling Using linq. 1 times took 00:00:07.9994754 
Calling Using yield. 1 times took 00:00:03.5616040 
Calling Using linq. 1 times took 00:00:08.0803573 
Calling Using yield. 1 times took 00:00:03.5892681 
Calling Using linq. 1 times took 00:00:08.1216921 
Calling Using yield. 1 times took 00:00:03.6571429 
Calling Using linq. 1 times took 00:00:08.1437973 
Calling Using yield. 1 times took 00:00:03.6606362 
Calling Using linq. 1 times took 00:00:08.0058955 
Calling Using yield. 1 times took 00:00:03.6477621 
Calling Using linq. 1 times took 00:00:08.1084669 
Calling Using yield. 1 times took 00:00:03.5875057 

Jak widać, wykorzystanie metody uzyskiwania zysku jest znacznie szybsze. (Prawdopodobnie dlatego, że nie wyliczać każdego folderu dwukrotnie).

Mój kod testowy:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.IO; 
using System.Linq; 

namespace Demo 
{ 
    class Program 
    { 
     private void run() 
     { 
      string root = "F:\\TFROOT"; 

      Action test1 =() => leafFolders1(root).Count(); 
      Action test2 =() => leafFolders2(root).Count(); 

      for (int i = 0; i < 10; ++i) 
      { 
       test1.TimeThis("Using linq."); 
       test2.TimeThis("Using yield."); 
      } 
     } 

     static void Main() 
     { 
      new Program().run(); 
     } 

     static IEnumerable<string> leafFolders1(string root) 
     { 
      var folderWithoutSubfolder = Directory.EnumerateDirectories(root, "*.*", SearchOption.AllDirectories) 
       .Where(f => !Directory.EnumerateDirectories(f, "*.*", SearchOption.TopDirectoryOnly).Any()); 

      return folderWithoutSubfolder; 
     } 

     static IEnumerable<string> leafFolders2(string root) 
     { 
      bool anySubfolders = false; 

      foreach (var subfolder in Directory.EnumerateDirectories(root)) 
      { 
       anySubfolders = true; 

       foreach (var leafFolder in leafFolders2(subfolder)) 
        yield return leafFolder; 
      } 

      if (!anySubfolders) 
       yield return root; 
     } 
    } 

    static class DemoUtil 
    { 
     public static void Print(this object self) 
     { 
      Console.WriteLine(self); 
     } 

     public static void Print(this string self) 
     { 
      Console.WriteLine(self); 
     } 

     public static void Print<T>(this IEnumerable<T> self) 
     { 
      foreach (var item in self) 
       Console.WriteLine(item); 
     } 

     public static void TimeThis(this Action action, string title, int count = 1) 
     { 
      var sw = Stopwatch.StartNew(); 

      for (int i = 0; i < count; ++i) 
       action(); 

      Console.WriteLine("Calling {0} {1} times took {2}", title, count, sw.Elapsed); 
     } 
    } 
} 
+0

EnumerateDirectories jest leniwie oceniany, więc dodatkowe połączenie wykonane w odpowiedzi Tima jest dość tanie. Kiedy porównałem twój kod z Timem, Tim miał mniej niż połowę czasu. Wyobrażam sobie, że dzieje się tak dlatego, że użycie iteratora rekursywnie zwiększa obciążenie. – Brian

+0

@Brian Czy uruchomiłeś test wiele razy, aby wyeliminować artefakty spowodowane przez buforowanie dysku przy pierwszym uruchomieniu? –

+0

Tak. Przeprowadziłem testy kilkaset razy, zanim zacząłem, skompilowałem z optymalizacją i raz uruchomiłem każdy test (aby uniknąć artefaktów jittera) przed uruchomieniem timerów. – Brian

Powiązane problemy