我需要在树中搜索可能在树中任何位置的数据。如何用linq完成?
class Program
{
static void Main(string[] args) {
var familyRoot = new Family() {Name = "FamilyRoot"};
var familyB = new Family() {Name = "FamilyB"};
familyRoot.Children.Add(familyB);
var familyC = new Family() {Name = "FamilyC"};
familyB.Children.Add(familyC);
var familyD = new Family() {Name = "FamilyD"};
familyC.Children.Add(familyD);
//There can be from 1 to n levels of families.
//Search all children, grandchildren, great grandchildren etc, for "FamilyD" and return the object.
}
}
public class Family {
public string Name { get; set; }
List<Family> _children = new List<Family>();
public List<Family> Children {
get { return _children; }
}
}
答案 0 :(得分:8)
这是It'sNotALie.
s answer的扩展名。
public static class Linq
{
public static IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten(c, selector))
.Concat(new[] { source });
}
}
示例测试用法:
var result = familyRoot.Flatten(x => x.Children).FirstOrDefault(x => x.Name == "FamilyD");
返回familyD
个对象。
您也可以将其设为IEnumerable<T>
来源:
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
return source.SelectMany(x => Flatten(x, selector))
.Concat(source);
}
答案 1 :(得分:6)
没有递归的另一种解决方案......
var result = FamilyToEnumerable(familyRoot)
.Where(f => f.Name == "FamilyD");
IEnumerable<Family> FamilyToEnumerable(Family f)
{
Stack<Family> stack = new Stack<Family>();
stack.Push(f);
while (stack.Count > 0)
{
var family = stack.Pop();
yield return family;
foreach (var child in family.Children)
stack.Push(child);
}
}
答案 2 :(得分:1)
简单:
familyRoot.Flatten(f => f.Children);
//you can do whatever you want with that sequence there.
//for example you could use Where on it and find the specific families, etc.
IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten(selector(c), selector))
.Concat(new[]{source});
}
答案 3 :(得分:1)
我喜欢Kenneth Bo Christensen使用堆栈的答案,它运行良好,易于阅读且速度快(并且不使用递归)。 唯一令人不快的是它颠倒了子项的顺序(因为堆栈是FIFO)。如果排序顺序对您无关紧要,那就没关系。 如果是这样,可以使用选择器(当前)轻松实现排序。在foreach循环中使用反向()(其余代码与Kenneth的原始帖子相同)...
public static IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
var stack = new Stack<T>();
stack.Push(source);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach (var child in selector(current).Reverse())
stack.Push(child);
}
}
答案 4 :(得分:0)
嗯,我想方法是采用分层结构的技术:
您需要递归部分
// Anchor
rootFamily.Children.ForEach(childFamily =>
{
if (childFamily.Name.Contains(search))
{
// Your logic here
return;
}
SearchForChildren(childFamily);
});
// Recursion
public void SearchForChildren(Family childFamily)
{
childFamily.Children.ForEach(_childFamily =>
{
if (_childFamily.Name.Contains(search))
{
// Your logic here
return;
}
SearchForChildren(_childFamily);
});
}
答案 5 :(得分:0)
因此,最简单的选择是编写一个遍历层次结构并生成单个序列的函数。然后,这将在LINQ操作开始时进行,例如
IEnumerable<T> Flatten<T>(this T source)
{
foreach(var item in source) {
yield item;
foreach(var child in Flatten(item.Children)
yield child;
}
}
简单地调用:familyRoot.Flatten()。Where(n =&gt; n.Name ==“Bob”);
一种轻微的替代方法可以让您快速忽略整个分支:
IEnumerable<T> Flatten<T>(this T source, Func<T, bool> predicate)
{
foreach(var item in source) {
if (predicate(item)) {
yield item;
foreach(var child in Flatten(item.Children)
yield child;
}
}
然后你可以做以下事情:family.Flatten(n =&gt; n.Children.Count&gt; 2)。在哪里(...)
答案 6 :(得分:0)
我尝试了两个建议的代码,并使代码更清晰:
public static IEnumerable<T> Flatten1<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten1(c, selector)).Concat(new[] { source });
}
public static IEnumerable<T> Flatten2<T>(this T source, Func<T, IEnumerable<T>> selector)
{
var stack = new Stack<T>();
stack.Push(source);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach (var child in selector(current))
stack.Push(child);
}
}
Flatten2()似乎有点快,但它的运行速度很快。
答案 7 :(得分:0)
其他一些变种的答案是“不是”,MarcinJuraszek和DamienG。
首先,前两个给出了违反直觉的命令。要获得对结果进行良好的树遍历排序,只需反转连接(首先放置&#34;源&#34;)。
其次,如果您正在使用像EF这样的昂贵资源,并且您希望限制整个分支,Damien建议您注入谓词是一个很好的,并且仍然可以使用Linq。
最后,对于昂贵的来源,使用注入的选择器从每个节点预先选择感兴趣的字段也可能是好的。
将所有这些放在一起:
public static IEnumerable<R> Flatten<T,R>(this T source, Func<T, IEnumerable<T>> children
, Func<T, R> selector
, Func<T, bool> branchpredicate = null
) {
if (children == null) throw new ArgumentNullException("children");
if (selector == null) throw new ArgumentNullException("selector");
var pred = branchpredicate ?? (src => true);
if (children(source) == null) return new[] { selector(source) };
return new[] { selector(source) }
.Concat(children(source)
.Where(pred)
.SelectMany(c => Flatten(c, children, selector, pred)));
}