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

使用1 LINQ语句从分层数据填充树,linq,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 stri

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

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

        }
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
命名空间控制台应用程序2
{
类数据对象{
公共int ID{get;set;}
public int ParentID{get;set;}
公共字符串数据{get;set;}
公共数据对象(int-id,int-pid,字符串数据){this.id=id;this.ParentID=pid;this.data=data;}
}
三烯类{
公共数据对象数据{get;set;}
公共列表子项{get;set;}
}
班级计划
{
静态void Main(字符串[]参数)
{
列表数据=新列表();
添加(新的数据对象(1,0,“第1项”);
添加(新的数据对象(2,0,“第2项”);
添加(新数据对象(21,2,“第2.1项”);
添加数据(新数据对象(22,2,“第2.2项”);
添加数据(新数据对象(221、22,“第2.2.1项”);
添加(新的数据对象(3,0,“第3项”);
}
}
}
所需的输出是3个三节点的列表,具有项目1、2和3。项2将有一个包含2个数据对象的列表作为其子成员,依此类推

我一直在尝试使用LINQ中的一条语句来填充这棵树(或者更确切地说是一个林)。一个简单的GROUPBY给了我所需的数据,但挑战是如何在TreeNode对象中组织它


有人能给你一个提示或者一个不可能的结果吗?

如果你只想要两个层次的树,它非常简单:

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(),
}

如果只需要两层树,则非常简单:

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(),
}

LINQ在递归数据结构方面做得不是特别好,但是您可以接近它

由于您可能需要任意深度的树,我将更改您对子级的定义,并添加一个构造函数和一个助手方法,该方法将执行使树构建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);
    }
}
类树节点
{
公共数据对象数据{get;set;}
公共列表子项{get;private set;}
公共树节点(数据对象数据)
{
数据=数据;
Children=新列表();
}
//选择与XElement方法匹配的名称。我将为其命名
//selfandsubjects或更改行为以匹配名称。
公共IEnumerable后代和self()
{
return(newtreenode[]{this}).Concat(在this.Children中来自c
c.后代和自我()中的sc
选择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; 
            });
}
publicstatictreeNodebuildtree(IEnumerable项,int rootId)
{
var root=newtreenode(新数据对象(rootId,int.MinValue,“root”);
返回(从项目中的i开始)
设n=新树节点(i)
按n.Data.ParentID将n分组到节点组中
orderby节点组。键升序
选择节点组)
.聚合(根,(父,子组)=>
{
parent.genderantsandself()
.First(n=>n.Data.ID==childGroup.Key)
.childreng.AddRange(childGroup);
返回父母;
});
}
这种方法会做出一些假设,但应该会让你走上正确的方向

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

LINQ在递归数据结构方面做得不是特别好,但您可以接近它

由于您可能需要任意深度的树,我将更改您对子级的定义,并添加一个构造函数和一个助手方法,该方法将执行使树构建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);
    }
}
类树节点
{
公共数据对象数据{get;set;}
公共列表子项{get;private set;}
公共树节点(数据对象数据)
{
数据=数据;
Children=新列表();
}
//选择与XElement方法匹配的名称。我将为其命名
//selfandsubjects或更改行为以匹配名称。
公共IEnumerable后代和self()
{
return(newtreenode[]{this}).Concat(在this.Children中来自c
c.后代和自我()中的sc
选择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; 
            });
}
publicstatictreeNodebuildtree(IEnumerable项,int rootId)
{
var root=newtreenode(新数据对象(rootId,int.MinValue,“root”);
返回(从项目中的i开始)
设n=新树节点(i)
按n.Data.ParentID将n分组到节点组中
orderby节点组。键升序
选择节点组)
.聚合(根,(父,子组)=>
{
parent.genderantsandself()
.First(n=>n.Data.ID==childGroup.Key)
.childreng.AddRange(childGroup);
返回父母;
});
}
这种方法会做出一些假设,但应该会让你走上正确的方向

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