使用List(T).GroupBy()对嵌套对象进行分组

时间:2009-06-23 16:15:03

标签: c# linq

我有一个奇怪的排序案例,我正在努力使用LINQs GroupBy方法。

我有两个类:Category和Item。每个项目都有一个类别,一个类别可以有一个父类别。我需要做的是按照正确的类别组织所有项目,但是如果有的话,我还想按父类别对类别进行排序。

理想情况下,我应该能够将结果可视化为:

<Category 1> 
  <Item 1>  
  <Item 2> 
</Category 1> 
<Category 2>
  <Category 3>  
    <Item 3>  
    <Item 4>  
  </Category 3>
</Category 2>  
<Category 4>  
  <Item 5>
</Category 4>
<Category 5>  
  <Item 6>
</Category 5>  

我目前正在使用items.GroupBy(x => x.Category),它除了父类别外,还提供了所有内容。所以我的结果如下:

<Category 1> 
    <Item 1>  
    <Item 2> 
</Category 1> 

    <Category 3>  
      <Item 3>  
      <Item 4>  
    </Category 3>

<Category 4>  
  <Item 5>
</Category 4> 
<Category 5>  
  <Item 6>
</Category 5>

问题在于(在此示例中)未列出类别3(类别2)的父类别。

我开始在嵌套组中闲逛,但在考虑自己手动走树之前我没有走得太远(foreach'ing)。在我这样做之前,我希望这里的LINQ大师可以帮助我......

5 个答案:

答案 0 :(得分:3)

那么,您希望获得哪种数据类型?目前,它将是IGrouping<Category, Item>,但如果您希望最上面的类别成为关键字,那么这些值可能是项目类别。

您已将结果显示为XML,但您实际上将如何使用它们?鉴于您已经获得的结果,您不能轻易获得父类别吗?您是否需要在实际分组部分中使用父类别?如果两个类别具有相同的父类别,您是否希望将该父类别中的所有项目混合在一起?

对不起所有问题 - 但我们知道的越多,我们就能越好地为您提供帮助。

编辑:如果您只想按最上面的类别对项目进行分组,则可以执行

items.GroupBy(x => GetTopmostCategory(x))
...
public Category GetTopmostCategory(Item item)
{
    Category category = item.Category;
    while (category.Parent != null)
    {
        category = category.Parent;
    }
    return category;
}

(您可以将其放入CategoryItem,这可能会给您完全相同的返回类型,但分组只会通过最顶层的类别。希望这实际上是你想要的......

答案 1 :(得分:1)

如果你有一棵树要走,你已经有了按类别分组的物品。你是否控制了视图的界面?

public abstract class TreeNode {
  private readonly int name;
  private Category c = null;

  public int Name { get { return name; } }
  public Category Parent { get { return c; } }
  public abstract string Tag { get; }

  public TreeNode(int n, Category c) {
    this.name = n;
    AssignCategory(c);
  }

  public void AssignCategory(Category c) {
    if (c != null) {
      this.c = c;
      c.AddChild(this);
    }
  }

  public virtual IList<TreeNode> Children {
    get { return null; }
  }
}

项目和类别看起来像

public class Item : TreeNode {
  public Item(int n, Category c) : base(n, c) {}

  public override string Tag { get { return "Item"; } }
}

public class Category : TreeNode {
  List<TreeNode> kids = new List<TreeNode>();

  public Category(int n, Category c) : base(n, c) {}

  public void AddChild(TreeNode child) {
    kids.Add(child);
  }

  public override string Tag { get { return "Category"; } }

  public override IList<TreeNode> Children {
    get { return kids; }
  }
}

然后你可以用一个陈旧的控制台显示器来显示它们:

public class CornyTextView {
  public int NodeDepth(TreeNode n) {
    if (n.Parent == null)
      return 0;
    else
      return 1 + NodeDepth(n.Parent);
  }

  public void Display(IEnumerable<TreeNode> nodes) {
    foreach (var n in nodes.OrderBy(n => n.Name)) {
      for (int i = 0; i < NodeDepth(n); i++)
        Console.Write("  ");

      Console.WriteLine("- " + n.Tag + " " + n.Name.ToString());
      if (n.Children != null)
        Display(n.Children);
    }
  }
}

所以为你的例子生成输出:

public static void Main() {
  var cats = new [] {
    new Category(1, null),
    new Category(2, null),
    new Category(3, null),
    new Category(4, null),
    new Category(5, null),
  };
  cats[2].AssignCategory(cats[1]);

  var items = new[] {
    new Item(6, cats[4]),
    new Item(5, cats[3]),
    new Item(3, cats[2]), new Item(4, cats[2]),
    new Item(1, cats[0]), new Item(2, cats[0]),
  };

  new CornyTextView()
        .Display(cats.Where(c => c.Parent == null)
                     .Select(c => c as TreeNode));
}

请注意,即使items被洗牌,输出也是

- Category 1
  - Item 1
  - Item 2
- Category 2
  - Category 3
    - Item 3
    - Item 4
- Category 4
  - Item 5
- Category 5
  - Item 6

答案 2 :(得分:0)

看看这个blog。我喜欢ByHierarchy扩展方法

答案 3 :(得分:0)

这就是我解决问题的方法:

var items = Items.GroupBy(x => x.Category).GroupBy(y => y.Key.Parent);

这让我可以做以下(令人讨厌的)渲染标记:

<table>
    <tr>
        <th>Item Description</th>
        <th>Value</th>
    </tr>
    <% foreach (var parentGroup in Model.Items) { %>
        <% if (parentGroup.Key != null) { %>

        <tr>
            <th colspan="2" style="background-color:#ff9900;"><%= parentGroup.Key.Label %></th>
        </tr>

            <% foreach (var childGroup in parentGroup) { %>
                <tr>
                    <th colspan="2"><%= childGroup.Key.Label %></th>
                </tr>
                <% foreach (var item in childGroup) { %>
                    <tr>
                        <td><%= item.Description %></td>
                        <td><%= String.Format("{0:c}", item.Value) %></td>
                    </tr>
                <% } %>
            <% } %>
        <% } else { %>
            <% foreach (var childGroup in parentGroup) { %>
                <tr>
                    <th colspan="2" style="background-color:#ff9900;"><%= childGroup.Key.Label %></th>
                </tr>
                <% foreach (var item in childGroup) { %>
                    <tr>
                        <td><%= item.Description %></td>
                        <td><%= String.Format("{0:c}", item.Value) %></td>
                    </tr>
                <% } %>
            <% } %>

        <% } %>
    <% } %>
</table>

我很想整合这个标签汤,但我现在必须要这样做。

答案 4 :(得分:0)

我感觉org.ccil.cowan.tagsoup从td标签中删除了样式属性。例如... Bill应用commandline.process后来自NetFlow Analyzer它只返回Bill From NetFlow Analyzer。