【问题标题】:Loading flat data into a tree data structure将平面数据加载到树数据结构中
【发布时间】:2018-02-22 15:18:38
【问题描述】:

目前,我有一个 Item 类和一个表示从 SQL Server 2012 DB 查询中检索到的平面数据的项目列表。这是 Item 类。

public class Item
{
    public string Parent { get; set; }
    public string Child { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
}

我需要将这些平面数据转换为树结构,并且能够在执行某些操作时遍历树。这是一些数据的视觉效果。我的树总是有一个根节点。

我在 SO 上看到了几篇与此相关的帖子,但我仍在为此苦苦挣扎。这是我拥有的通用 TreeNode 类。

public class TreeNode<T>
{
    private readonly T _value;
    private readonly List<TreeNode<T>> _children = new List<TreeNode<T>>();

    public TreeNode(T value)
    {
        _value = value;
    }

    public TreeNode<T> this[int i]
    {
        get { return _children[i]; }
    }

    public TreeNode<T> Parent { get; private set; }

    public T Value { get { return _value; } }

    public ReadOnlyCollection<TreeNode<T>> Children
    {
        get { return _children.AsReadOnly(); }
    }

    public TreeNode<T> AddChild(T value)
    {
        var node = new TreeNode<T>(value) { Parent = this };
        _children.Add(node);
        return node;
    }

    public TreeNode<T>[] AddChildren(params T[] values)
    {
        return values.Select(AddChild).ToArray();
    }

    public bool RemoveChild(TreeNode<T> node)
    {
        return _children.Remove(node);
    }

    public void Traverse(Action<T> action)
    {
        action(Value);
        foreach (var child in _children)
            child.Traverse(action);
    }

    public IEnumerable<T> Flatten()
    {
        return new[] { Value }.Concat(_children.SelectMany(x => x.Flatten()));
    }
}

我无法将平面数据递归加载到树结构中。我也不明白如何利用 Traverse 方法中的 Action 在遍历时执行某些任务。要填充树,我相信我需要从根开始。我是这样获得的:

var root = new TreeNode<Item>(items.Single(i => i.Parent == null));

然后我可以像这样加载第一级:

root.AddChildren(items.Where(i => i.Parent.Equals(root.Value.Child)).ToArray());

我的两个问题如下:

  1. 如何递归加载所有内容?
  2. 如何使用 Traverse 方法中的 Action 来简单地按项目名称和适当的缩进写出树结构(或执行任何任务)?

提前致谢!!

【问题讨论】:

  • 每个帖子一个问题,拜托。如果有人答对了第一个问题,第二个答错了,另一个人答对了第一个问题,但第二个答对了,你会如何选择接受哪个答案?
  • 我会将您的类的名称从 Item 更改为 Node,这是通常使用的。您的节点定义了错误的子节点: public string Child { get;放; }。它应该是 public List Child { get;放; } 或者在二叉树的情况下有 public Node Left { get;放; } 和公共节点右 {get;放; }
  • @jdweng - 不,Item 实际上代表我们 SQL 数据库的物料清单表中的一部分,并且该类不能更改。有一个递归 CTE,它会吐出代表 BOM 的平面项目列表。我需要将项目的平面列表转换为 TreeNodes 的 IEnumberable。列表中的平面项没有子项的集合。我正在尝试将平面项目映射到具有子节点(和父节点)的节点。谢谢。

标签: c# recursion tree parent-child treenode


【解决方案1】:

第一个问题:递归加载

您需要创建一个辅助函数(如果您在 C# 7.0 中编写代码,您可以将其作为本地函数并去除 items 参数):

private static void AddDescendants(IReadOnlyCollection<Item> items, TreeNode<Item> node)
{
    var children = node.AddChildren(items.Where(i => i.Parent == node.Value.Child).ToArray());
    foreach (var child in children)
    {
        AddDescendants(items, child);
    }
}

获取root后按照你的例子调用它:

var root = new TreeNode<Item>(items.Single(i => i.Parent == null));
AddDescendants(items, root);

第二个问题:遍历

您的 Traverse 函数执行 pre-order traversal 并且绝对不提供有关您处于哪个树级别的任何信息,因此不能用于在输出中执行缩进。

如果您将实现更改为采用Action&lt;TreeNode&lt;T&gt;&gt;,如下所示:

public void Traverse(Action<TreeNode<T>> action)
{
    action(this);
    foreach (var child in _children)
        child.Traverse(action);
}

然后你可以通过计算父母来计算缩进级别:

root.Traverse(node =>
{
    var depth = 0;
    var ancestor = node.Parent;
    while (ancestor != null)
    {
        depth++;
        ancestor = ancestor.Parent;
    }
    Console.WriteLine(new string(' ', depth * 2) + node.Value.Name);
});

这是带有输出的完整工作示例:https://ideone.com/faVOtd

【讨论】:

  • 谢谢!我非常感谢您的努力和完整的工作示例。
  • 如果我在 TreeNode 上添加属性 IsRoot,我如何在操作中访问它?
  • 如果您使用原始的 Traverse 功能,您将无法使用。在我建议的实现中,参数是TreeNode,所以[你可以直接从参数访问IsRoot]。
【解决方案2】:

这是通常在课堂上的做法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication23
{
    class Program
    {
        static void Main(string[] args)
        {
            DataTable dt = new DataTable("_InvTrans");
            dt.Columns.Add("Parent", typeof(string));
            dt.Columns.Add("Child", typeof(string));
            dt.Columns.Add("Name", typeof(string));

            dt.Rows.Add(new object[] { "Null", "A", "Alpha" });
            dt.Rows.Add(new object[] { "A", "B", "Bravo" });
            dt.Rows.Add(new object[] { "A", "C", "Charlie" });
            dt.Rows.Add(new object[] { "B", "E", "Echo" });
            dt.Rows.Add(new object[] { "B", "F", "Foxtrot" });
            dt.Rows.Add(new object[] { "C", "W", "Whiskey" });
            dt.Rows.Add(new object[] { "C", "H", "Hotel" });

            new Node(dt);

        }

    }
    public class Node
    {
        public static Node root = new Node();
        static DataTable dt = null;

        public List<Node> Child { get; set; }
        public string Name { get; set; }
        public string id { get; set; }

        public Node(){}
        public Node(DataTable dt)
        {
            Node.dt = dt;
            root.id = "Null";
            Add(root);
        }

        public static void Add(Node parent)
        {
            foreach (DataRow row in Node.dt.AsEnumerable().Where(x => x.Field<string>("Parent") == parent.id))
            {
                Node child = new Node();
                if (parent.Child == null) parent.Child = new List<Node>();
                parent.Child.Add(child);
                child.Name = row.Field<string>("Name");
                child.id = row.Field<string>("Child");
                Add(child);
            }
        }
    }

}

【讨论】:

    猜你喜欢
    • 2015-11-08
    • 1970-01-01
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多