Search code examples
c#algorithmtreeview

Build folder/files tree from string path with c#


I need to solve a problem that involves building tree from stirng paths, here are the models:

public class Folder
{
    public string Name { get; set; }
    
    public List<Folder> Folders { get; set; } = new List<Folder>();
    public List<File> Files { get; set; } = new List<File>();
}

public class File 
{
    public string Name { get; set; }
}

And here are the string paths:

var cars = new List<string>()
{
    "Car/",
    "Car/BMW/",
    "Car/Great Wall/",
    "Car/Great Wall/Another/",
    "Car/Great Wall/Another/test - file.bak",
    "Car/Great Wall/Another/second - file.bak",
    "Car/Great Wall/Car/",
    "Car/Great Wall/local - copy.bak",
    "Car/Great Wall/local - copy(2).bak",
    "Car/Mercedes/process - file.bak",
    "Car/Mercedes/process - file(2).bak",
    "Car/test123 - file.bak"
};

As a result I should get a List of Folders and Files with tree structure and when displaying it has to be something like this:

-Car
  -BMW
  -Great Wall
    -Another
      -test - file.bak
      -second - file.bak
    -Car
  -local - copy.bak
  -local - copy(2).bak
  -Mercedes
    -process - file.bak
    -process - file(2).bak
  -test123 - file.bak

Any help is appreciated a lot :)


Solution

  • Here you have the code:

    using System;
    using System.Collections.Generic;
    
    namespace FolderTree
    {
        public class Folder
        {
            public string Name { get; set; }
            public List<Folder> Folders { get; set; } = new List<Folder>();
            public List<File> Files { get; set; } = new List<File>();
        }
    
        public class File
        {
            public string Name { get; set; }
        }
        class Program
        {
            static void Main(string[] args)
            {
                var cars = new List<string>()
                {
                    "Car/",
                    "Car/BMW/",
                    "Car/Great Wall/",
                    "Car/Great Wall/Another/",
                    "Car/Great Wall/Another/test - file.bak",
                    "Car/Great Wall/Another/second - file.bak",
                    "Car/Great Wall/Car/",
                    "Car/Great Wall/local - copy.bak",
                    "Car/Great Wall/local - copy(2).bak",
                    "Car/Mercedes/process - file.bak",
                    "Car/Mercedes/process - file(2).bak",
                    "Car/test123 - file.bak"
                };
    
                var folders = GetFoldersFormStrings(cars);
                ShowFolders(folders);
            }
    
            static List<Folder> GetFoldersFormStrings(List<string> strings)
            {
                var folders = new List<Folder>();
                strings.Sort(StringComparer.InvariantCultureIgnoreCase);
                var folderByPath = new Dictionary<string, Folder>();
                foreach (var str in strings)
                {
                    if (str.EndsWith("/")) // we have a folder
                    {
                        EnsureFolder(folders, folderByPath, str);
                    }
                    else // we have a file
                    {
                        var lastSlashPosition = str.LastIndexOf("/");
                        var parentFolderPath = str.Substring(0, lastSlashPosition + 1);
                        var parentFolder = EnsureFolder(folders, folderByPath, parentFolderPath);
                        var fileName = str.Substring(lastSlashPosition + 1);
                        var file = new File
                        {
                            Name = fileName
                        };
                        parentFolder.Files.Add(file);
                    }
                }
                return folders;
            }
    
            private static Folder EnsureFolder(List<Folder> rootFolders, Dictionary<string, Folder> folderByPath, string folderPath)
            {
                if (!folderByPath.TryGetValue(folderPath, out var folder))
                {
                    var folderPathWithoutEndSlash = folderPath.TrimEnd('/');
                    var lastSlashPosition = folderPathWithoutEndSlash.LastIndexOf("/");
                    List<Folder> folders;
                    string folderName;
                    if (lastSlashPosition < 0) // it's a first level folder
                    {
                        folderName = folderPathWithoutEndSlash;
                        folders = rootFolders;
                    }
                    else
                    {
                        var parentFolderPath = folderPath.Substring(0, lastSlashPosition + 1);
                        folders = folderByPath[parentFolderPath].Folders;
                        folderName = folderPathWithoutEndSlash.Substring(lastSlashPosition + 1);
                    }
                    folder = new Folder
                    {
                        Name = folderName
                    };
                    folders.Add(folder);
                    folderByPath.Add(folderPath, folder);
                }
                return folder;
            }
    
            private static void ShowFolders(List<Folder> folders)
            {
                foreach (var folder in folders)
                {
                    ShowFolder(folder, 0);
                }
            }
    
            private static void ShowFolder(Folder folder, int indentation)
            {
                string folderIndentation = new string(' ', indentation);
                string fileIndentation = folderIndentation + "  ";
                Console.WriteLine($"{folderIndentation}-{folder.Name}");
                foreach (var file in folder.Files)
                {
                    Console.WriteLine($"{fileIndentation}-{file.Name}");
                }
                foreach (var subfolder in folder.Folders)
                {
                    ShowFolder(subfolder, indentation + 2);
                }
            }
        }
    }
    

    I'm sorting the strings to prevent trying to create a subfolder before the parent folder, and to prevent to add a file before the containing folder is created.

    for each string, if it ends with slash then a create a folder, else I create the file. I maintain a dictionary of folders by path to get the parent folder when needed, so I can add a folder or a file to the containing folder.

    To show the folder structure I use a recursive algorithm that increments the indentation as you go deep into the tree.

    The rest is just string manipulation.

    This is the output of the program:

    -Car
      -test123 - file.bak
      -BMW
      -Great Wall
        -local - copy(2).bak
        -local - copy.bak
        -Another
          -second - file.bak
          -test - file.bak
        -Car
      -Mercedes
        -process - file(2).bak
        -process - file.bak