具有多个组级别的 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
TreeNode
和 Visitor
定义:
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" }
}
基于这些分组,可以构建一棵树(其中每个生成的分组都是一个分支),或者简单地直观地显示嵌套分组。
无论哪种方式,我们首先需要计算结果分组。我的方法使用以下逻辑:
- 展平每个
group
/Item1
/Item2
值组合 --> ( group, valeur, uid )
- 按
uid
对展平项目进行分组
- 对于组中的每个项目,仅保留
group
和 valeur
值
- 通过匹配
group/valeur
个项目集合对 items-by-uid 分组进行分组
- 从每个嵌套组中,select 匹配的
group/valeur
项集合作为结果分组的 Levels
,uid
的集合作为结果分组的 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.
我正在使用 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
TreeNode
和 Visitor
定义:
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" }
}
基于这些分组,可以构建一棵树(其中每个生成的分组都是一个分支),或者简单地直观地显示嵌套分组。
无论哪种方式,我们首先需要计算结果分组。我的方法使用以下逻辑:
- 展平每个
group
/Item1
/Item2
值组合 -->( group, valeur, uid )
- 按
uid
对展平项目进行分组- 对于组中的每个项目,仅保留
group
和valeur
值
- 对于组中的每个项目,仅保留
- 通过匹配
group/valeur
个项目集合对 items-by-uid 分组进行分组 - 从每个嵌套组中,select 匹配的
group/valeur
项集合作为结果分组的Levels
,uid
的集合作为结果分组的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.