【问题标题】:Eliminate Recursion from Delete Empty Directories Algorithm从删除空目录算法中消除递归
【发布时间】:2012-03-21 07:28:09
【问题描述】:

我想我以前知道怎么做,但我好像忘记了。

我有一个递归算法来删除目录树中的所有空目录:

static bool DeleteDirectoriesRecursive(string path)
{
    var remove = true;
    foreach (var dir in System.IO.Directory.GetDirectories(path))
    {
        remove &= DeleteDirectoriesRecursive(dir);
    }
    if (remove &= (System.IO.Directory.GetFiles(path).Length == 0))
        System.IO.Directory.Delete(path);
    return remove;
}

我试图从该算法中消除递归,而不是“修复”该算法(即the similar question 不使用remove 变量,但我想保留它)。

我已经启动了一个新函数,使用Stack<> 类,但是我想不出一个好的方法来返回基本路径并执行子目录确定的操作。我想解开非尾递归需要更多的努力。

【问题讨论】:

  • 为什么要用另一个(你的)堆栈(IL 堆栈)替换?你能从中得到什么?
  • 知识。没有人说我会在生产代码中这样做。
  • @zmbq - 我就是这么想的。这实际上是一个非常好的递归使用,因为它使用调用堆栈来爬取一棵树。尝试用自己的堆栈做同样的事情只会使代码变得更长、更难理解,并且也不会提供任何性能优势。
  • @SteveWortham:递归的大多数用途都很好,但有时您必须使用显式堆栈来实现它,因为溢出 IL 堆栈是致命的。
  • @Gabe - 好点。目录结构必须非常庞大,您才会遇到 StackOverflowException,但我想这是可能的。

标签: c# .net algorithm recursion stack


【解决方案1】:

由于 Gabe 在使用递归时提到了 StackOverflowException 的可能性,我受到启发,没有它就可以完成这项工作。我使用代码here 作为起点。这就是我想出的……

public static bool DeleteDirectories(string root)
{
    bool removed = false;

    var dirs = new Stack<string>();
    var emptyDirStack = new Stack<string>();
    var emptyDirs = new Dictionary<string, int>();

    if (!System.IO.Directory.Exists(root))
    {
        throw new ArgumentException();
    }
    dirs.Push(root);

    while (dirs.Count > 0)
    {
        string currentDir = dirs.Pop();

        string[] subDirs;
        try
        {
            subDirs = System.IO.Directory.GetDirectories(currentDir);
        }
        catch (UnauthorizedAccessException e)
        {
            Console.WriteLine(e.Message);
            continue;
        }
        catch (System.IO.DirectoryNotFoundException e)
        {
            Console.WriteLine(e.Message);
            continue;
        }

        if (Directory.GetFiles(currentDir).Length == 0)
        {
            emptyDirStack.Push(currentDir);
            emptyDirs.Add(currentDir, subDirs.Length); // add directory path and number of sub directories
        }

        // Push the subdirectories onto the stack for traversal.
        foreach (string str in subDirs)
            dirs.Push(str);
    }

    while (emptyDirStack.Count > 0)
    {   
        string currentDir = emptyDirStack.Pop();
        if (emptyDirs[currentDir] == 0)
        {
            string parentDir = Directory.GetParent(currentDir).FullName;
            Console.WriteLine(currentDir); // comment this line
            //Directory.Delete(currentDir); // uncomment this line to delete
            if (emptyDirs.ContainsKey(parentDir))
            {
                emptyDirs[parentDir]--; // decrement number of subdirectories of parent
            }
            removed = true;
        }
    }

    return removed;
}

几点说明:

  1. 在没有递归的情况下爬取一棵树并不太难,我在上面链接到的 MSDN 文章中完成了它。但是这个特殊的例子比他们的要复杂一些。
  2. 我存储了emptyDirs Dictionary 中不包含文件的每个目录,以及它包含的子目录的数量。
  3. 由于删除目录的顺序很关键,我不得不以相反的顺序遍历 emptyDirs 字典(我使用 Linq 来完成此操作)。

我想有更有效的方法,但这还不错,而且它似乎在我的测试中有效。

更新: 我摆脱了颠倒的字典,转而使用第二个堆栈。不过,我仍然使用字典进行快速查找。

【讨论】:

  • 您绝对不应该依赖Dictionary 中的任何排序。在某些情况下,它会按照您插入它们的顺序返回它们,但您不应该依赖它,因为它并不总是有效。
  • @svick - 很高兴知道。我刚刚放弃了反向字典。
  • 是的,我已经开始尽量避免使用Dictionary 类。我可以通过 List&lt;KeyValuePair&lt;T1, T2&gt;&gt; 类完成很多相同的事情。
  • @palswim - 字典在搜索大型集合时速度更快。但是话又说回来,如果我使用了您提到的 List ,我将获得以相反顺序遍历它的能力,并且不需要第二个堆栈。这可能是您应该决定是优化速度还是优化内存的事情之一。
【解决方案2】:

无递归版本要困难得多,效率也不高,所以我只建议保留你的递归。另外,这里有一个更简洁的版本:

static bool DeleteDirectoriesRecursive(DirectoryInfo d)
{
    bool remove = true;

    foreach (DirectoryInfo c in d.GetDirectories())
    {
        remove &= DeleteDirectoriesRecursive(c);
    }

    if (remove && d.GetFiles().Length == 0) {
        d.Delete();
        return true;
    }

    return false;
}

【讨论】:

  • 有点干净。不过,如果需要,您仍然必须找到一个地方将删除设置为 falseremove &amp;= (d.GetFiles().Length == 0) 某处)。
猜你喜欢
  • 1970-01-01
  • 2014-05-12
  • 1970-01-01
  • 2018-10-15
  • 1970-01-01
  • 2010-10-21
相关资源
最近更新 更多