कैसे LINQ के माध्यम से पेड़ समतल करने के लिए?


95

इसलिए मेरे पास साधारण पेड़ है:

class MyNode
{
 public MyNode Parent;
 public IEnumerable<MyNode> Elements;
 int group = 1;
}

मेरे पास ए IEnumerable<MyNode>। मैं एक फ्लैट सूची के रूप में सभी की सूची MyNode(आंतरिक नोड ऑब्जेक्ट सहित Elements) ( ) प्राप्त करना चाहता हूं Where group == 1। LINQ के माध्यम से ऐसे कैसे करें?


1
आप किस क्रम की चपटी सूची में होना चाहते हैं?
फिलिप

1
बच्चे के नोड्स होने पर नोड्स कब बंद हो जाते हैं? मुझे लगता है कि यह कब Elementsअशक्त या खाली है?
एडम हल्ड्सवर्थ


इसे संबोधित करने का सबसे आसान / सबसे स्पष्ट तरीका एक पुनरावर्ती LINQ क्वेरी का उपयोग करना है। यह प्रश्न: stackoverflow.com/questions/732281/expressing-recursion-in-linq पर इस पर बहुत चर्चा हुई है, और यह विशेष उत्तर कुछ विस्तार में जाता है कि आप इसे कैसे लागू करेंगे।
अल्वारो रॉड्रिग्ज

जवाबों:


138

आप इस तरह एक पेड़ को समतल कर सकते हैं:

IEnumerable<MyNode> Flatten(IEnumerable<MyNode> e) =>
    e.SelectMany(c => Flatten(c.Elements)).Concat(new[] { e });

फिर आप groupउपयोग करके फ़िल्टर कर सकते हैं Where(...)

कुछ "शैली के लिए अंक" अर्जित करने के लिए, Flattenएक स्थिर वर्ग में विस्तार फ़ंक्शन में परिवर्तित करें।

public static IEnumerable<MyNode> Flatten(this IEnumerable<MyNode> e) =>
    e.SelectMany(c => c.Elements.Flatten()).Concat(e);

"और भी बेहतर शैली" के लिए अधिक अंक अर्जित करने के लिए, Flattenएक सामान्य एक्सटेंशन विधि में कनवर्ट करें जो एक पेड़ और एक फ़ंक्शन लेता है जो एक नोड से वंश उत्पन्न करता है:

public static IEnumerable<T> Flatten<T>(
    this IEnumerable<T> e
,   Func<T,IEnumerable<T>> f
) => e.SelectMany(c => f(c).Flatten(f)).Concat(e);

इस फ़ंक्शन को इस तरह से कॉल करें:

IEnumerable<MyNode> tree = ....
var res = tree.Flatten(node => node.Elements);

आप प्री-ऑर्डर के बजाय बाद के आदेश, के पक्षों के आसपास स्विच में सपाट पसंद करेंगे, तो Concat(...)


@AdamHouldsworth संपादन के लिए धन्यवाद! कॉल में तत्व Concatहोना चाहिए new[] {e}, नहीं new[] {c}(यह भी cवहाँ से संकलन नहीं होगा)।
dasblinkenlight 15

मैं असहमत हूं: संकलित, परीक्षण किया गया और साथ काम कर रहा हूं ceसंकलन का उपयोग नहीं करता है। आप if (e == null) return Enumerable.Empty<T>();अशक्त बाल सूचियों का सामना करने के लिए भी जोड़ सकते हैं ।
एडम हाउल्ड्सवर्थ 15

1
अधिक की तरह `सार्वजनिक स्थैतिक IEnumerable <T> Flatten <T> (यह IEnumerable <T> स्रोत, फ़ंक <T, IEnumerable <T >> f) {if (स्रोत == null) वापसी rumerable.Empty <T> (); वापसी का स्रोत .lectMany (c => f (c) .Flatten (f))। कॉनैट (स्रोत); } `
myWallJSON

10
ध्यान दें कि यह समाधान O (nh) है जहाँ n वृक्ष में वस्तुओं की संख्या है और h वृक्ष की औसत गहराई है। चूँकि h O (1) और O (n) के बीच हो सकता है, यह O (n) और O (n वर्ग) एल्गोरिथम के बीच होता है। बेहतर एल्गोरिदम हैं।
एरिक लिपिपर्ट

1
मैंने देखा है कि यदि सूची IEnumerable <baseType> की है, तो फ़ंक्शन फ्लैट की गई सूची में तत्वों को नहीं जोड़ेगा। आप इसे इस तरह से फ़ंक्शन को कॉल करके हल कर सकते हैं: var Res = tree.latten (नोड => नोड। एडल्ट।ऑफटाइप <DerivedType>)
फ्रैंक होरेमन्स

125

स्वीकृत उत्तर के साथ समस्या यह है कि यह अक्षम है अगर पेड़ गहरा है। यदि पेड़ बहुत गहरा है तो यह ढेर को उड़ा देता है। आप एक स्पष्ट स्टैक का उपयोग करके समस्या को हल कर सकते हैं:

public static IEnumerable<MyNode> Traverse(this MyNode root)
{
    var stack = new Stack<MyNode>();
    stack.Push(root);
    while(stack.Count > 0)
    {
        var current = stack.Pop();
        yield return current;
        foreach(var child in current.Elements)
            stack.Push(child);
    }
}

ऊँचाई h के वृक्ष में n नोड्स और एक ब्रांचिंग कारक को n से काफी कम मानते हुए, यह विधि स्टैक स्पेस में O (1) है, हीप स्पेस में O (h) और समय में O (n) है। दिए गए अन्य एल्गोरिदम स्टैक में ओ (एच), ढेर में ओ (1) और समय में ओ (एनएच) है। यदि ब्रांचिंग कारक n की तुलना में छोटा है तो h, O (lg n) और O (n) के बीच है, जो बताता है कि naatesve एल्गोरिथ्म ढेर की खतरनाक मात्रा और बड़ी मात्रा में समय का उपयोग कर सकता है यदि h n के करीब है।

अब जब हमारे पास एक ट्रावेल है, तो आपकी क्वेरी सीधी है:

root.Traverse().Where(item=>item.group == 1);

3
@ जोहनीकार्डी: यदि आप एक बिंदु पर बहस करने जा रहे हैं तो शायद कोड स्पष्ट रूप से सही नहीं है। यह और अधिक स्पष्ट रूप से सही क्या कर सकता है?
एरिक लिपर्ट

3
@ebramtharwat: सही है। आप Traverseसभी तत्वों को बुला सकते हैं। या आप Traverseएक अनुक्रम लेने के लिए संशोधित कर सकते हैं , और क्या इसने अनुक्रम के सभी तत्वों को आगे बढ़ाया है stack। याद रखें, stack"तत्व जिन्हें मैंने अभी तक पता नहीं लगाया है"। या आप एक "डमी" रूट बना सकते हैं, जहां आपका अनुक्रम इसके बच्चे हैं, और फिर डमी रूट को पार करना है।
एरिक लिपर्ट

2
यदि आप करते हैं तो foreach (var child in current.Elements.Reverse())आपको एक अधिक उम्मीद की जा सकती है। विशेष रूप से, बच्चे उस क्रम में दिखाई देंगे जो वे पिछले बच्चे के बजाय पहले दिखाई देते हैं। यह ज्यादातर मामलों में मायने नहीं रखता है, लेकिन मेरे मामले में मुझे एक अनुमानित और अपेक्षित क्रम में समतल होने की आवश्यकता थी।
मीका झोल्टू

2
@MicahZoltu, आप से बचने सकता .Reverseआदान-प्रदान करके Stack<T>एक के लिएQueue<T>
रूबेंस Farias

2
@MicahZoltu आप आदेश के बारे में सही हैं, लेकिन इसके साथ समस्या Reverseयह है कि यह अतिरिक्त पुनरावृत्तियों का निर्माण करता है, जो कि इस दृष्टिकोण से बचने के लिए है। @RubensFarias चौड़ाई-पहले ट्रैवर्सल में परिणाम के Queueलिए प्रतिस्थापन Stack
जैक ए।

25

बस पूर्णता के लिए, यहाँ dasblinkenlight और एरिक लिपर्ट के उत्तरों का संयोजन है। यूनिट का परीक्षण और सब कुछ। :-)

 public static IEnumerable<T> Flatten<T>(
        this IEnumerable<T> items,
        Func<T, IEnumerable<T>> getChildren)
 {
     var stack = new Stack<T>();
     foreach(var item in items)
         stack.Push(item);

     while(stack.Count > 0)
     {
         var current = stack.Pop();
         yield return current;

         var children = getChildren(current);
         if (children == null) continue;

         foreach (var child in children) 
            stack.Push(child);
     }
 }

3
NullReferenceException var बच्चों से बचने के लिए = getChildren (current); if (बच्चों! = null) {foreach (बच्चों में var child) stack। पश (बच्चा); }
सर्ग

2
मैं यह नोट करना चाहूंगा कि भले ही यह सूची को समतल करता है, लेकिन यह इसे रिवर्स ऑर्डर में लौटाता है। अंतिम तत्व पहले बन जाता है
कॉर्कस

21

अपडेट करें:

नेस्टिंग (गहराई) के स्तर में रुचि रखने वाले लोगों के लिए। स्पष्ट एन्यूमरेटर स्टैक कार्यान्वयन के बारे में अच्छी चीजों में से एक यह है कि किसी भी समय (और विशेष रूप से जब तत्व की उपज) stack.Countवर्तमान में प्रसंस्करण गहराई का प्रतिनिधित्व करता है। इसलिए इसे ध्यान में रखते हुए और C # 7.0 वैल्यू टुपल्स का उपयोग करते हुए, हम केवल विधि घोषणा को निम्नानुसार बदल सकते हैं:

public static IEnumerable<(T Item, int Level)> ExpandWithLevel<T>(
    this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)

और yieldकथन:

yield return (item, stack.Count);

फिर हम Selectउपरोक्त विधि को सरल बनाकर मूल विधि को लागू कर सकते हैं:

public static IEnumerable<T> Expand<T>(
    this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector) =>
    source.ExpandWithLevel(elementSelector).Select(e => e.Item);

मूल:

आश्चर्यजनक रूप से कोई भी (यहां तक ​​कि एरिक) ने एक पुनरावर्ती पूर्व-आदेश डीएफटी के "प्राकृतिक" पुनरावृत्त बंदरगाह को दिखाया, इसलिए यहां यह है:

    public static IEnumerable<T> Expand<T>(
        this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
    {
        var stack = new Stack<IEnumerator<T>>();
        var e = source.GetEnumerator();
        try
        {
            while (true)
            {
                while (e.MoveNext())
                {
                    var item = e.Current;
                    yield return item;
                    var elements = elementSelector(item);
                    if (elements == null) continue;
                    stack.Push(e);
                    e = elements.GetEnumerator();
                }
                if (stack.Count == 0) break;
                e.Dispose();
                e = stack.Pop();
            }
        }
        finally
        {
            e.Dispose();
            while (stack.Count != 0) stack.Pop().Dispose();
        }
    }

मुझे लगता है कि आप प्री-ऑर्डर बनाए रखने के लिए eकॉल elementSelectorकरने के लिए हर बार स्विच करते हैं - यदि ऑर्डर में कोई फर्क नहीं पड़ता, तो क्या आप एक eबार शुरू होने के बाद सभी को प्रोसेस करने के लिए फ़ंक्शन बदल सकते हैं ?
NetMage

@NetMage मैं विशेष रूप से पूर्व आदेश चाहता था। छोटे बदलाव के साथ यह पोस्ट ऑर्डर को संभाल सकता है। लेकिन मुख्य बिंदु है, यह डेप्थ फर्स्ट ट्रैवर्सल है । के लिए सांस प्रथम Traversal मैं का प्रयोग करेंगे Queue<T>। वैसे भी, यहाँ विचारार्थक के साथ एक छोटा सा स्टैक रखने का है, जो पुनरावर्ती कार्यान्वयन में हो रहा है के समान है।
इवान स्टोव

@IvanStoev मैं सोच रहा था कि कोड को सरल बनाया जाएगा। मुझे लगता है कि Stackजिग-ज़ैग चौड़ाई वाले पहले ट्रैवर्सल के परिणाम का उपयोग करना होगा।
नेटमैसेज

7

मुझे यहाँ दिए गए उत्तरों के साथ कुछ छोटे मुद्दे मिले:

  • क्या होगा यदि वस्तुओं की प्रारंभिक सूची शून्य है?
  • यदि बच्चों की सूची में एक अशक्त मूल्य है तो क्या होगा?

पिछले उत्तरों पर निर्मित और निम्नलिखित के साथ आया:

public static class IEnumerableExtensions
{
    public static IEnumerable<T> Flatten<T>(
        this IEnumerable<T> items, 
        Func<T, IEnumerable<T>> getChildren)
    {
        if (items == null)
            yield break;

        var stack = new Stack<T>(items);
        while (stack.Count > 0)
        {
            var current = stack.Pop();
            yield return current;

            if (current == null) continue;

            var children = getChildren(current);
            if (children == null) continue;

            foreach (var child in children)
                stack.Push(child);
        }
    }
}

और इकाई परीक्षण:

[TestClass]
public class IEnumerableExtensionsTests
{
    [TestMethod]
    public void NullList()
    {
        IEnumerable<Test> items = null;
        var flattened = items.Flatten(i => i.Children);
        Assert.AreEqual(0, flattened.Count());
    }
    [TestMethod]
    public void EmptyList()
    {
        var items = new Test[0];
        var flattened = items.Flatten(i => i.Children);
        Assert.AreEqual(0, flattened.Count());
    }
    [TestMethod]
    public void OneItem()
    {
        var items = new[] { new Test() };
        var flattened = items.Flatten(i => i.Children);
        Assert.AreEqual(1, flattened.Count());
    }
    [TestMethod]
    public void OneItemWithChild()
    {
        var items = new[] { new Test { Id = 1, Children = new[] { new Test { Id = 2 } } } };
        var flattened = items.Flatten(i => i.Children);
        Assert.AreEqual(2, flattened.Count());
        Assert.IsTrue(flattened.Any(i => i.Id == 1));
        Assert.IsTrue(flattened.Any(i => i.Id == 2));
    }
    [TestMethod]
    public void OneItemWithNullChild()
    {
        var items = new[] { new Test { Id = 1, Children = new Test[] { null } } };
        var flattened = items.Flatten(i => i.Children);
        Assert.AreEqual(2, flattened.Count());
        Assert.IsTrue(flattened.Any(i => i.Id == 1));
        Assert.IsTrue(flattened.Any(i => i == null));
    }
    class Test
    {
        public int Id { get; set; }
        public IEnumerable<Test> Children { get; set; }
    }
}

4

मामले में किसी और को यह पता चलता है, लेकिन पेड़ को समतल करने के बाद भी स्तर जानने की जरूरत है, यह कोसिमन के डैस्ब्लिंकनेलाइट और एरिक लिपर्ट के समाधान के संयोजन पर फैलता है:

    public static IEnumerable<Tuple<T, int>> FlattenWithLevel<T>(
            this IEnumerable<T> items,
            Func<T, IEnumerable<T>> getChilds)
    {
        var stack = new Stack<Tuple<T, int>>();
        foreach (var item in items)
            stack.Push(new Tuple<T, int>(item, 1));

        while (stack.Count > 0)
        {
            var current = stack.Pop();
            yield return current;
            foreach (var child in getChilds(current.Item1))
                stack.Push(new Tuple<T, int>(child, current.Item2 + 1));
        }
    }

2

वास्तव में एक अन्य विकल्प के लिए एक उचित OO डिज़ाइन होना चाहिए।

उदाहरण के MyNodeलिए सभी समतल वापस लौटने के लिए कहें ।

ऐशे ही:

class MyNode
{
    public MyNode Parent;
    public IEnumerable<MyNode> Elements;
    int group = 1;

    public IEnumerable<MyNode> GetAllNodes()
    {
        if (Elements == null)
        {
            return Enumerable.Empty<MyNode>(); 
        }

        return Elements.SelectMany(e => e.GetAllNodes());
    }
}

अब आप शीर्ष स्तर MyNode को सभी नोड्स प्राप्त करने के लिए कह सकते हैं।

var flatten = topNode.GetAllNodes();

यदि आप कक्षा को संपादित नहीं कर सकते हैं, तो यह एक विकल्प नहीं है। लेकिन अन्यथा, मुझे लगता है कि यह एक अलग (पुनरावर्ती) LINQ विधि के लिए पसंद किया जा सकता है।

यह LINQ का उपयोग कर रहा है, इसलिए मुझे लगता है कि यह उत्तर यहां लागू है;)


शायद Enumerabl.Empty नई सूची से बेहतर है?
फ्रैंक

1
वास्तव में! अपडेट किया गया!
जूलियन

0
void Main()
{
    var allNodes = GetTreeNodes().Flatten(x => x.Elements);

    allNodes.Dump();
}

public static class ExtensionMethods
{
    public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector = null)
    {
        if (source == null)
        {
            return new List<T>();
        }

        var list = source;

        if (childrenSelector != null)
        {
            foreach (var item in source)
            {
                list = list.Concat(childrenSelector(item).Flatten(childrenSelector));
            }
        }

        return list;
    }
}

IEnumerable<MyNode> GetTreeNodes() {
    return new[] { 
        new MyNode { Elements = new[] { new MyNode() }},
        new MyNode { Elements = new[] { new MyNode(), new MyNode(), new MyNode() }}
    };
}

class MyNode
{
    public MyNode Parent;
    public IEnumerable<MyNode> Elements;
    int group = 1;
}

1
अपने विस्तार में एक फ़ॉर्च का उपयोग करने का मतलब है कि यह अब 'विलंबित निष्पादन' नहीं है (जब तक कि आप उपज वापसी का उपयोग नहीं करते हैं)।
त्रिकोणीय Tran

0

डेव के और इवान स्टोव के उत्तर के संयोजन के मामले में आपको घोंसले के स्तर की आवश्यकता है और सूची "क्रम में" चपटी हुई है और कोनिममन द्वारा दिए गए उत्तर की तरह उलट नहीं हुई है।

 public static class HierarchicalEnumerableUtils
    {
        private static IEnumerable<Tuple<T, int>> ToLeveled<T>(this IEnumerable<T> source, int level)
        {
            if (source == null)
            {
                return null;
            }
            else
            {
                return source.Select(item => new Tuple<T, int>(item, level));
            }
        }

        public static IEnumerable<Tuple<T, int>> FlattenWithLevel<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
        {
            var stack = new Stack<IEnumerator<Tuple<T, int>>>();
            var leveledSource = source.ToLeveled(0);
            var e = leveledSource.GetEnumerator();
            try
            {
                while (true)
                {
                    while (e.MoveNext())
                    {
                        var item = e.Current;
                        yield return item;
                        var elements = elementSelector(item.Item1).ToLeveled(item.Item2 + 1);
                        if (elements == null) continue;
                        stack.Push(e);
                        e = elements.GetEnumerator();
                    }
                    if (stack.Count == 0) break;
                    e.Dispose();
                    e = stack.Pop();
                }
            }
            finally
            {
                e.Dispose();
                while (stack.Count != 0) stack.Pop().Dispose();
            }
        }
    }

यह भी अच्छा होगा कि पहले गहराई या चौड़ाई पहले
ह्यूग

0

कोनामिमान के उत्तर पर निर्माण, और टिप्पणी है कि आदेश अप्रत्याशित है, यहां एक स्पष्ट सॉर्ट परम के साथ एक संस्करण है:

public static IEnumerable<T> TraverseAndFlatten<T, V>(this IEnumerable<T> items, Func<T, IEnumerable<T>> nested, Func<T, V> orderBy)
{
    var stack = new Stack<T>();
    foreach (var item in items.OrderBy(orderBy))
        stack.Push(item);

    while (stack.Count > 0)
    {
        var current = stack.Pop();
        yield return current;

        var children = nested(current).OrderBy(orderBy);
        if (children == null) continue;

        foreach (var child in children)
            stack.Push(child);
    }
}

और एक नमूना उपयोग:

var flattened = doc.TraverseAndFlatten(x => x.DependentDocuments, y => y.Document.DocDated).ToList();

0

नीचे इवान स्टोव के कोड के साथ पथ में प्रत्येक वस्तु के सूचकांक को बताने की एडिटोनल विशेषता है। "Item_120" की खोज करें:

Item_0--Item_00
        Item_01

Item_1--Item_10
        Item_11
        Item_12--Item_120

आइटम और एक int सरणी लौटाएगा [1,2,0]। जाहिर है, घोंसले का स्तर भी सरणी की लंबाई के रूप में उपलब्ध है।

public static IEnumerable<(T, int[])> Expand<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getChildren) {
    var stack = new Stack<IEnumerator<T>>();
    var e = source.GetEnumerator();
    List<int> indexes = new List<int>() { -1 };
    try {
        while (true) {
            while (e.MoveNext()) {
                var item = e.Current;
                indexes[stack.Count]++;
                yield return (item, indexes.Take(stack.Count + 1).ToArray());
                var elements = getChildren(item);
                if (elements == null) continue;
                stack.Push(e);
                e = elements.GetEnumerator();
                if (indexes.Count == stack.Count)
                    indexes.Add(-1);
                }
            if (stack.Count == 0) break;
            e.Dispose();
            indexes[stack.Count] = -1;
            e = stack.Pop();
        }
    } finally {
        e.Dispose();
        while (stack.Count != 0) stack.Pop().Dispose();
    }
}

नमस्ते, @ लिस्ज़, आप इस कोड को कहाँ चिपकाएँगे? मुझे इस आइटम के लिए "संशोधक 'जनता' मान्य नहीं है", "संशोधक 'स्थिर' इस आइटम के लिए मान्य नहीं है"
Kynao

0

यहाँ कुछ कतार का उपयोग करके उपयोग करने के लिए तैयार हैं और पहले मुझे और फिर मेरे बच्चों को फ्लेटेन पेड़ लौटा रहे हैं।

public static IEnumerable<T> Flatten<T>(this IEnumerable<T> items, 
    Func<T,IEnumerable<T>> getChildren)
    {
        if (items == null)
            yield break;

        var queue = new Queue<T>();

        foreach (var item in items) {
            if (item == null)
                continue;

            queue.Enqueue(item);

            while (queue.Count > 0) {
                var current = queue.Dequeue();
                yield return current;

                if (current == null)
                    continue;

                var children = getChildren(current);
                if (children == null)
                    continue;

                foreach (var child in children)
                    queue.Enqueue(child);
            }
        }

    }

0

हर एक बार थोड़ी देर में मैं इस समस्या को दूर करने की कोशिश करता हूं और अपने स्वयं के समाधान को तैयार करता हूं जो मनमाने ढंग से गहरी संरचनाओं (कोई पुनरावृत्ति) का समर्थन करता है, चौड़ाई पहले ट्रैवर्सल का प्रदर्शन करता है, और कई LINQ प्रश्नों का दुरुपयोग नहीं करता है या बच्चों पर पूर्ववर्ती पुनरावृत्ति निष्पादित करता है। .NET स्रोत में चारों ओर खुदाई करने और कई समाधानों की कोशिश करने के बाद , मैं अंत में इस समाधान के साथ आया हूं। यह इयान स्टोव के उत्तर (जिसका उत्तर मैंने अभी-अभी देखा है) के बहुत करीब होने के बावजूद समाप्त हो गया, हालाँकि मेरा अनंत लूप का उपयोग नहीं करता है या असामान्य कोड प्रवाह है।

public static IEnumerable<T> Traverse<T>(
    this IEnumerable<T> source,
    Func<T, IEnumerable<T>> fnRecurse)
{
    if (source != null)
    {
        Stack<IEnumerator<T>> enumerators = new Stack<IEnumerator<T>>();
        try
        {
            enumerators.Push(source.GetEnumerator());
            while (enumerators.Count > 0)
            {
                var top = enumerators.Peek();
                while (top.MoveNext())
                {
                    yield return top.Current;

                    var children = fnRecurse(top.Current);
                    if (children != null)
                    {
                        top = children.GetEnumerator();
                        enumerators.Push(top);
                    }
                }

                enumerators.Pop().Dispose();
            }
        }
        finally
        {
            while (enumerators.Count > 0)
                enumerators.Pop().Dispose();
        }
    }
}

एक काम करने वाला उदाहरण यहां पाया जा सकता है

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