【问题标题】:Recursive Add on a List<T> containing List<T> Elements在包含 List<T> 元素的 List<T> 上递归添加
【发布时间】:2021-08-25 16:25:42
【问题描述】:

我正在尝试使用递归重构一些代码以扩展可能性(并清理代码)。

问题来了:

我有一个看起来像文件夹结构的 Treeview 对象:

- ElementA
- ElementB
   - Element B1
   - Element B2
      - Element B2-1
   - Element B3
      - Element B3-1
         - Element B3-1-1
- Element C
This is the guilty class:

public class TreeNode
{
        private readonly string name;   //Name of the node
        private readonly string path;   //Path of the node \\foo\\foo\\bar
        public List<TreeNode> children; //list of TreeNode children

        public void Add(TreeNode tName) //Add a child to a specific node
        {
            if (this.children is null)
                this.children = new List<TreeNode>();
            this.children.Add(tName);
        }
}

我必须根据从后端获取的文件夹列表创建一个 TreeNode 列表。

现在我的代码:

List<TreeNode> nList = new List<TreeNode>();   // The list I want to fill in
ICollection<CfgFolder> folders= new List<CfgFolder>();  //Collection of folder to base myself on

//here get CfgFolder list from backend
//...
//

foreach (var folder in folders)
{
    TreeNode newRow = new TreeNode(folder.Name, folder.ObjectPath); //Create a TreeNode without children (this is the new object to add)
    bool added = false;
    if (nList.Count == 0) // Add first element
        nList.Add(new TreeNode(folder.Name, folder.ObjectPath));
    else
    {   
        foreach (var child in nList.ToList())
        {
            if (child.Path == newRow.Path) // If first level add simply to the list (same initial path \\Root )
            {
                nList.Add(new TreeNode(folder.Name, folder.ObjectPath));
                added = true;
                break;
            }
        }
        if (!added)
        {
            foreach (var child in nList.ToList())
            {
                if (child.Path + "\\" + child.Name == newRow.Path) // If second level add to a child (path like \\ROOT\\Folder )
                {
                    nList.FirstOrDefault(x => x.Name == child.Name).Add(new TreeNode(folder.Name, folder.ObjectPath));
                    added = true;
                    break;
                }
            }
        }
        if (!added)
        {
            foreach (var child in nList.ToList())
            {
                foreach (var child2 in child.children)
                {
                    if (child2.Path + "\\" + child2.Name == newRow.Path) // If third level add to a child of the child (path like \\ROOT\\Folder\\SubFolder )
                    {
                        nList.FirstOrDefault(x => x.Name == child.Name).children.FirstOrDefault(x => x.Name == child2.Name).Add(new TreeNode(folder.Name, folder.ObjectPath));
                        added = true;
                        break;
                    }

                }

            }
        }
        //Also 4th level, ... till n level
    }
}

这工作正常,我可以获得想要的结构,但这并不好看,我正在努力创建返回我的 nList 正确更新的递归方法。

这看起来像这样,但是当我在每个递归调用上重写“Mylist”时,这并不好:-(

无论如何,我能够找到添加文件夹的正确位置,但无法正确更新初始 nList

private List<TreeNode> Recursive(List<TreeNode> Mylist, TreeNode newRow, bool added, int? loop = 0)
{
   if (!added)
   {
      foreach (var child in Mylist.ToList())
      {
         if (child.Path + "\\" + child.Name == newRow.Path) 
         {
            Mylist.FirstOrDefault(x => x.Name == child.Name).Add(new TreeNode(newRow.Name, newRow.ObjectPath)); //This line is false it should be something like nList[Mylist.Position]
            added = true;
            break;
          }

       }
   }
   if (added)
      return Mylist; //This line is false also
   return Recursive(Mylist[loop??0].children.ToList(), newRow, added, path,loop+1);
}

我希望我的解释是可以理解的。

【问题讨论】:

    标签: c# list recursion tree


    【解决方案1】:

    我建议使用具有通用键和通用值类型的更通用的树。这个想法是创建一个可重用的集合,如Dictionary&lt;TKey, TValue&gt;。类Tree&lt;TKey, TValue&gt;是递归结构的根,是TreeNode&lt;TKey, TValue&gt;的基类。这使得每个节点也成为一棵树,这很好,因为每个节点都可以是子树的根。

    public class Tree<TKey, TValue>
    {
        public List<TreeNode<TKey, TValue>> Children { get; } = new();
    
        public void Add(TKey[] keys, TValue value)
        {
            ...
        }
    }
    
    public class TreeNode<TKey, TValue> : Tree<TKey, TValue>
    {
        public TreeNode(TKey key)
        {
            Key = key;
        }
    
        public TKey Key { get; }
        public TValue Value { get; set; }
    }
    

    Tree&lt;,&gt; 只有一个 Children 属性。 TreeNodes&lt;,&gt; 添加了 KeyValue 属性。

    如果我们想在更深的层次上添加一个元素,我们需要所有的键指定一个通过树结构的路径。因此,Addkeys 参数是一个键数组。对于您的具体问题,关键是文件夹的名称(不带路径)。该值可以是任何值,例如完整路径或DirectoryInfo ClassFileInfo Class 或它们的共同祖先FileSystemInfo,以便能够混合目录和文件信息。

    让我们实现公共Add 方法。它调用私有递归 Add 方法,该方法具有一个额外的 index 参数,该参数引用当前递归级别使用的数组中的键。

    public void Add(TKey[] keys, TValue value)
    {
        Add(keys, value, 0);
    }
    
    private void Add(TKey[] keys, TValue value, int index)
    {
        TKey key = keys[index];
        TreeNode<TKey, TValue> child = Children.FirstOrDefault(c => Equals(c.Key, key));
        if (child is null) {
            child = new TreeNode<TKey, TValue>(key);
            Children.Add(child);
        }
        if (index == keys.Length - 1) {
            child.Value = value;
        } else {
            child.Add(keys, value, index + 1);
        }
    }
    

    让我们在Tree&lt;,&gt; 中添加一个打印方法进行测试:

    public void PrintChildren()
    {
        foreach (var child in Children) {
            child.Print("");
        }
    }
    

    它调用TreeNode&lt;,&gt;中声明的递归打印方法:

    public void Print(string indent = "")
    {
        Console.WriteLine($"{indent}- {Key} = {Value}");
        foreach (var child in Children) {
            child.Print(indent + "   ");
        }
    }
    

    我们可以测试它:

    var tree = new Tree<string, string>();
    var folders = new List<string> {
        @"Element A",
        @"Element B\Element B1",
        @"Element B\Element B2\Element B2-1",
        @"Element B\Element B3",
        @"Element B\Element B3\Element B3-1",
        @"Element B\Element B3\Element B3-1\Element B3-1-1",
        @"Element C"
    };
    
    foreach (string folder in folders) {
        string[] keys = folder.Split('\\');
        tree.Add(keys, folder);
    }
    
    tree.PrintChildren();
    

    您将看到我们仅将值插入为叶子。如果我们想在每个级别都有值,我们必须将 B2 文件夹添加为

    @"Element B",
    @"Element B\Element B2",
    @"Element B\Element B2\Element B2-1",
    

    【讨论】:

    • 你好奥利维尔!感谢您的快速回答。我已经对其进行了测试,它的工作方式很有趣 :-) 不幸的是,我无法更改当前 TreeNode 类的结构 :-(
    • 但是您可以将相同的算法应用于现有的 TreeNode 类。只需将Key 替换为name 并将Value 替换为path
    • 我重构了我的解决方案,将TreeNode 类拆分为TreeTreeNode 类。这反映了树有孩子但没有键或值的事实。
    • 我通过使用拆分路径的想法对您的解决方案进行了小幅调整,这非常有效:-) 非常感谢您的帮助!
    • 不客气。另外,由于您是 StackOverflow 的新手,我想通知您,您可以通过选中答案旁边的勾号来为好的答案投票并接受对您帮助最大的答案。在本网站上,点赞或接受的答案都算作“感谢”。
    猜你喜欢
    • 2011-09-09
    • 2018-07-20
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2020-05-23
    • 2014-04-14
    相关资源
    最近更新 更多