递归Linq - 子属性的父指示?

时间:2015-03-15 15:04:52

标签: c# linq recursion

我有一个表(假设这个例子是DataTable),它有层次结构记录(id,ParentId,IsEmergency):

enter image description here

ParentId=0表示root。

所以我们有这个:

0
|
+--- 4706606
|
+--- 4706605
|
+--- 4666762
        |
        +--- 4668461
               |
               +--- 4706607 

我只显示那些有parentId=0

的记录

但我想添加另一个逻辑列,其中指出“此父级在其中一个子/子小孩中有IsEmergency=true

所以我在追求

id         parentId        hasEmergencyInOneOfItsChild
-------------------------------------------------------
4706606    0                false
4706605    0                false
4666762    0                true

问题:

如何通过递归linq实现它?

为方便起见,我创建了这个数据表:

   DataTable dt = new DataTable("myTable");
   dt.Columns.Add("id", typeof (int));
   dt.Columns.Add("parentId", typeof (int));
   dt.Columns.Add("isEmergency", typeof (bool));

   DataRow row = dt.NewRow();
   row["id"] =4706606;
   row["parentId"] = 0;
   row["isEmergency"] =false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4706605;
   row["parentId"] = 0;
   row["isEmergency"] = false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4666762;
   row["parentId"] = 0;
   row["isEmergency"] =false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4668461;
   row["parentId"] = 4666762;
   row["isEmergency"] = false;
   dt.Rows.Add(row);

        row = dt.NewRow();
   row["id"] =4706607;
   row["parentId"] = 4668461;
   row["isEmergency"] = true;
   dt.Rows.Add(row);

我尝试了一些东西,但它非常讨厌且效率不高

2 个答案:

答案 0 :(得分:3)

一种方法是定义一个包含相关属性的数据结构和一个递归属性,以确定它是否在isEmergency层次结构中:

internal class Node
{
    internal string Id { get; set; }
    internal IEnumerable<Node> Children { get; set; } 
    internal bool IsEmergency { get; set; }

    internal bool IsEmergencyHierarchy
    {
        get { return IsEmergency || 
                     (Children != null && Children.Any(n => n.IsEmergencyHierarchy)); }
    }
}

然后你可以像这样构建树:

// convert rows to nodes
var nodes = dt.Rows.Cast<DataRow>().Select(r => new Node
{
    Id = r["id"].ToString(),
    ParentId = r["parentId"].ToString(),
    IsEmergency = (r["isEmergency"] as bool? == true)
}).ToList();

// group and index by parent id
var grouped = nodes.GroupBy(n => n.ParentId).ToDictionary(g => g.Key);

// match up child nodes
foreach (var n in nodes)
{
    n.Children = grouped.ContainsKey(n.Id) 
        ? (IEnumerable<Node>)grouped[n.Id] 
        : new Node[0];
}

// select top nodes
var top = grouped.ContainsKey("0") 
    ? (IEnumerable<Node>)grouped["0"] 
    : new Node[0];

您可以检查结果:

foreach (var t in top)
{
    Console.WriteLine("{0} - {1}", t.Id, t.IsEmergencyHierarchy);
}

输出:

4706606 - False
4706605 - False
4666762 - True

答案 1 :(得分:1)

鉴于您的数据:

var lookup = dt.Rows.Cast<DataRow>().Select(x => new
{
    id = (int)x["id"],
    parentId = (int)x["parentId"],
    isEmergency = (bool)x["isEmergency"],
}).ToLookup(x => x.parentId);

我构建了一个由parentid“排序”的查找(假设它类似于字典,但每个键都有多个值)

// Taken from http://stackoverflow.com/a/21086662/613130
public static IEnumerable<T> SelectSelfDescendents<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
    foreach (var item in source)
    {
        yield return item;

        foreach (T item2 in SelectSelfDescendents(selector(item), selector))
            yield return item2;
    }
}

一个递归函数,用于递归返回输入集合和后代

var roots = lookup[0].Select(x => new
{
    x.id,
    // x.parentId // useless, it's 0
    isEmergency = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).Any(y => y.isEmergency),
    childs = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).ToArray()
}).ToArray();

一个小Linq处理一切: - )

lookup[0]

我们从“root”元素(具有parentId == 0的元素)开始

这一个

.SelectSelfDescendents(new[] { x }, y => lookup[y.id])

给定一个不可数的返回ienumerable加上它的所有递归子项。

new[] { x }

转换单个元素集合中的“当前”根元素。

y => lookup[y.id]

这是一个后代选择器:给定一个元素,它在查找中找到所有以y.id作为父项的子项(因为查找是通过parentId)

所以最后SelectSelfDescendents将返回new[] { x }与其所有“后代”连接

.Any(y => y.isEmergency)

如果有任何isEmergency true返回true

现在,正如您所注意到的那样,我正在重新计算SelectSelfDescendants两次(一次用于isEmergency,一次用于childs)...实际上childs是一个属性我已经添加到“调试”递归枚举,所以如果没有必要,可以删除它。如果您想保留它,可以使用let关键字(或“展开”let关键字,就像您在聊天中所显示的那样...这是相同的事情)。请注意,我使用SelectSelfDescendents“缓存”.ToArray()(正如您所做的那样)。没有它,它仍然会被评估两次,因为它返回IEnumerable<>

var roots = (from x in lookup[0]
             let childs = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).ToArray()
             select new
             {
                 x.id,
                 // x.parentId // useless, it's 0
                 isEmergency = childs.Any(y => y.isEmergency),
                 childs 
             }).ToArray();