具有多个组级别的 GroupBy

GroupBy with multiple groups level

我正在使用 GroupBy 创建一组分层组以在多个子网格中使用。

这是returnproducts.json

的函数
var result = lProd.SelectMany(x => x.Caracteristiques.Distinct(), (parentObj, childnum) =>
                                                 new
                                                 {
                                                     parentObj,
                                                     childnum
                                                 })
                    .GroupBy(x => x.childnum.nom)
                    .Select(x => new
                    {
                        group = x.Key,
                        valeursAndUids = x.Select(z => (z.childnum.valeur, z.parentObj.ProductUid)).OrderBy(q => q.valeur)
                    }).Where(sid => OrdredList.Any(si => si == sid.group))
                    .OrderBy(x => OrdredList.IndexOf(x.group));

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"
            }
        ]
    }
    //....
]

如何获得一组按 group value 分组的产品?

基本上我希望它像这样输出:

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

通过这种方式,我通过 group/sub 组根据级别重新分组所有值(该组已经排序) 我可以有很多级别,但我总是想依赖组值。 有办法吗?

我相信您想获取组的键和组名,然后遍历子组。这样的事情应该有效:

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

        }
    }
}

我设置了几个简单的类来说明:

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

我没有想出比构建树并使用 visitor 模式打印它更好的方法。

首先,我们需要建立一个扁平的产品列表:

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

现在我们可以建一棵树了:

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

并打印出来:

root.Accept(new DepthTreeVisitor());

输出:

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

TreeNodeVisitor 定义:

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

至此完成demo.

我将从定义结果分组的简单表示开始。我能想到的最简单的是:

// Resulting grouping
{
    Levels: { <level1>, <level2>, ... },
    Uids:   { <uidA>, <uidB>, ... }
}

根据您的示例输出,生成的分组可以表示如下:

// Resulting example groupings
{
    Levels: { ( "coloris", "Beige" ), ( "ref_commercial", "29245" ), ( "Address", "172 B" ) }
    Uids:   { "QB32-20220325-486274", "QB32-20220325-106045" }
},
{
    Levels: { ( "coloris", "Venezia" ), ( "ref_commercial", "29245" ), ( "Address", "1725 A" ) }
    Uids:   { "QB32-20220325-205994", "QB32-20220325-270903" }
}

基于这些分组,可以构建一棵树(其中每个生成的分组都是一个分支),或者简单地直观地显示嵌套分组。

无论哪种方式,我们首先需要计算结果分组。我的方法使用以下逻辑:

  1. 展平每个 group/Item1/Item2 值组合 --> ( group, valeur, uid )
  2. uid 对展平项目进行分组
    • 对于组中的每个项目,仅保留 groupvaleur
  3. 通过匹配 group/valeur 个项目集合对 items-by-uid 分组进行分组
  4. 从每个嵌套组中,select 匹配的 group/valeur 项集合作为结果分组的 Levelsuid 的集合作为结果分组的 Uids.

假设 Product class 如下所示:

public class Product
{
    public string Group { get; set; }
    public (string Item1, string Item2)[] ValeursAndUids { get; set; }
}

,实现可以是:

Product[] products = new[] { ... };

List<((string GroupName, string Valeur)[] Levels, string[] Uids)> groupings = products
    // Step 1:
    .SelectMany(p => p.ValeursAndUids
        .Select(vu => ( GroupName: p.Group, Valeur: vu.Item1, Uid: vu.Item2)))
    // Step 2:
    .GroupBy(item => item.Uid,
        item => ( item.GroupName, item.Valeur) )
    // Step 3:
    .GroupBy(itemsByUid => string.Join(string.Empty, itemsByUid.Select(_ => _)),
         // Step 4:
         (_, nestedGroup) => (
             Levels: nestedGroup.First().Select(level => level).ToArray(),
             Uids: nestedGroup.Select(itemsByUid => itemsByUid.Key).ToArray()
         ))
    .ToList();

鉴于您的示例输入,这几乎是执行每个步骤后的样子:

步骤 1 - 展平

( "coloris", "Beige", "QB32-20220325-486274" )
( "coloris", "Beige", "QB32-20220325-106045" )
( "coloris", "Venezia", "QB32-20220325-205994" )
( "coloris", "Venezia", "QB32-20220325-270903" )
( "ref_commercial", "29245", "QB32-20220325-486274" )
( "ref_commercial", "29245", "QB32-20220325-106045" )
( "ref_commercial", "29245", "QB32-20220325-205994" )
( "ref_commercial", "29245", "QB32-20220325-270903" )
( "Address", "172 B", "QB32-20220325-486274" )
( "Address", "172 B", "QB32-20220325-106045" )
( "Address", "1725 A", "QB32-20220325-205994" )
( "Address", "1725 A", "QB32-20220325-270903" )

步骤 2 - 按 uid 分组; select每个项目仅 group/valeur 个值

"QB32-20220325-486274"
    ( "coloris", "Beige" )
    ( "ref_commercial", "29245" )
    ( "Address", "172B" )

"QB32-20220325-106045"
    ( "coloris", "Beige" )
    ( "ref_commercial", "29245" )
    ( "Address", "172B" )

"QB32-20220325-205994"
    ( "coloris", "Venezia" )
    ( "ref_commercial", "29245" )
    ( "Address", "1725 A" )

"QB32-20220325-270903"
    ( "coloris", "Venezia" )
    ( "ref_commercial", "29245" )
    ( "Address", "1725 A" )

第 3 步 - 通过匹配 group/valeur 个项目集合对组进行分组

"(coloris, Beige)(ref_commercial, 29245)(Address, 172 B)"
    "QB32-20220325-486274"
        ("coloris", "Beige")
        ("ref_commercial", "29245")
        ("Address", "172 B")
    "QB32-20220325-106045"
        ("coloris", "Beige")
        ("ref_commercial", "29245")
        ("Address", "172 B")
"(coloris, Venezia)(ref_commercial, 29245)(Address, 1725 A)"
    "QB32-20220325-205994"
        ("coloris", "Venezia")
        ("ref_commercial", "29245")
        ("Address", "1725 A")
    "QB32-20220325-270903"
        ("coloris", "Venezia")
        ("ref_commercial", "29245")
        ("Address", "1725 A")

步骤 4 - select为每个嵌套组 select 收集适当的 group/valeur 集合和 uid 集合

{
    Levels: { ( "coloris", "Beige" ), ( "ref_commercial", "29245" ), ( "Address", "172 B" ) }
    Uids:   { "QB32-20220325-486274", "QB32-20220325-106045" }
},
{
    Levels: { ( "coloris", "Venezia" ), ( "ref_commercial", "29245" ), ( "Address", "1725 A" ) }
    Uids:   { "QB32-20220325-205994", "QB32-20220325-270903" }
}

然后嵌套分组可以通过例如可视化。以下 level-wise 打印:

const string tab = "\t";
string tabs;

foreach (var group in grouped)
{
    tabs = string.Empty;

    foreach (var level in group.Levels)
    {
        Console.WriteLine($"{tabs}{level.GroupName} {level.Valeur}");

        tabs += tab;
    }

    foreach (var uid in group.Uids)
    {
        Console.WriteLine($"{tabs}{uid}");
    }
}

,或嵌套到树结构中(此答案中未显示)。

示例 fiddle here.