Good morning all, I have a folder which contains thousands of subdirectories at different depths. I need to list all of the directories which don't contain subdirectories (the proverbial "end of the line"). It's fine if they contain files. Is there a way to do this with EnumerateDirectories?
For example, if a fully recursive EnumerateDirectories returned:
/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
I'm only interested in:
/files/q/1
/files/q/2/examples
/files/7/eb/s/t
This should work:
var folderWithoutSubfolder = Directory.EnumerateDirectories(root, "*.*", SearchOption.AllDirectories)
.Where(f => !Directory.EnumerateDirectories(f, "*.*", SearchOption.TopDirectoryOnly).Any());
If you want to avoid calling EnumerateDirectories()
twice for each directory, you can implement it like so:
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;
}
I did some timing tests, and for me this approach is more than twice as fast as using the Linq approach.
I ran this test using a release build, run outside of any debugger. I ran it on an SSD containing a large number of folders - the total number of LEAF folders was 25035.
My results for the SECOND run of the program (the first run was to preheat the disk cache):
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
As you can see, using the yield approach is significantly faster. (Probably because it doesn't enumerate each folder twice.)
My test code:
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);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With