LINQ:我们可以从层次结构创建一个平面列表

时间:2011-10-14 15:01:08

标签: linq xslt

好的......标题是正确的......

我不希望从平面列表中获得层次结构,但完全相反

我有Folder类,其中包含属性Children所包含的文件夹列表。所以这是典型的层次结构模型。

我现在想把这个列表弄平......它将是一个预先遍历的遍历,即

假设

   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 

从这个层次结构中,我期待的平面列表正好是它出现在上面的顺序!

如果LINQ无法执行此操作,那么XSLT可以将其整合到xml元素列表中吗?

7 个答案:

答案 0 :(得分:6)

  

如果LINQ不能这样做,那么XSLT可以将它变成一个列表   XML元素?

有些人已经展示了如何使用LINQ执行此操作。

这是一个简短的XSLT解决方案,它将提供的嵌套项列表的XML表示转换为平面有序的项列表:

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

将此转换应用于所提供输入的XML表示

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

生成所需的,正确排序的平面序列

<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/>

更新:这是一个非递归甚至更简单的XSLT解决方案(感谢Andrew Welch提醒我这一点):

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

在递归解决方案以 实际堆栈溢出结束的情况下,此解决方案可以正常运行。

答案 1 :(得分:3)

编辑:现在我们已经有了一个更多的上下文,听起来你实际上已经开始使用XML了。但是,我们仍然不知道您对元素执行了哪些处理。 XSLT 可能是正确的方法,但另一种方法是使用LINQ to XML及其Descendants方法:

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

可能最终比XSLT方法更简单。例如,如果您想通过从每个元素中获取属性将其转换为List<string>

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

一个缺点是,它仍然会将整个XML文件作为XElement(等)对象加载到内存中。 XSLT版本完全有可能以流式方式处理这一问题,并且内存使用效率更高。如果这是相关的,Dimitre无疑会提供更多信息。


LINQ中没有任何内容可以在不执行递归的情况下压缩多个级别的层次结构。 SelectMany执行一个级别的展平,但您需要递归以将多级层次结构展平为单个列表。

现在,如果您正在使用LINQ to XML, 非常容易支持它 - 您只需使用Descendants方法:

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

要为您的域类编写类似的内容,您需要编写更多代码。如果您可以提供有关您真正获得的内容的更多信息(XML或域类),我们可以为您提供更多帮助。

编辑:好的,听起来XML在这里是一个红色的鲱鱼。但找到所有的后代非常容易。你可以使用迭代器块来做它,但这很快就会变得非常令人不快。这是另一个简单的选择:

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

您可以根据您希望让孩子回来的顺序来定制精确的算法。

答案 2 :(得分:2)

您可以使用SelectMany来展平层次结构

http://msdn.microsoft.com/en-us/library/bb534336.aspx

答案 3 :(得分:1)

这是一个linq风格的扩展方法,可以满足您的要求(无递归,循环处理)。

    public static IEnumerable<T> WalkTreeDepthFirst<T>(this IEnumerable<T> source,
       Func<T, IEnumerable<T>> childFunction)
    {
        // http://en.wikipedia.org/wiki/Depth-first_search
        HashSet<T> seenIt = new HashSet<T>();
        Stack<T> toVisit = new Stack<T>();

        foreach (T item in source.Reverse())
        {
            toVisit.Push(item);
        }

        while (toVisit.Any())
        {
            T item = toVisit.Pop();
            if (!seenIt.Contains(item))
            {
                seenIt.Add(item);
                foreach (T child in childFunction(item).Reverse())
                {
                    toVisit.Push(child);
                }
                yield return item;
            }
        }
    }

答案 4 :(得分:1)

这是我第一次尝试:

    public static IEnumerable<Folder> SelfPlusChildren(Folder f)
    {
        return new[] {f}.Concat(f.Children.SelectMany(SelfPlusChildren));
    }

答案 5 :(得分:0)

您可以编写一个简单的扩展方法来执行此操作:

public static IEnumerable<Folder> GetFolders(this Folder rootFolder)
{
    yield return rootFolder;

    foreach (var child in rootFolder.Children)
        foreach(var folder in GetFolders(child))
            yield return folder;
}

使用SelectMany()缩短或缩短时间:

public static IEnumerable<Folder> GetFolders(this Folder rootFolder)
{
    yield return rootFolder;

    foreach (var folder in rootFolder.Children.SelectMany(GetFolders))
        yield return folder;
}

答案 6 :(得分:0)

.Net框架中没有标准实现,但您可以自己实现它。

以下是如何做到这一点:

public static IEnumerable<T> FlattenTree<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
    var state = new Stack<T>();
    state.Push(root);

    while (state.Count != 0)
    {
        T top = state.Pop();
        yield return top;

        IEnumerable<T> children = getChildren(top) ?? Enumerable.Empty<T>();
        foreach (T child in children.Reverse())
        {
            state.Push(child);
        }
    }
}