Jake Petroules Jake Petroules - 4 months ago 10
C# Question

How can I convert a list of filenames to a tree structure?

I have a string array of some file paths:

path/to/folder/file.xxx
path/to/other/
path/to/file/file.xx
path/file.x
path/


How can I convert this list to a tree structure? So far I have the following:

/// <summary>
/// Enumerates types of filesystem nodes.
/// </summary>
public enum FilesystemNodeType
{
/// <summary>
/// Indicates that the node is a file.
/// </summary>
File,

/// <summary>
/// Indicates that the node is a folder.
/// </summary>
Folder
}

/// <summary>
/// Represents a file or folder node.
/// </summary>
public class FilesystemNode
{
private readonly ICollection<FilesystemNode> _children;

/// <summary>
/// Initializes a new instance of the <see cref="FilesystemNode"/> class.
/// </summary>
public FilesystemNode()
{
_children = new LinkedList<FilesystemNode>();
}

/// <summary>
/// Gets or sets the name of the file or folder.
/// </summary>
public string Name { get; set; }

/// <summary>
/// Gets or sets the full path to the file or folder from the root.
/// </summary>
public string Path { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the node is a file or folder.
/// </summary>
public FilesystemNodeType Type { get; set; }

/// <summary>
/// Gets a list of child nodes of this node. The node type must be a folder to have children.
/// </summary>
public ICollection<FilesystemNode> Children
{
get
{
if (Type == FilesystemNodeType.Folder)
return _children;

throw new InvalidOperationException("File nodes cannot have children");
}
}
}


I'm just a bit at a loss at how to actually split up the paths and all. Any path that ends with a / is a directory, any one that doesn't, is not.

Also, while my input will always contain a path to the folder, how would I account for that situation if it did not?

For example, if I had the input:

path/to/file.c
path/file.c
path/


How would I account for the fact that
path/to/
is not in the input?

Answer

Here is a solution that generates a nested dictionary of NodeEntry items (you can substitute your file info class as needed):

public class NodeEntry
{
    public NodeEntry()
    {
        this.Children = new NodeEntryCollection();
    }

    public string Key { get; set; }
    public NodeEntryCollection Children { get; set; }

}

public class NodeEntryCollection : Dictionary<string, NodeEntry>
{
    public void AddEntry(string sEntry, int wBegIndex)
    {
        if (wBegIndex < sEntry.Length)
        {
            string sKey;
            int wEndIndex;

            wEndIndex = sEntry.IndexOf("/", wBegIndex);
            if (wEndIndex == -1)
            {
                wEndIndex = sEntry.Length;
            }
            sKey = sEntry.Substring(wBegIndex, wEndIndex - wBegIndex);
            if (!string.IsNullOrEmpty(sKey)) {
                NodeEntry oItem;

                if (this.ContainsKey(sKey)) {
                    oItem = this[sKey];
                } else {
                    oItem = new NodeEntry();
                    oItem.Key = sKey;
                    this.Add(sKey, oItem);
                }
                // Now add the rest to the new item's children
                oItem.Children.AddEntry(sEntry, wEndIndex + 1);
            }
        }
    }
}

To use the above, create a new collection:

        NodeEntryCollection cItems = new NodeEntryCollection();

then, for each line in your list:

        cItems.AddEntry(sLine, 0);