Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ: Can we create a flat list from hierarchy

Tags:

linq

xslt

OK... the title is correct...

I dont want hierarchy from a flat list but exact reverse

I have Folder class which has list of Folders in it held by property Children. So this is a typical hierarchy model.

I now want to flatten this list out... it will be an pre-order traversal i.e.

Assume

   A
   - A.1
   ----- A.1.1
   ----- A.1.2
   - A.2
   ----- A.2.1
   - A.3
   B
   - B.1
   - B.2
   ----- B.2.1
   ----- B.2.2
   ----------- B.2.2.1
   ----------- B.2.2.2 

From this hierarchy, the flat list I am expecting is exactly the order in which it is appearing above!

If LINQ cant do this then can an XSLT make it flat into a list of xml-elements?

like image 461
WPF-it Avatar asked Oct 14 '11 15:10

WPF-it


2 Answers

If LINQ cant do this then can an XSLT make it flat into a list of xml-elements?

Several people have shown how to do this with LINQ.

Here is a short and simple XSLT solution that transforms an XML representation of the provided list of nested items into a flat ordered list of items:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
  <xsl:apply-templates select="*[1]"/>
 </xsl:template>

 <xsl:template match="*/*">
   <xsl:copy/>
   <xsl:apply-templates select="*[1]"/>
   <xsl:apply-templates select="following-sibling::*[1]"/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the XML-representation of your provided input:

<t>
    <A>
        <A.1>
            <A.1.1/>
            <A.1.2/>
        </A.1>
        <A.2>
            <A.2.1/>
        </A.2>
        <A.3/>
    </A>
    <B>
        <B.1/>
        <B.2>
            <B.2.1/>
            <B.2.2>
                <B.2.2.1/>
                <B.2.2.2/>
            </B.2.2>
        </B.2>
    </B>
</t>

The wanted, correctly ordered flat sequence is produced:

<A/>
<A.1/>
<A.1.1/>
<A.1.2/>
<A.2/>
<A.2.1/>
<A.3/>
<B/>
<B.1/>
<B.2/>
<B.2.1/>
<B.2.2/>
<B.2.2.1/>
<B.2.2.2/>

UPDATE: Here is a non-recursive and even simpler XSLT solution (thanks to Andrew Welch for reminding me about this):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:for-each select="//*">
   <xsl:copy/>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

This solution works without problem in cases where a recursive solution ends up with real stack overflow.

like image 79
Dimitre Novatchev Avatar answered Nov 15 '22 11:11

Dimitre Novatchev


EDIT: Now that we've got a bit more context, it sounds like you've actually got XML to start with. However, we still don't know what processing you're performing on the elements. XSLT may be the right approach, but another would be to use LINQ to XML and its Descendants method:

var doc = XDocument.Load(stream);
var descendants = doc.Descendants("Folder");
// Use descendants now

That may end up being even simpler than the XSLT approach. For example, if you want to transform it into a List<string> by taking an attribute from each element:

var doc = XDocument.Load(stream);
var names = doc.Descendants("Folder")
               .Select(x => (strong) x.Attribute("name"))
               .ToList();   

One downside is that this will still load the whole XML file into memory as XElement (etc) objects. It's entirely possible that the XSLT version can handle this in a streaming fashion with more efficient memory usage. Dimitre can no doubt give more information if this is relevant.


There's nothing in LINQ to flatten multiple levels of hierarchy without performing recursion yourself. SelectMany performs one level of flattening, but you'd need to recurse to flatten your multi-level hierarchy to a single list.

Now if you're using LINQ to XML, that does support it very easily - you can just use the Descendants method:

var allFolders = root.Descendants("Folder");

To write something similar for your domain class though, you'd need to write more code. If you can give more information about what you've really got (XML or domain classes) we may be able to help you more.

EDIT: Okay, it sounds like XML is a red herring here. But finding all the descendants is pretty easy. You can do it using iterator blocks, but that becomes pretty unpleasantly inefficient fairly quickly. Here's another simple alternative:

public IList<Folder> SelfAndDescendants()
{
    List<Folder> ret = new List<Folder>();
    AddSelfAndDescendants(ret);
    return ret;
}

private void AddSelfAndDescendants(IList<Folder> list)
{
    list.Add(this);
    foreach (var child in children)
    {
        AddSelfAndDescendants(list);
    }
}

You can tailor the exact algorithm based on the order in which you want to get the children back.

like image 39
Jon Skeet Avatar answered Nov 15 '22 10:11

Jon Skeet