LINQ का उपयोग कर एक पेड़ की खोज करना


87

मेरे पास इस वर्ग का एक पेड़ है।

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

मैं सभी बच्चों और उनके सभी बच्चों की तलाश करना चाहता हूं ताकि उन्हें एक शर्त मिल सके:

node.Key == SomeSpecialKey

मैं इसे कैसे लागू कर सकता हूं?


दिलचस्प है, मुझे लगता है कि आप SelectMany फ़ंक्शन का उपयोग करके इसे पूरा कर सकते हैं, याद रखें कि कुछ समय पहले कुछ समान करना है।
जेथ्रो

जवाबों:


175

यह एक गलत धारणा है कि इसके लिए पुनरावृत्ति की आवश्यकता है। इसके लिए एक स्टैक या एक कतार की आवश्यकता होगी और पुनरावृत्ति का उपयोग करके इसे लागू करने का सबसे आसान तरीका है। पूर्णता के लिए मैं एक गैर-पुनरावर्ती उत्तर प्रदान करूंगा।

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)

31
+1। और यह विधि तब काम करती रहेगी जब पेड़ इतना गहरा होगा कि एक पुनरावर्ती ट्रावेल कॉल स्टैक को उड़ा देगा और ए StackOverflowException
ल्यूक

3
@ ल्यूक हालांकि उन स्थितियों के लिए इस तरह के विकल्प होना उपयोगी है, इसका मतलब होगा कि एक बहुत बड़ा पेड़। जब तक आप पेड़ बहुत गहरे हैं, पुनरावर्ती तरीके सामान्य रूप से सरल / अधिक पठनीय हैं।
फोर्ब्स लिंडसेय

3
@ टस्कन: पुनरावर्ती पुनरावृत्तियों का उपयोग करने के भी प्रदर्शन निहितार्थ होते हैं, "The Iterators की लागत" देखें "अनुभाग" blogs.msdn.com/b/wesdyer/archive/2007/03/23/… ((माना जाता है कि पेड़ों को काफी गहरा होने की आवश्यकता है) यह ध्यान देने योग्य है)। और, fwiw, मैं vidstige के उत्तर को यहाँ पढ़ने वाले उत्तर के समान ही पढ़ने योग्य पाता हूँ।
ल्यूक

3
हाँ, प्रदर्शन के कारण मेरा समाधान न चुनें। जब तक अड़चन साबित नहीं हो जाती, तब तक पठनीयता हमेशा पहले होती है। हालांकि मेरा समाधान बहुत सीधा है, इसलिए मुझे लगता है कि यह स्वाद की बात है ... मैंने वास्तव में अपने उत्तर को केवल पुनरावर्ती उत्तरों के पूरक के रूप में पोस्ट किया है, लेकिन मुझे खुशी है कि लोगों ने इसे पसंद किया।
vidstige

11
मुझे लगता है कि यह ध्यान देने योग्य है कि उपर्युक्त समाधान एक अंतिम (अंतिम-बच्चा-पहला) गहराई-पहली खोज करता है। यदि आप एक (प्रथम बाल-प्रथम) चौड़ाई-पहले खोज करना चाहता था, तो आप करने के लिए नोड्स संग्रह का प्रकार बदल सकते हैं Queue<Node>(करने के लिए इसी परिवर्तन के साथ Enqueue/ Dequeueसे Push/ Pop)।
एंड्रयू कॉन्स

16

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

    }
}

1
+1 समस्या को सामान्य रूप से हल करता है। लिंक किए गए लेख ने एक महान व्याख्या प्रदान की।
जॉन यीशु

पूरा होने के लिए आपको मापदंडों पर अशक्त जाँच की आवश्यकता होती है headऔर childrenFuncविधियों को दो भागों में तोड़ने की आवश्यकता होती है ताकि पैरामीटर जाँच त्रैमासिक समय के लिए स्थगित न हो।
एरिक

15

यदि आप Linq को syntax की तरह बनाए रखना चाहते हैं, तो आप सभी वंशज (बच्चों + बच्चों के बच्चे) को प्राप्त करने के लिए एक विधि का उपयोग कर सकते हैं।

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

यह गणना करने योग्य किसी भी अन्य का उपयोग या जहां या पहले या जो भी हो, की तरह किया जा सकता है।


मुझे यह पसंद है, साफ! :)
vidstige

3

आप ट्री नोड्स को एन्यूमरेट करने के लिए इस विस्तार विधि को आजमा सकते हैं:

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

2
ध्यान दें कि यह तकनीक अक्षम है अगर पेड़ गहरा है और अपवाद को फेंक सकता है यदि पेड़ बहुत गहरा है।
एरिक लिपर्ट

1
@ एरिक अच्छा बिंदु। और छुट्टी से वापस स्वागत है? (यह इस इंटरनेट बात दुनिया में फैले के साथ क्या बताने के लिए मुश्किल है।)
dlev

2

शायद आपको बस जरूरत है

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

क्या वह बाल-बच्चों की खोज करेगा?
जेथ्रो

मुझे लगता है कि यह अभ्यस्त काम है, क्योंकि यह पेड़ में केवल एक स्तर पर खोज करता है और एक पूर्ण पेड़ ट्रैवर्सल नहीं करता है
lunactic

@ यूफुक: पहली पंक्ति केवल 1 स्तर गहरी, दूसरी केवल 2 स्तरों गहरी काम करती है। यदि आपको सभी स्तरों पर खोज करने की आवश्यकता है , तो आपको एक पुनरावर्ती फ़ंक्शन की आवश्यकता है।
व्लाद

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

क्योंकि Find का इनपुट Func <Node, bool> myFunc है आप इस विधि का उपयोग किसी अन्य संपत्ति को फ़िल्टर करने के लिए कर सकते हैं जिसे आप नोड में भी परिभाषित कर सकते हैं। उदाहरण के लिए नोड में एक नाम संपत्ति थी और आप नाम से एक नोड ढूंढना चाहते थे, आप बस p => p.Name == "समथिंग" में पास कर सकते थे
वरुण चटर्जी

2

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

0

कुछ समय पहले मैंने एक कोडप्रोजेक्ट लेख लिखा था, जो वर्णन करता है कि लाइनक को पेड़ जैसी संरचनाओं का उपयोग कैसे करें:

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

यह एक लाइन-टू-एक्सएमएल स्टाइल एपीआई प्रदान करता है जहां आप वंशज, बच्चों, पूर्वजों आदि की खोज कर सकते हैं ...

संभवतः आपकी वर्तमान समस्या के लिए overkill, लेकिन दूसरों के लिए ब्याज की हो सकती है।


0

आप ट्री को क्वेरी करने के लिए इस एक्सटेंशन विधि का उपयोग कर सकते हैं।

    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

मेरे पास एक सामान्य एक्सटेंशन विधि है जो किसी भी 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();

0

मैं पेड़ के सामानों की गणना के लिए निम्नलिखित कार्यान्वयन का उपयोग करता हूं

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

ऊपर के कार्यान्वयन में ब्रेडफर्स्ट यूएनफोल्ड नोड्स कतार के बजाय नोड अनुक्रमों की कतार का उपयोग करता है। यह क्लासिक बीएफएस एल्गोरिथ्म तरीका नहीं है।


0

और बस मज़े के लिए (लगभग एक दशक बाद) एक उत्तर भी जेनरिक का उपयोग करके लेकिन एक स्टैक और लूप के साथ, @vidstige द्वारा स्वीकृत उत्तर के आधार पर।

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);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.