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
属性只是k1
和k2
是普通的string
s 而不是IEnumerable<string>
s。
另外,当您使用IEnumerable
作为字典键时,您期望会发生什么?比较键时,字典不会检查其中的项目。它只会比较IEnumerable
s 的内存地址,即使它们的内容相同,它们也很可能总是不同的。
【参考方案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<string, Dictionary<string, List<int>>>
。它一开始是一个空字典,我们将在遍历每个未嵌套项时构建它。
Aggregate 的第二个参数应该是一些修改当前累加值并返回它的代码。我们实际上并不需要返回它的版本,因为我们总是修改我们创建的种子的内容,而不是拥有一个不可变的样式“接受当前迭代,准备基于它的新版本并返回它以供下次使用。”在某种程度上,它违背了“LINQ 不应该有副作用”,但 Aggregate 普遍接受它可以产生这种副作用,并且从某种意义上说,它是安全的,因为我们正在修改我们在种子中创建的实例。小心在除第一个参数之外的其他地方创建的可变对象上使用 Aggregate
所以第二个参数的 lambda 接收新的 DictDictionary<string, List>
,用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 将外键与内键反转为以字典为值的字典的主要内容,如果未能解决你的问题,请参考以下文章
将字典中具有相同值的所有键组合在一起,并将键与值交换,将值与键交换