【问题标题】:How can I convert a list of filenames to a tree structure?如何将文件名列表转换为树结构?
【发布时间】:2011-12-23 23:40:07
【问题描述】:

我有一些文件路径的字符串数组:

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

如何将此列表转换为树结构?到目前为止,我有以下内容:

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

我对如何实际分割路径和所有内容有点茫然。任何以 / 结尾的路径都是目录,任何不以 / 结尾的路径都不是。

另外,虽然我的输入将始终包含文件夹的路径,但如果没有,我将如何解释这种情况?

例如,如果我有输入:

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

我如何解释path/to/ 不在输入中?

【问题讨论】:

    标签: c# tree


    【解决方案1】:

    这是一个生成 NodeEntry 项的嵌套字典的解决方案(您可以根据需要替换文件信息类):

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

    要使用上述内容,请创建一个新集合:

            NodeEntryCollection cItems = new NodeEntryCollection();
    

    然后,对于列表中的每一行:

            cItems.AddEntry(sLine, 0);
    

    【讨论】:

    • 如何从子节点中选择子节点,比如如何在directory1/subdirectory/files中从subdirectory获取子节点?
    【解决方案2】:

    我受到了 Competitive_tech 的回答的启发,并将 Dictionary&lt;string, NodeEntry&gt; 替换为“简单”ObservableCollection&lt;NodeEntry&gt;,因为“密钥”信息将在此字典中存储两次:一次作为字典的键,一次作为公共属性NodeEntry 类。

    所以我基于“competent_tech”代码的示例如下所示:

    public class NodeEntryObservableCollection : ObservableCollection<NodeEntry>
    {
        public const string DefaultSeparator = "/";
    
        public NodeEntryObservableCollection(string separator = DefaultSeparator)
        {
            Separator = separator; // default separator
        }
    
        /// <summary>
        /// Gets or sets the separator used to split the hierarchy.
        /// </summary>
        /// <value>
        /// The separator.
        /// </value>
        public string Separator { get; set; }
    
        public void AddEntry(string entry)
        {
            AddEntry(entry, 0);
        }
    
        /// <summary>
        /// Parses and adds the entry to the hierarchy, creating any parent entries as required.
        /// </summary>
        /// <param name="entry">The entry.</param>
        /// <param name="startIndex">The start index.</param>
        public void AddEntry(string entry, int startIndex)
        {
            if (startIndex >= entry.Length)
            {
                return;
            }
    
            var endIndex = entry.IndexOf(Separator, startIndex);
            if (endIndex == -1)
            {
                endIndex = entry.Length;
            }
            var key = entry.Substring(startIndex, endIndex - startIndex);
            if (string.IsNullOrEmpty(key))
            {
                return;
            }
    
            NodeEntry item;
            item = this.FirstOrDefault(n => n.Key == key);
            if (item == null)
            {
                item = new NodeEntry(Separator) { Key = key };
                Add(item);
            }
            // Now add the rest to the new item's children
            item.Children.AddEntry(entry, endIndex + 1);
        }
    }
    
    public class NodeEntry
    {
        public string Key { get; set; }
    
        public NodeEntryObservableCollection Children { get; set; }
    
        public NodeEntry(string separator = NodeEntryObservableCollection.DefaultSeparator)
        {
            Children = new NodeEntryObservableCollection(separator);
        }
    }
    

    这有助于我将数据绑定到 TreeView 中,如下所示:

    <TreeView Name="trvMyTreeView">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type local:NodeEntry}" ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Key}"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    

    后面有这样的示例代码:

    IList<string> pathes = new List<string>
    {
        "localhost",
        "remotehost.levelD.levelDB",
        "localhost.level1.level11",
        "localhost.level1",
        "remotehost.levelD.levelDA",
        "localhost.level2.level22",
        "remotehost.levelA",
        "remotehost",
        "remotehost.levelB",
        "remotehost.levelD",
        "localhost.level2",
        "remotehost.levelC"
    };
    SortedSet<string> sortedPathes = new SortedSet<string>(pathes);
    
    var obsCollection = new NodeEntryObservableCollection(".");
    foreach (var p in sortedPathes) { obsCollection.AddEntry(p); }
    
    trvMyTreeView.ItemsSource = obsCollection;
    

    【讨论】:

      【解决方案3】:

      '/' 字符分割每一行。如果字符串数组的长度为 5,那么前四项应该是目录,您必须测试最后一项是否有扩展名:

      string.IsNullOrEmpty(new FileInfo("test").Extension)
      

      如果像你的情况一样,即使最后一个目录也总是有一个'/',那么拆分字符串数组的最后一项是空的。

      剩下的就是遍历你的树。解析项目时,检查第一个目录是否存在于根节点的Children 属性中。如果它不存在,则添加它,如果存在,则使用此并更进一步。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-07
        • 1970-01-01
        • 1970-01-01
        • 2022-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-05
        相关资源
        最近更新 更多