2013-07-23 23 views
7

Buongiorno a tutte, Ho una cartella che contiene migliaia di sottodirectory a diverse profondità. Ho bisogno di elencare tutte le directory che non contengono sottodirectory (il proverbiale "fine della linea"). Va bene se contengono file. C'è un modo per farlo con EnumerateDirectories?C# Elenca tutte le sottodirectory "foglia" con EnumerateDirectories

Ad esempio, se un EnumerateDirectories completamente ricorsive restituiti:

/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 

Sono solo interessato a:

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

risposta

14

Questo dovrebbe funzionare:

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

contatore votato per rimuovere -1 ... sembrava funzionare per me – Sayse

+0

+1, che appena mi rende orgoglioso ! –

+0

nice One-Liners – m1m1k

3

Se si desidera evitare di chiamare lo EnumerateDirectories() due volte per ogni directory, è possibile implementarlo in questo modo:

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; 
} 

Ho fatto alcuni test di temporizzazione, e per me questo approccio è più di due volte più veloce utilizzando l'approccio Linq.

Ho eseguito questo test utilizzando un build di rilascio, eseguito all'esterno di qualsiasi debugger. Mi sono imbattuto su uno SSD che contiene un gran numero di cartelle - il numero totale di cartelle foglia fu 25035.

I miei risultati per la seconda esecuzione del programma (la prima esecuzione è stato quello di preriscaldare la cache del disco):

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 

Come si può vedere, l'utilizzo dell'approccio di rendimento è notevolmente più veloce. (Probabilmente perché non enumera ogni cartella due volte.)

mio codice di prova:

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 è valutato pigramente, quindi la chiamata extra fatta nella risposta di Tim è piuttosto economica. Quando ho confrontato il tuo codice con Tim's, Tim ha funzionato in meno della metà delle volte. Immagino che questo sia dovuto al fatto che l'uso di un iteratore aggiunge ricorsivamente un sacco di spese generali. – Brian

+0

@Brian Il test è stato eseguito più volte per eliminare gli artefatti causati dalla memorizzazione nella cache del primo passaggio? –

+0

Sì. Ho eseguito i test un paio di volte prima di iniziare, compilato con ottimizzazioni e ho eseguito ogni test una sola volta (per evitare artefatti di jitter) prima di avviare i timer. – Brian