递归列出时如何知道哪个文件或目录是最后一个?

问题描述 投票:1回答:3

Introduction

我正在写一个DirectoryTreeDrawer类附带的库。它的主要目的是根据提供的目录路径或TextWriter实例绘制树结构(到底层的DirectoryInfo)。

Demo

以下是使用DirectoryTreeDrawer类的.NET Core控制台应用程序示例:

public class Program
{
    public static void Main()
    {
        using (var drawer = new DirectoryTreeDrawer(System.Console.Out))
        {
            var workingDirectoryPath = System.IO.Directory.GetCurrentDirectory();
            drawer.DrawTree(workingDirectoryPath);
        }
    }
}

Sample output

运行上面的命令会产生类似于下面几行的输出(为简洁而截断):

ConsoleDemo\
├────ConsoleDemo.csproj.nuget.cache
├────ConsoleDemo.csproj.nuget.g.props
├────ConsoleDemo.csproj.nuget.g.targets
├────project.assets.json
├──ConsoleDemo.csproj
├──Program.cs

How it works

DrawTree()PrintDirectoryContent()称为递归魔法开始的地方。从提供的路径开始,程序以递归方式遍历子目录,并以反映原始目录的相对深度的方式打印文件和目录的名称。

public void DrawTree(DirectoryInfo directoryInfo)
{
    var searchPattern = "*";
    var searchOption = SearchOption.TopDirectoryOnly;

    PrintDirectoryName(directoryInfo, depth: 0);
    PrintDirectoryContent(directoryInfo, searchPattern, searchOption, depth: 0);
}

private void PrintDirectoryContent(DirectoryInfo currentDirectory, string searchPattern, SearchOption searchOption, int depth)
{
    var directories = currentDirectory.GetDirectories(searchPattern, searchOption);
    var directoriesCount = directories.GetLength(0);
    for (var directoryIndex = 0; directoryIndex < directoriesCount; directoryIndex++)
    {
        var directoryInfo = directories[directoryIndex];
        PrintDirectoryName(directoryInfo, depth + 1);
        PrintDirectoryContent(directoryInfo, searchPattern, searchOption, depth + 1);
    }

    var files = currentDirectory.GetFiles(searchPattern, searchOption);
    var filesCount = files.GetLength(0);
    for (var fileIndex = 0; fileIndex < filesCount; fileIndex++)
    {
        var fileInfo = files[fileIndex];
        PrintFileName(fileInfo, depth + 1);
    }
}

Helper methods

文件(或目录)前缀由单个符号组成,后面分别重复符号到当前深度。

private void PrintDirectoryName(DirectoryInfo directoryInfo, int depth)
{
    _textWriter.WriteLine($"{CreateDepthPrefix(depth)}{directoryInfo.Name}{Path.DirectorySeparatorChar}");
}

private void PrintFileName(FileInfo fileInfo, int depth)
{
    _textWriter.WriteLine($"{CreateDepthPrefix(depth)}{fileInfo.Name}");
}

private string CreateDepthPrefix(int depth)
{
    return $"{'├'}{new string('─', 2 * depth)}";
}

Question

我想标记最后一个条目(文件或目录)的前缀不同于通常的前缀。而不是用标志开始前缀,我想用标志开始它。所以最后一行输出而不是:

├──Program.cs

......看起来像这样:

└──Program.cs

对我来说,问题是如何知道哪个文件或目录是最后一个要打印的文件或目录。如果我能够知道它,我可以在打印前缀时简单地运行检查。

有没有更好的解决方案,然后将所有条目(文件和目录名称和深度)保存到集合,然后执行检查“最后进入”条件?或者也许它是唯一的一个?

Code repository

该库是开源的,可在GitLab上找到。在这里您还可以找到原始的DirectoryTreeDrawer类。请注意,为了简洁起见,我对它进行了大量编辑。

Final notice

我想说清楚,我不是要求代码审查,就像某人看起来一样。我正面临一个问题,我正在寻求解决方案。

c# recursion io .net-standard
3个回答
1
投票

我的简短回答是我在评论中写的,彼得已经提供了答案,但是这里有一种可以被认为更具可读性的替代格式,它为每个目录和文件提供缩进,这样就可以更容易地看到哪个父级它属于。有关示例输出,请参见最后一张图

这是通过跟踪父项中的最后一个文件或文件夹并将其传递给PrintItem方法(这是我在您的问题的评论中提出的答案)来完成的。另一个变化是前缀从父传递到子传递,因此我们可以包含嵌套项的连接器。要跟踪“嵌套”的项目(意味着项目的父项具有显示在当前项目之后的兄弟项目),我们将IsNested参数传递给PrintDirectory方法,以便可以相应地更新前缀。

我还将它修改为static类,它将TextWriter传递给不同的方法。不确定这是否真的更好,但除了TextWriter之外,其他一切看起来都应该是静态的。

public static class DirectoryTreeDrawer
{
    public static void DrawTree(string directoryPath, TextWriter textWriter)
    {
        DrawTree(new DirectoryInfo(directoryPath), textWriter);
    }

    public static void DrawTree(DirectoryInfo directoryInfo, TextWriter textWriter)
    {
        PrintDirectory(directoryInfo, textWriter);
    }

    private static void PrintDirectory(DirectoryInfo directory, TextWriter textWriter, 
        string prefix = "  ", string searchPattern = "*", SearchOption searchOption = 
        SearchOption.TopDirectoryOnly, bool isLast = true, bool isNested = false)
    {
        PrintItem(directory.Name, prefix, isLast, textWriter, true);

        var subDirs = directory.GetDirectories(searchPattern, searchOption);
        var files = directory.GetFiles(searchPattern, searchOption);

        // If this is a "nested" directory, add the parent's connector to the prefix
        prefix += isNested ? "│ " : "  ";

        for (var directoryIndex = 0; directoryIndex < subDirs.Length; directoryIndex++)
        {
            var isLastChild = directoryIndex == subDirs.Length - 1 && files.Length == 0;

            // If the parent has files or other directories, mark this as "nested"
            var isNestedDir = files.Length > 0 || !isLastChild;

            PrintDirectory(subDirs[directoryIndex], textWriter, prefix, searchPattern, 
                searchOption, isLastChild, isNestedDir);
        }            

        for (var fileIndex = 0; fileIndex < files.Length; fileIndex++)
        {
            var isLastFile = fileIndex == files.Length - 1;

            PrintItem(files[fileIndex].Name, prefix, isLastFile, textWriter);
        }
    }

    private static void PrintItem(string name, string prefix, bool isLastItem, 
        TextWriter textWriter, bool isDirectory = false)
    {
        var itemConnector = isLastItem ? "└─" : "├─";
        var suffix = isDirectory ? Path.DirectorySeparatorChar.ToString() : "";

        textWriter?.WriteLine($"{prefix}{itemConnector}{name}{suffix}");
    }
}

用法

private static void Main()
{
    DirectoryTreeDrawer.DrawTree(Environment.CurrentDirectory, Console.Out);

    GetKeyFromUser("\nDone! Press any key to exit...");
}

产量

Sample Output

而且,从输出结果来看,很明显我多年来一直在重复使用同一个项目,它在Debug文件夹中有一堆无关的文件...... :)


0
投票

我会做什么,我会列出所有,然后更改光标位置,然后更改打印字符:

public void DrawTree(string directoryPath)
        {
            if (string.IsNullOrWhiteSpace(directoryPath))
            {
                throw new ArgumentException(
                    "Provided directory path is null, emtpy " +
                    "or consists of only whitespace characters.",
                    nameof(directoryPath));
            }

            DrawTree(new DirectoryInfo(directoryPath));
            //remember current position because we need to return position to it
            int currentCursorPositionTop = Console.CursorTop;
            //set position to the last row
            Console.SetCursorPosition(0, Console.CursorTop-1);
            //change the first charachter
            Console.Write("└");
            //return cursor position to the previous one so our "Press any key to continue" can apear below our list.
            Console.SetCursorPosition(0, currentCursorPositionTop);
        }

我希望这有帮助。这就是应用更改时的样子:enter image description here

更新:为了保持更高级别的抽象,我建议将数据写入List,然后只需更改最后一个的前缀:

class ContentItem {
    public string Prefix {get;set; }
    public int Depth {get;set; }
    public string Name {get;set; }
    public override string ToString() {
        return $"{Prefix}{(new String("-", Depth))} {Name}";
    }
}

因此,改变控制台光标位置,您可以更改列表项前缀:

 var items[items.Count()-1].Prefix = "└";

然后你循环遍历项目并将它们传递给TextWriter,StreamWriter或其他任何东西。


-1
投票

问题是如何知道哪个文件或目录是最后一个要打印的文件或目录。如果我能够知道它,我可以在打印前缀时简单地运行检查。

鉴于你发布的代码,知道你是否在最后一个条目是微不足道的,因为你的递归方法有一个depth参数,并且每个项目的循环都被编入索引。这意味着你只需要查看你的int值,例如:

private void PrintDirectoryContent(DirectoryInfo currentDirectory, string searchPattern, SearchOption searchOption, int depth)
{
    var directories = currentDirectory.GetDirectories(searchPattern, searchOption);
    var directoriesCount = directories.GetLength(0);
    for (var directoryIndex = 0; directoryIndex < directoriesCount; directoryIndex++)
    {
        var directoryInfo = directories[directoryIndex];
        PrintDirectoryName(directoryInfo, depth + 1);
        PrintDirectoryContent(directoryInfo, searchPattern, searchOption, depth + 1);
    }

    var files = currentDirectory.GetFiles(searchPattern, searchOption);
    var filesCount = files.GetLength(0);
    for (var fileIndex = 0; fileIndex < filesCount; fileIndex++)
    {
        var fileInfo = files[fileIndex];
        PrintFileName(fileInfo, depth + 1, depth == 0 && fileIndex == filesCount - 1);
    }
}

private void PrintDirectoryName(DirectoryInfo directoryInfo, int depth)
{
    _textWriter.WriteLine($"{CreateDepthPrefix('├', depth)}{directoryInfo.Name}{Path.DirectorySeparatorChar}");
}

private void PrintFileName(FileInfo fileInfo, int depth, bool isLast)
{
    _textWriter.WriteLine($"{CreateDepthPrefix(isLast ? '└' : '├', depth)}{fileInfo.Name}");
}

private string CreateDepthPrefix(char initialChar, int depth)
{
    return $"{initialChar}{new string('─', 2 * depth)}";
}

即表达式depth == 0 && fileIndex == filesCount - 1用作我添加到isLast方法的PrintFileName()参数的精确值。

请注意,缺少一个好的Minimal, Complete, and Verifiable example,我不得不对你发布的代码进行一些调整,以便编译和运行它。您发布的代码也不会产生您的问题所说的输出;具体来说,顶级目录名称也与'├'字符结束。我想在你的实际代码中,你就是特殊情况。

我没有花时间试图让输出与你说的完全匹配,而是只关注手头的问题。我假设您可以根据实际拥有的代码调整上面的代码,其中包含该问题的答案。

© www.soinside.com 2019 - 2024. All rights reserved.