C# Linq 将外键与内键反转为以字典为值的字典

Posted

技术标签:

【中文标题】C# Linq 将外键与内键反转为以字典为值的字典【英文标题】:C# Linq revers outer-key with inner-key for a dictionary with dictionary as value 【发布时间】:2022-01-11 23:45:02 【问题描述】:

我正在尝试使用 Linq 对 Dictionary<string, Dictionary<string, List>> 类型的字典进行分组 以这样的方式使外键成为内键,反之亦然。

这是我得到的最接近的

       Dictionary<IEnumerable<string>, Dictionary<string, List>> reversed = 
          nodeTypedContainer
            .GroupBy(kv =>
            
                IEnumerable<string> keys = kv.Value.Keys.Select(x => x);

                return keys;
            , kv =>
            
                return new
                
                    Values = kv.Value.Values.SelectMany(x => x.ToList()),
                    Node = kv.Key
                ;
            )
            .ToDictionary(
            group =>
            
                return group.Key;
            ,
            group =>
            
                return group
                     .Select(x => (x.Containers, x.Node))
                     .GroupBy(x => x.Node, x => x.Containers)
                     .ToDictionary(x => x.Key, x => x.SelectMany(q => q));
            );

nodeTypedContainer 在哪里

Dictionary<string, Dictionary<string, IEnumerable<V1Container>>>

所以外键是一个 IEnumerable,这是有道理的,因为如果我最初有这样的字典

[
   
      key: node1,
      value: [
           key: k1
           value: [1, 2, 3]
         ,
           key: k2
           value: [2]
       ]
   ,
   
      key: node2,
      value: [
           key: k1
           value: [3]
      ]
   
]

应该倒过来

[
   
      key: k1,
      value: [
           key: node1
           value: [1, 2, 3]
      ,
           key: node2
           value: [3]
      ]
   ,
   
      key: k2,
      value: [
           key: node1
           value: [2]
      ]
   ,
]

【问题讨论】:

我喜欢没有任何解释的近距离投票。 您对那里的类型不是很清楚。 List 是什么? V1Container 是什么?您能否在问题中添加不带任何别名的源类型和目标类型? 你说外部键(我认为这是外部字典的键)是IEnumerable,但在输出示例中,外部key 属性只是k1k2是普通的strings 而不是IEnumerable&lt;string&gt;s。 另外,当您使用IEnumerable 作为字典键时,您期望会发生什么?比较键时,字典不会检查其中的项目。它只会比较IEnumerables 的内存地址,即使它们的内容相同,它们也很可能总是不同的。 【参考方案1】:

有趣的一个,我想.. 我会使用 SelectMany 将巢扩展到 key1, key2, value 然后聚合将其重新组合在一起,而不是 GroupBy/ToDictionary

var r = nodeTypedContainer
    .SelectMany(
        kvpO => kvpO.Value, 
        (kvpO, kvpI) => new  KeyO = kvpO.Key, KeyI = kvpI.Key, kvpI.Value 
    )
    .Aggregate(
        new Dictionary<string, Dictionary<string, List<int>>>(),
        (seed, val) => 
            if (seed.TryGetValue(val.KeyI, out var dictI))
                dictI.Add(val.KeyO, val.Value);
            else
                seed[val.KeyI] = new()   val.KeyO, val.Value  ;
            return seed;
        
    );

聚合和较小程度的 SelectMany,我认为并不经常使用,因此可能需要稍微解释一下。

带有一个参数的 SelectMany 非常简单:它将 T[][] 转换为 T[],因此一些嵌套的列表列表(就像一个人列表,每个人都有一个宠物列表)变成一个直接列表嵌套项目的个数(10 个人每人在一个列表中有 20 只宠物,成为 200 只宠物的 1 个列表)。

带有两个参数的SelectMany 允许我们访问原始人以及宠物列表,这意味着我们可以访问更高级别的巢以及较低的巢。这意味着我们可以列出 200 只宠物,每个人也重复 20 次

在这种情况下,它会转换数据:

[
   
      key: node1,
      value: [
           key: k1
           value: [1, 2, 3]
         ,
           key: k2
           value: [2]
       ]
   ,
   
      key: node2,
      value: [
           key: k1
           value: [3]
      ]
   
]

变成这样的:

 node1, k1, [1, 2, 3] 
 node1, k2, [2] 
 node2, k1, [3] 

现在没有层次结构;而是重复 node1。

然后我们使用 Aggregate 将其重新组合在一起

Aggregate 的第一个参数是我们将输出的新Dictionary&lt;string, Dictionary&lt;string, List&lt;int&gt;&gt;&gt;。它一开始是一个空字典,我们将在遍历每个未嵌套项时构建它。 Aggregate 的第二个参数应该是一些修改当前累加值并返回它的代码。我们实际上并不需要返回它的版本,因为我们总是修改我们创建的种子的内容,而不是拥有一个不可变的样式“接受当前迭代,准备基于它的新版本并返回它以供下次使用。”在某种程度上,它违背了“LINQ 不应该有副作用”,但 Aggregate 普遍接受它可以产生这种副作用,并且从某种意义上说,它是安全的,因为我们正在修改我们在种子中创建的实例。小心在除第一个参数之外的其他地方创建的可变对象上使用 Aggregate

所以第二个参数的 lambda 接收新的 Dict;它必须查找外部字典是否包含 inner 键(如 k1、k2)。如果 确实 那么它应该添加 outer 键和内部列表作为新条目。如果它没有,那么它应该创建一个新的innerDictionary&lt;string, List&gt;,用outer键和inner List

初始化

【讨论】:

【参考方案2】:

这是我能想到的最直接的方法:

Dictionary<string, Dictionary<string, List<int>>> reversed = 
(
    from kv1 in nodeTypedContainer
    from kv2 in kv1.Value
    select new  k1 = kv1.Key, k2 = kv2.Key, v = kv2.Value
)
    .ToLookup(x => x.k2)
    .ToDictionary(x => x.Key, x => x.ToDictionary(y => y.k1, y => y.v));

【讨论】:

以上是关于C# Linq 将外键与内键反转为以字典为值的字典的主要内容,如果未能解决你的问题,请参考以下文章

试图将外键与触发器结合起来

在火花中创建一个以字长为键、以排序字为值的字典?

将字典中具有相同值的所有键组合在一起,并将键与值交换,将值与键交换

C# - 字典 - 如何在字典中获取特定类值的最大值? [复制]

使用 LINQ 反转和展平字典

将字典的键与 List 的值进行比较,并返回所有匹配的值,包括重复值