web-dev-qa-db-fra.com

Rechercher un arbre avec LINQ

J'ai un arbre créé à partir de cette classe.

class Node
{
    public string Key { get; }
    public List<Node> Children { get; }
}

Je veux rechercher dans tous les enfants et tous leurs enfants pour obtenir ceux correspondant à une condition:

node.Key == SomeSpecialKey

Comment puis-je le mettre en œuvre?

78

C'est une idée fausse que cela nécessite de la récursivité. Il va nécessiter une pile ou une file d'attente et le moyen le plus simple consiste à l'implémenter en utilisant la récursivité. Par souci d'exhaustivité, je vais fournir une réponse non récursive.

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

Utilisez cette expression par exemple pour l'utiliser:

root.Descendants().Where(node => node.Key == SomeSpecialKey)
150
vidstige

Rechercher un arbre d'objets avec Linq

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;
        }

    }
}
16
CD..

Si vous voulez conserver une syntaxe semblable à celle de Linq, vous pouvez utiliser une méthode pour obtenir tous les descendants (enfants + enfants enfants etc.)

static class NodeExtensions
{
    public static IEnumerable<Node> Descendants(this Node node)
    {
        return node.Children.Concat(node.Children.SelectMany(n => n.Descendants()));
    }
}

Cet énumérable peut ensuite être interrogé comme n'importe quel autre en utilisant où ou en premier ou autre.

14
ForbesLindesay

Vous pouvez essayer cette méthode d'extension pour énumérer les nœuds d'arborescence:

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;
    }
}

Puis utilisez cela avec une clause Where():

var matchingNodes = rootNode.GetTreeNodes().Where(x => x.Key == SomeSpecialKey);
4
dlev
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;
        }
    }

Et puis vous pouvez chercher comme:

    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");
2
Varun Chatterji

Peut-être vous avez juste besoin

node.Children.Where(child => child.Key == SomeSpecialKey)

Ou, si vous avez besoin de chercher un niveau plus profond,

node.Children.SelectMany(
        child => child.Children.Where(child => child.Key == SomeSpecialKey))

Si vous devez effectuer une recherche à tous les niveaux, procédez comme suit:

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)));
}
2
Vlad

Pourquoi ne pas utiliser une méthode d'extension 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;
        }
    }
}

alors fais juste ça

var result = nodes.Children.SelectHierarchy(n => n.Children, n => n.Key.IndexOf(searchString) != -1);
2
Dean Chalk

J'ai une méthode d'extension générique qui peut aplatir n'importe quel IEnumerable<T> et à partir de cette collection aplatie, vous pouvez obtenir le nœud souhaité.

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;
            }
        }
    }
}

Utilisez ceci comme ceci:

var q = from node in myTree.FlattenHierarchy(x => x.Children)
        where node.Key == "MyKey"
        select node;
var theNode = q.SingleOrDefault();
1
Mikael Östberg

Cela ne fonctionnera pas sans récursivité.

Essaye ça:

IEnumerable<Node> GetMatchingNodes(Node parent, string key)
{
    var result = new List<Node>();
    if(parent.Key == key)
        result.Add(parent)
    result.AddRange(parent.Children.Where(c => GetMatchingNodes(c, key)));
    return result;
}

appelez ça comme ça:

Node rootNode = ...;
var allMatchingNodes = GetMatchingNodes(rootNode, key);
1
Daniel Hilgarth

J'utilise les implémentations suivantes pour énumérer les éléments de l'arbre

    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 dans la mise en œuvre ci-dessus utilise la file d'attente de séquences de noeuds au lieu de la file d'attente Ce n'est pas la méthode classique de l'algorithme BFS. 

0

Il y a quelque temps, j'ai écrit un article sur codeproject décrivant comment utiliser Linq pour interroger des structures de type arborescence:

http://www.codeproject.com/KB/linq/LinqToTree.aspx

Ceci fournit une API de style linq-to-XML où vous pouvez rechercher des descendants, des enfants, des ancêtres, etc.

Probablement exagéré pour votre problème actuel, mais pourrait intéresser les autres.

0
ColinE

Vous pouvez utiliser cette méthode d'extension pour interroger l'arborescence.

    public static IEnumerable<Node> InTree(this Node treeNode)
    {
        yield return treeNode;

        foreach (var childNode in treeNode.Children)
            foreach (var flattendChild in InTree(childNode))
                yield return flattendChild;
    }
0
BitKFu