이 클래스에서 만든 트리가 있습니다.
class Node
{
public string Key { get; }
public List<Node> Children { get; }
}
모든 자녀와 모든 자녀를 검색하여 조건과 일치하는 항목을 얻고 싶습니다.
node.Key == SomeSpecialKey
어떻게 구현할 수 있습니까?
이 클래스에서 만든 트리가 있습니다.
class Node
{
public string Key { get; }
public List<Node> Children { get; }
}
모든 자녀와 모든 자녀를 검색하여 조건과 일치하는 항목을 얻고 싶습니다.
node.Key == SomeSpecialKey
어떻게 구현할 수 있습니까?
답변:
이것은 재귀가 필요하다는 오해입니다. 그것은 것입니다 스택 또는 큐와 쉬운 방법을 필요로 재귀를 사용하여 구현하는 것입니다. 완전성을 위해 비재 귀적 답변을 제공하겠습니다.
static IEnumerable<Node> Descendants(this Node root)
{
var nodes = new Stack<Node>(new[] {root});
while (nodes.Any())
{
Node node = nodes.Pop();
yield return node;
foreach (var n in node.Children) nodes.Push(n);
}
}
예를 들어 다음 표현식을 사용하여 사용하십시오.
root.Descendants().Where(node => node.Key == SomeSpecialKey)
StackOverflowException
.
Queue<Node>
( Enqueue
/ Dequeue
에서 Push
/에 해당하는 변경 사항과 함께)로 변경할 수 있습니다 Pop
.
public static class TreeToEnumerableEx
{
public static IEnumerable<T> AsDepthFirstEnumerable<T>(this T head, Func<T, IEnumerable<T>> childrenFunc)
{
yield return head;
foreach (var node in childrenFunc(head))
{
foreach (var child in AsDepthFirstEnumerable(node, childrenFunc))
{
yield return child;
}
}
}
public static IEnumerable<T> AsBreadthFirstEnumerable<T>(this T head, Func<T, IEnumerable<T>> childrenFunc)
{
yield return head;
var last = head;
foreach (var node in AsBreadthFirstEnumerable(head, childrenFunc))
{
foreach (var child in childrenFunc(node))
{
yield return child;
last = child;
}
if (last.Equals(node)) yield break;
}
}
}
head
및 childrenFunc
매개 변수 검사가 통과 시간을 지연되지 않도록 두 부분으로 방법을 깰.
Linq를 구문과 같은 방식으로 유지하려면 모든 하위 항목 (자식 + 자식의 자식 등)을 가져 오는 방법을 사용할 수 있습니다.
static class NodeExtensions
{
public static IEnumerable<Node> Descendants(this Node node)
{
return node.Children.Concat(node.Children.SelectMany(n => n.Descendants()));
}
}
이 열거 형은 where 또는 first 또는 무엇이든 사용하여 다른 것과 마찬가지로 쿼리 할 수 있습니다.
이 확장 방법을 시도하여 트리 노드를 열거 할 수 있습니다.
static IEnumerable<Node> GetTreeNodes(this Node rootNode)
{
yield return rootNode;
foreach (var childNode in rootNode.Children)
{
foreach (var child in childNode.GetTreeNodes())
yield return child;
}
}
그런 다음 Where()
절 과 함께 사용하십시오 .
var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);
아마도 당신은 단지
node.Children.Where(child => child.Key == SomeSpecialKey)
또는 한 단계 더 깊이 검색해야하는 경우
node.Children.SelectMany(
child => child.Children.Where(child => child.Key == SomeSpecialKey))
모든 수준에서 검색해야하는 경우 다음을 수행하십시오.
IEnumerable<Node> FlattenAndFilter(Node source)
{
List<Node> l = new List();
if (source.Key == SomeSpecialKey)
l.Add(source);
return
l.Concat(source.Children.SelectMany(child => FlattenAndFilter(child)));
}
public class Node
{
string key;
List<Node> children;
public Node(string key)
{
this.key = key;
children = new List<Node>();
}
public string Key { get { return key; } }
public List<Node> Children { get { return children; } }
public Node Find(Func<Node, bool> myFunc)
{
foreach (Node node in Children)
{
if (myFunc(node))
{
return node;
}
else
{
Node test = node.Find(myFunc);
if (test != null)
return test;
}
}
return null;
}
}
그런 다음 다음과 같이 검색 할 수 있습니다.
Node root = new Node("root");
Node child1 = new Node("child1");
Node child2 = new Node("child2");
Node child3 = new Node("child3");
Node child4 = new Node("child4");
Node child5 = new Node("child5");
Node child6 = new Node("child6");
root.Children.Add(child1);
root.Children.Add(child2);
child1.Children.Add(child3);
child2.Children.Add(child4);
child4.Children.Add(child5);
child5.Children.Add(child6);
Node test = root.Find(p => p.Key == "child6");
IEnumerable<T>
확장 방법을 사용하지 않는 이유
public static IEnumerable<TResult> SelectHierarchy<TResult>(this IEnumerable<TResult> source, Func<TResult, IEnumerable<TResult>> collectionSelector, Func<TResult, bool> predicate)
{
if (source == null)
{
yield break;
}
foreach (var item in source)
{
if (predicate(item))
{
yield return item;
}
var childResults = SelectHierarchy(collectionSelector(item), collectionSelector, predicate);
foreach (var childItem in childResults)
{
yield return childItem;
}
}
}
그럼 그냥 해
var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);
얼마 전에 Linq를 사용하여 트리와 같은 구조를 쿼리하는 방법을 설명하는 코드 프로젝트 기사를 작성했습니다.
http://www.codeproject.com/KB/linq/LinqToTree.aspx
하위 항목, 하위 항목, 조상 등을 검색 할 수있는 linq-to-XML 스타일 API를 제공합니다.
현재 문제에 대해서는 과잉 일 가능성이 있지만 다른 사람들에게는 관심이있을 수 있습니다.
나는 어떤 것을 평평하게 할 수있는 일반적인 확장 메서드를 가지고 있으며 IEnumerable<T>
그 평탄화 된 컬렉션에서 원하는 노드를 얻을 수 있습니다.
public static IEnumerable<T> FlattenHierarchy<T>(this T node, Func<T, IEnumerable<T>> getChildEnumerator)
{
yield return node;
if (getChildEnumerator(node) != null)
{
foreach (var child in getChildEnumerator(node))
{
foreach (var childOrDescendant in child.FlattenHierarchy(getChildEnumerator))
{
yield return childOrDescendant;
}
}
}
}
다음과 같이 사용하십시오.
var q = from node in myTree.FlattenHierarchy(x => x.Children)
where node.Key == "MyKey"
select node;
var theNode = q.SingleOrDefault();
트리 항목을 열거하기 위해 다음 구현을 사용합니다.
public static IEnumerable<Node> DepthFirstUnfold(this Node root) =>
ObjectAsEnumerable(root).Concat(root.Children.SelectMany(DepthFirstUnfold));
public static IEnumerable<Node> BreadthFirstUnfold(this Node root) {
var queue = new Queue<IEnumerable<Node>>();
queue.Enqueue(ObjectAsEnumerable(root));
while (queue.Count != 0)
foreach (var node in queue.Dequeue()) {
yield return node;
queue.Enqueue(node.Children);
}
}
private static IEnumerable<T> ObjectAsEnumerable<T>(T obj) {
yield return obj;
}
위의 구현에서 BreadthFirstUnfold는 노드 큐 대신 노드 시퀀스 큐를 사용합니다. 이것은 고전적인 BFS 알고리즘 방식이 아닙니다.
그리고 재미를 위해 (거의 10 년 후) Generics를 사용하지만 @vidstige가 수락 한 답변을 기반으로 Stack 및 While 루프를 사용하는 답변입니다.
public static class TypeExtentions
{
public static IEnumerable<T> Descendants<T>(this T root, Func<T, IEnumerable<T>> selector)
{
var nodes = new Stack<T>(new[] { root });
while (nodes.Any())
{
T node = nodes.Pop();
yield return node;
foreach (var n in selector(node)) nodes.Push(n);
}
}
public static IEnumerable<T> Descendants<T>(this IEnumerable<T> encounter, Func<T, IEnumerable<T>> selector)
{
var nodes = new Stack<T>(encounter);
while (nodes.Any())
{
T node = nodes.Pop();
yield return node;
if (selector(node) != null)
foreach (var n in selector(node))
nodes.Push(n);
}
}
}
컬렉션이 주어지면 다음과 같이 사용할 수 있습니다.
var myNode = ListNodes.Descendants(x => x.Children).Where(x => x.Key == SomeKey);
또는 루트 개체와 함께
var myNode = root.Descendants(x => x.Children).Where(x => x.Key == SomeKey);