Group nested objects using List (T) .GroupBy ()

I have a weird sorting case. I'm trying my best to use the LINQs GroupBy method.

I have two classes: Category and Subject. Each item has a category, and a Category can have a parent category. What I need to do is organize all the elements by their category, but I also want to sort the categories by parent category, if any.

Ideally, I should be able to visualize my results, for example:

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

I am currently using items.GroupBy(x => x.Category) , which gives me everything except the parent categories. So my results look like this:

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

The problem is that (in this example) the parent category for category 3 (category 2) is not specified.

I started working with nested groups, but I’m not very far off before considering only manual walking on a tree (foreach'ing). Before I do this, I hope LINQ gurus can help me ...

+4
source share
5 answers

Well, what type of data do you expect to receive? Currently it will be IGrouping<Category, Item> , but if you want the IGrouping<Category, Item> category to be the key, then the values ​​could be elements or categories.

You showed the results as XML, but how are you going to use them? Given the results, you can’t easily get the parent category anyway? Do I need to use the parent category in the actual group? If two categories have the same parent category, do you want all the elements in this parent category to be engraved together?

Sorry for all the questions - but the more we know, the better we can help you.

EDIT: If you want to group items by the very top category, you can do

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

(You can put this in a Category or Item , potentially.) This will give you exactly the same return type, but the grouping will only be through the topmost category. Hope this is actually what you want ...

+3
source

If you have a tree for walking, you already have items grouped into categories. Do you control the interface of your kind?

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

The item and category look like

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

Then you can show them, say, with a console display:

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

So, to generate the output for your example:

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

Please note that even if items shuffled, the output

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

Take a look at the blog . I like the ByHierarchy extension method

0
source

This is how I solved my problem:

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

This allowed me to do the following (disgusting) rendering of markup:

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

I would like to combine this soup with tags, but I have to go with this for now.

0
source

I have the feeling that org.ccil.cowan.tagsoup removes style attributes from the td tag. for example ... Bill From NetFlow Analyzer after using the commandline.process command returns only Bill From NetFlow Analyzer.

0
source

Source: https://habr.com/ru/post/1286635/


All Articles