无限级类别的类

首先是一个简单的接口,ITreeNode,它表示一个树的节点。取这个名字是因为类别就是“树”。还有一点约定,见代码注释

[code=C]
    /// <summary>
    /// 定义树结点
    /// </summary>
    public interface ITreeNode
    {
        /// <summary>
        /// Id 应大于 0
        /// </summary>
        int Id { get; }

        /// <summary>
        /// 如果 ParentId 为 0,表示没有父节点
        /// </summary>
        int ParentId { get; }

        /// <summary>
        /// 节点的顺序,越小越靠前
        /// </summary>
        int Ordering { get; }
    }

[/code]

然后是一个 Forest 类,封装一些常用操作。用它来帮助我们处理树形结构,

[code=C]


    /// <summary>
    /// 表示一个森林结构。
    /// </summary>
    public class Forest <T> : IEnumerable <T> where T : ITreeNode
    {
        IEnumerable <T> _allNodes;

        public bool IsRoot(T node)
        {
            return node.ParentId == 0;
        }

        /// <summary>
        /// 将 nodes 组织成森林结构。如果一个结点的 ParentId 等于 0,则这个结点是根结点。
        /// </summary>
        public Forest(IEnumerable <T> nodes)
        {
            // _allNodes 中的节点是对树进行前序变量的顺序
           
            _allNodes = nodes;


            // ToList 是必须的,否则堆栈溢出
            IEnumerable <T> temp = _allNodes.OrderBy(x => GetPathString(x)).ToList();
            _allNodes = temp;
        }


        public T GetNode(int id)
        {
            return _allNodes.SingleOrDefault(x => x.Id == id);
        }

        public T GetParent(T node)
        {
            return _allNodes.SingleOrDefault(x => x.Id == node.ParentId);
        }


        /// <summary>
        /// 获取 node 的子结点
        /// </summary>
        /// <param name="node"> </param>
        /// <returns> </returns>
        public IEnumerable <T> GetChildNodes(T node)
        {
            return _allNodes
                .Where(x => x.ParentId == node.Id)
                .OrderBy(x => x.Ordering);
        }


        /// <summary>
        /// 获取森林中的所有根结点
        /// </summary>
        /// <returns> </returns>
        public IEnumerable <T> GetRootNodes()
        {
            return _allNodes
                .Where(x => IsRoot(x))
                .OrderBy(x => x.Ordering);
        }


        /// <summary>
        /// 获取结点在树中的路径
        /// </summary>
        /// <returns> </returns>
        public IEnumerable <T> GetPath(T node)
        {
            List <T> path = new List <T>();

            while (node != null)
            {
                if (path.Contains(node))
                {
                    string msg = string.Format("构造结点的路径时,发现结点两次出现在路径中,这说明有两个结点互为父级。结点 Id:{0}", node.Id);
                    throw new Exception(msg);
                }
                path.Add(node);
                node = GetParent(node);
            }
            path.Reverse();
            return path;
        }


        /// <summary>
        /// 帮助排序
        /// </summary>
        /// <param name="node"> </param>
        /// <returns> </returns>
        private string GetPathString(T node)
        {
            IEnumerable <T> path = GetPath(node);
            StringBuilder sb = new StringBuilder();
            foreach (T item in path)
            {
                T parent = this.GetParent(item);
                int parentId = parent == null ? 0 : parent.Id;
                sb.AppendFormat("{0:000000}:{1:000000}:{2:000000}&", parentId, item.Ordering,  item.Id);
            }
            return sb.ToString();
        }

        /// <summary>
        /// 判断 n1 是否是 n2 的后代
        /// </summary>
        /// <param name="n1"> </param>
        /// <param name="n2"> </param>
        /// <returns> </returns>
        public bool IsDescendant(T n1, T n2)
        {
            IEnumerable <T> path = GetPath(n1);
            return path.Contains(n2);
        }


        /// <summary>
        /// 获取以参数node为根的树中的所有结点
        /// </summary>
        /// <param name="node"> </param>
        /// <returns> </returns>
        public IEnumerable <T> GetNodesInTree(T node)
        {
            List <T> result = new List <T>();


            //  对子树做层次遍历
            Queue <T> queue = new Queue <T>();
            queue.Enqueue(node);
            while (queue.Count > 0)
            {
                T head = queue.Dequeue();
                result.Add(head);

                //  子结点入队
                foreach (T child in GetChildNodes(head))
                {
                    queue.Enqueue(child);
                }
            }
            return result;
        }


        #region IEnumerable <T> 成员

        public IEnumerator <T> GetEnumerator()
        {
            return _allNodes.GetEnumerator();
        }

        #endregion

        #region IEnumerable 成员

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _allNodes.GetEnumerator();
        }

        #endregion


        /// <summary>
        /// 判断在 node 是否有下一个兄弟结点
        /// </summary>
        /// <param name="node"> </param>
        /// <returns> </returns>
        private bool HasNextSibling(T node)
        {
            T parent = GetParent(node);
            if (parent == null)
            {
                return GetRootNodes().Last().Equals(node) == false;
            }

            return GetChildNodes(parent).Last().Equals(node) == false;
        }

        /// <summary>
        /// 为每个结点的 treeLines 属性赋值。结点必须已
        /// </summary>
        /// <param name="categoryTable"> </param>
        public string GetTreeLines(T node)
        {

            //  取得结点的路径
            IEnumerable <T> path = GetPath(node);

            // 构造树形线
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < path.Count(); i++)
            {
                T nodeInPath = path.ElementAt(i);

                if (path.Count() - 1 == i)  //  路径上的最后一个结点要特殊处理
                {
                    if (HasNextSibling(nodeInPath))  //  有下一个兄弟
                    {
                        sb.Append("├");
                    }
                    else  //  没有下一个兄弟
                    {
                        sb.Append("└");
                    }
                }
                else  //  不是路径上最后一个结点
                {
                    if (HasNextSibling(nodeInPath)) //  有下一个兄弟,加竖线
                    {
                        sb.Append("│");
                    }
                    else  //  没有下一个兄弟,加空白
                    {
                        sb.Append(" ");
                    }
                }
            }
            return sb.ToString();
        }

    }

[/code]


剩下的就是使用这个类了。首先需要一个实现了 ITreeNode 的类,可以让项目中的实体类实现这个接口,这里我写了一个 FakeTreeNode 类,他默然的 Ordering 值是 999。

[code=C]
    public class FakeTreeNode : ITreeNode
    {
        public FakeTreeNode()
        {
            Ordering = 999;
        }
        #region ITreeNode 成员

        public int Id
        {
            get;
            set;
        }

        public int ParentId
        {
            get;
            set;
        }

        public int Ordering
        {
            get;
            set;
        }

        #endregion

        public string Title { get; set; }
    }

[/code]

测试:

[code=C]

    class Program
    {
        public static void Main()
        {
            FakeTreeNode n1 = new FakeTreeNode { Id = 1, ParentId = 0, Title = "图书" };
            FakeTreeNode n2 = new FakeTreeNode { Id = 2, ParentId = 1, Title = "小说" };
            FakeTreeNode n3 = new FakeTreeNode { Id = 3, ParentId = 2, Title = "历史" };
            FakeTreeNode n4 = new FakeTreeNode { Id = 4, ParentId = 2, Title = "武侠", Ordering = 1 };
            FakeTreeNode n5 = new FakeTreeNode { Id = 5, ParentId = 2, Title = "言情" };
            FakeTreeNode n6 = new FakeTreeNode { Id = 6, ParentId = 1, Title = "散文" };
            FakeTreeNode n7 = new FakeTreeNode { Id = 7, ParentId = 0, Title = "电器" };
            FakeTreeNode n8 = new FakeTreeNode { Id = 8, ParentId = 7, Title = "冰箱" };
            FakeTreeNode n9 = new FakeTreeNode { Id = 9, ParentId = 7, Title = "电视" };
            FakeTreeNode n10 = new FakeTreeNode { Id = 10, ParentId = 9, Title = "液晶电视" };


            List <FakeTreeNode> nodes = new List <FakeTreeNode>();
            nodes.Add(n1);
            nodes.Add(n2);
            nodes.Add(n3);
            nodes.Add(n4);
            nodes.Add(n5);
            nodes.Add(n6);
            nodes.Add(n7);
            nodes.Add(n8);
            nodes.Add(n9);
            nodes.Add(n10);

            Forest <FakeTreeNode> forest = new Forest <FakeTreeNode>(nodes);
            output(forest);

            n7.Ordering = 998; // 电器前移
            n8.Ordering = 1000; // 冰箱后移
            forest = new Forest <FakeTreeNode>(forest);
            output(forest);

            Console.ReadKey();
        }


        private static void output(Forest <FakeTreeNode> forest)
        {
            foreach (var item in forest)
            {
                Console.WriteLine(forest.GetTreeLines(item) + item.Title);
            }
            Console.WriteLine("-----------");
        }
    }

[/code]

使用一个 foreach (var item in forest) 循环就可以按照期望的顺序打印整个类别层次。输出:

├图书
│├小说
││├武侠
││├历史
││└言情
│└散文
└电器
 ├冰箱
 └电视
  └液晶电视
-----------
├电器
│├电视
││└液晶电视
│└冰箱
└图书
 ├小说
 │├武侠
 │├历史
 │└言情
 └散文
-----------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值