Home > front end >  GroupBy with multiple groups level
GroupBy with multiple groups level

Time:04-02

I am using GroupBy create a hierarchical set of groups to use in multiple child grids.

products.json

[
    {
        "group": "coloris", // LEVEL 1
        "valeursAndUids": [
            {
                "Item1": "Beige",
                "Item2": "QB32-20220325-486274"
            },
            {
                "Item1": "Beige",
                "Item2": "QB32-20220325-106045"
            },
            {
                "Item1": "Venezia",
                "Item2": "QB32-20220325-205994"
            },
            {
                "Item1": "Venezia",
                "Item2": "QB32-20220325-270903"
            }
        ]
    },
    {
        "group": "ref_commercial", // LEVEL 2
        "valeursAndUids": [
            {
                "Item1": "29245",
                "Item2": "QB32-20220325-486274"
            },
            {
                "Item1": "29245",
                "Item2": "QB32-20220325-106045"
            },
            {
                "Item1": "29245",
                "Item2": "QB32-20220325-205994"
            },
            {
                "Item1": "29245",
                "Item2": "QB32-20220325-270903"
            }
        ]
    },
    {
        "group": "Address", // LEVEL 3
        "valeursAndUids": [
            {
                "Item1": "172 B",
                "Item2": "QB32-20220325-486274"
            },
            {
                "Item1": "172 B",
                "Item2": "QB32-20220325-106045"
            },
            {
                "Item1": "1725 A",
                "Item2": "QB32-20220325-205994"
            },
            {
                "Item1": "1725 A",
                "Item2": "QB32-20220325-270903"
            }
        ]
    }
    //....
]

How do I get a grouping of products that are grouped by group value?

Basically I want it to output like so:

coloris Beige // Level 1
    ref_commercial 29245 // Level 2
        Address 172B  // Level 3
            QB32-20220325-486274
            QB32-20220325-106045
            ...
coloris Venezia
    ref_commercial 29245
        Address 1725 A
          QB32-20220325-205994
          QB32-20220325-270903
          ...
coloris N
    ref_commercial N
        Address N
             // Level N
                ...

This way I regrouped all the values by group/sub groups by respecting the level ( the group is already sorted so) I can have many levels, but always I want to depend on the group value. Is there a way to do it?

CodePudding user response:

I believe you want to get the key and group name of the group, the iterate over the subgroups. Something like this should work:

public void GenerateGroups(List<Product> myList)
{
    var groups = myList.GroupBy(g => g.Group);

    foreach (var group in groups)
    { 
        var groupName = group.Key;
        var groupValue = group.First().Group;

        foreach (var group2 in group)
        {

        }
    }
}

I setup a simple couple of classes to illustrate:

internal class Product
{
    public string? Group { get; set; }
    public List<ValeursAndUids>? ValeursAndUids { get; set; }
}

internal class ValeursAndUids
{
    public string? Item1 { get; set; }
    public string? Item2 { get; set; }
}

CodePudding user response:

I have not come up with anything better than building a tree and printing it using the visitor pattern.

First of all, we need to build a flat list of products:

// just collect all groups into a single list
// it will produce the list: ["coloris", "ref_commercial", "Address", ""]
List<string> groups = listProducts.Select(p => p.Group).ToList();
groups.Add(""); // group for uids

// build flat list of products
// each product will be represented as a list of group values:
// [ ["Beige", "29245", "172 B", "QB32-20220325-486274"], ["Beige", "29245", "172 B", "QB32-20220325-106045"],... ]
IEnumerable<List<string>> products = listProducts
    .SelectMany(product => product.ValeursAndUids, (product, valueAndUid) => new { product.Group, valueAndUid })
    .GroupBy(item => item.valueAndUid.Item2)
    .Select(g =>
    {
        List<string> path = g.Select(item => item.valueAndUid.Item1).ToList();
        path.Add(g.Key);
        return path;
    });

Now we can build a tree:

TreeNode root = new TreeNode("root", "");
TreeNode parent = root;
foreach (List<string> path in products)
{
    TreeNode current = parent;

    for (int i = 0; i < path.Count; i  )
    {
        string part = path[i];
        parent = parent.GetChild(groups[i], part);
    }

    parent = current;
}

and print it:

root.Accept(new DepthTreeVisitor());

Output:

root
  coloris Beige
    ref_commercial 29245
      Address 172 B
         QB32-20220325-486274
         QB32-20220325-106045
  coloris Venezia
    ref_commercial 29245
      Address 1725 A
         QB32-20220325-205994
         QB32-20220325-270903

TreeNode and Visitor definitions:

public class TreeNode {

    public TreeNode(string groupName, string value)
    {
        GroupName = groupName;
        Value = value;
        Children = new HashSet<TreeNode>();
    }

    public string Value { get; }
    public string GroupName { get; }
    public ISet<TreeNode> Children { get; }

    public void Accept(Visitor visitor)
    {
        visitor.VisitTree(this);
    }

    public TreeNode GetChild(string group, string value)
    {
        foreach (TreeNode child in Children)
        {
            if (child.Value.Equals(value))
            {
                return child;
            }
        }

        return GetChild(new TreeNode(group, value));
    }

    private TreeNode GetChild(TreeNode child)
    {
        Children.Add(child);
        return child;
    }
}

public interface Visitor
{
    public void VisitTree(TreeNode tree);
}

public class DepthTreeVisitor : Visitor
{
    private int level = 0;

    public void VisitTree(TreeNode tree)
    {
        string indentString = new string(' ', 2 * level);
        Console.WriteLine($"{indentString}{tree.GroupName} {tree.Value}");
        level  ;

        foreach (TreeNode child in tree.Children)
        {
            child.Accept(this);
        }

        level--;
    }
}

Here is complete demo.

  • Related