我有一个表(假设这个例子是DataTable),它有层次结构记录(id,ParentId,IsEmergency):
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);
我尝试了一些东西,但它非常讨厌且效率不高
答案 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();