代码之家  ›  专栏  ›  技术社区  ›  Prolog Leodev

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

  •  0
  • Prolog Leodev  · 技术社区  · 6 年前

    介绍

    DirectoryTreeDrawer 班级。它的主要目的是绘制一个树结构(到底层 TextWriter )基于提供的目录路径或 DirectoryInfo 实例。

    演示

    下面是一个使用 班级:

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

    样本输出

    运行上述操作将产生与下面几行类似的输出(为简洁起见,将其截断):

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

    工作原理

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

    辅助方法

    文件(或目录)前缀由一个 ├ 符号后接重复 ─ 分别标记到当前深度。

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

    签字,我想从 └ 签字。所以最后一行输出代替:

    ├──Program.cs
    

    └──Program.cs
    

    对我来说,问题是如何知道哪个文件或目录是最后打印的。如果我知道的话,我可以在打印前缀的同时进行检查。

    有没有比将所有条目(文件和目录的名称和深度)保存到一个集合中,然后针对“成为最后一个条目”条件执行检查,还有更好的解决方案吗?或者可能是唯一的一个?

    代码库

    GitLab . 在这里你也可以找到原件 DirectoryTreeDrawer 班级。请注意,为了代码简洁,我对它进行了大量的编辑。

    最后通知

    0 回复  |  直到 6 年前
        1
  •  1
  •   Rufus L    6 年前

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

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

    我还把它改成了 static 通过 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

        2
  •  0
  •   Senad MeÅ¡kin    6 年前

    我要做的是,列出它们,然后更改光标位置,然后更改打印字符:

    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

    更新: 列出,然后更改最后一个的前缀:

    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或其他任何东西。

        3
  •  -1
  •   Peter Duniho    6 年前

    给定您发布的代码,知道您是否在最后一个条目并不重要,因为递归方法有一个 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)}";
    }
    

    一、 e.表达 depth == 0 && fileIndex == filesCount - 1 作为 isLast 我添加到 PrintFileName() 方法。

    注意缺少一个好的 Minimal, Complete, and Verifiable example ,我必须对您发布的代码进行一些调整以使其编译和运行。您发布的代码也不会产生您的问题所说的输出;具体地说,顶级目录名的结果是 '├'

    我没有花任何时间试图让输出与您所说的完全匹配,而是更愿意专注于手头的问题。我假设您可以将上面包含问题答案的代码改编为您实际拥有的任何代码。