使用1 LINQ语句从Hierarchical数据填充树

时间:2010-05-03 18:52:33

标签: linq

我已经设置了这个编程练习。

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

namespace ConsoleApplication2
{

    class DataObject {
       public int ID { get; set; }
       public int ParentID { get; set; }
       public string Data { get; set; }
       public  DataObject(int id, int pid, string data) { this.ID = id; this.ParentID = pid; this.Data = data; }
    }

    class TreeNode {
        public DataObject Data {get;set;}
        public List<DataObject> Children { get; set; }
    }


    class Program
    {

        static void Main(string[] args)
        {
            List<DataObject> data = new List<DataObject>();
            data.Add(new DataObject(1, 0, "Item 1"));
            data.Add(new DataObject(2, 0, "Item 2"));
            data.Add(new DataObject(21, 2, "Item 2.1"));
            data.Add(new DataObject(22, 2, "Item 2.2"));
            data.Add(new DataObject(221, 22, "Item 2.2.1"));
            data.Add(new DataObject(3, 0, "Item 3"));

        }
    }
}

所需的输出是3个treenode的列表,包含项目1,2和3.项目2将包含2个数据对象的列表作为其子成员,依此类推。

我一直在尝试使用LINQ中的1个语句填充这棵树(或者更确切地说是森林)。一个简单的组可以提供所需的数据,但挑战在于将它组织在TreeNode对象中。

有人可以为此提供暗示或不可能的结果吗?

2 个答案:

答案 0 :(得分:1)

LINQ对递归数据结构的效果不是很好,但你可以近距离接触。

由于您可能想要一个任意深度的树,我将更改您对Children的定义,并为构造函数添加一个辅助方法,该方法将执行使树构建LINQ语句成为可能所需的递归。

class TreeNode
{
    public DataObject Data { get; set; }
    public List<TreeNode> Children { get; private set; }

    public TreeNode(DataObject data)
    {
        Data = data;
        Children = new List<TreeNode>();
    }

    //name chosen to match XElement method.  I would name this
    //SelfAndDescendants or change the behavior to match the name.
    public IEnumerable<TreeNode> DescendantsAndSelf()
    {
        return (new TreeNode[] { this }).Concat(from c in this.Children
                                                from sc in c.DescendantsAndSelf()
                                                select sc);
    }
}

现在我们可以定义以下方法:

public static TreeNode BuildTree(IEnumerable<DataObject> items, int rootId)
{
    var root = new TreeNode(new DataObject(rootId, int.MinValue, "Root"));
    return (from i in items
            let n = new TreeNode(i)
            group n by n.Data.ParentID into nodeGroup
            orderby nodeGroup.Key ascending
            select nodeGroup)
            .Aggregate(root, (parent, childGroup) =>
            {
                parent.DescendantsAndSelf()
                      .First(n => n.Data.ID == childGroup.Key)
                      .Children.AddRange(childGroup);
                return parent; 
            });
}

这种方法做了一些假设,但应该让你朝着正确的方向前进。

  • 传入的元素都不是树的根节点,并且将创建根节点作为返回值。
  • 父节点的id始终低于节点的id(这样可以调用OrderBy + Aggregate将项目推送到树中)
  • 传递给方法的序列中的每个项目都属于根或其他项目之一(否则.First()调用将抛出异常)。

答案 1 :(得分:0)

如果你只想要两层树,那就相当简单了:

var roots = from root in data
where !data.Any(d => root.ParentID == d.ID)
select new TreeNode 
{
    Data = root,
    Children = data.Where(d => d.ParentID == root.ID).ToList(),
}