C# 使用LINQ构建字典
假设我们有一个变量“data”,它是Id和子Id的列表:C# 使用LINQ构建字典,c#,.net,linq,dictionary,.net-core,C#,.net,Linq,Dictionary,.net Core,假设我们有一个变量“data”,它是Id和子Id的列表: var data = new List<Data> { new() { Id = 1, ChildIds = new List<int> {123, 234, 345} }, new() { Id = 1, ChildIds = new List<int> {123, 234, 345} },
var data = new List<Data>
{
new()
{
Id = 1,
ChildIds = new List<int> {123, 234, 345}
},
new()
{
Id = 1,
ChildIds = new List<int> {123, 234, 345}
},
new()
{
Id = 2,
ChildIds = new List<int> {678, 789}
},
};
我想要一本有ChildId和相关Id的字典。如果字典中已存在ChildId,则应使用新Id覆盖
目前我有以下代码:
var dict = new Dictionary<int, int>();
foreach (var dataItem in data)
{
foreach (var child in dataItem.ChildIds)
{
dict[child] = dataItem.Id;
}
}
这很好,但我不喜欢使用两个循环。我更喜欢使用Linq ToDictionary以功能性的方式构建字典。
使用Linq建立字典的最佳方法是什么
为什么??我更喜欢函数式代码而不是变异状态。除此之外,我只是好奇如何使用Linq建立字典- 使用LINQ,您可以实现如下结果: Dictionary dict=来自数据中的项 从item.childId中的childId 选择新{item.Id,childId} 不同的 .ToDictionarykv=>kv.childId,kv=>kv.Id; 更新: 与foreach循环完全兼容的版本将使用group by with Last,而不是Distict: Dictionary dict2=来自数据中的项 从item.childId中的childId 按childId将新的{item.Id,childId}分组到g中 选择g.Last .ToDictionarykv=>kv.childId,kv=>kv.Id;
正如一些人已经指出的,依赖于输入元素的顺序感觉不起作用。LINQ表达式比原始的foreach循环更加复杂。使用LINQ,您可以获得如下结果: Dictionary dict=来自数据中的项 从item.childId中的childId 选择新{item.Id,childId} 不同的 .ToDictionarykv=>kv.childId,kv=>kv.Id; 更新: 与foreach循环完全兼容的版本将使用group by with Last,而不是Distict: Dictionary dict2=来自数据中的项 从item.childId中的childId 按childId将新的{item.Id,childId}分组到g中 选择g.Last .ToDictionarykv=>kv.childId,kv=>kv.Id;
正如一些人已经指出的,依赖于输入元素的顺序感觉不起作用。LINQ表达式比原始的foreach循环更加复杂。方法是创建Id和ChildId的每个组合的对,并构建这些组合的字典:
var list = new List<(int Id, int[] ChildIds)>()
{
(1, new []{10, 11}),
(2, new []{11, 12})
};
var result = list
.SelectMany(pair => pair.ChildIds.Select(childId => (childId, pair.Id)))
.ToDictionary(p => p.childId, p => p.Id);
该方法是为Id和ChildId的每个组合创建一对,并构建一个字典:
var list = new List<(int Id, int[] ChildIds)>()
{
(1, new []{10, 11}),
(2, new []{11, 12})
};
var result = list
.SelectMany(pair => pair.ChildIds.Select(childId => (childId, pair.Id)))
.ToDictionary(p => p.childId, p => p.Id);
SelectMany linq运算符实际上有一些不太为人所知的重载。其中一个有一个结果收集器,它是您的场景的完美用例 下面是将其转换为字典的示例代码段。请注意,我必须使用Distinct,因为您有两个值为1的id,其中有一些重复的子id,这会给字典带来问题
void Main()
{
// Get the data
var list = GetData();
// Turn it into a dictionary
var dict = list
.SelectMany(d => d.ChildIds, (data, childId) => new {data.Id, childId})
.Distinct()
.ToDictionary(x => x.childId, x => x.Id);
// show the content of the dictionary
dict.Keys
.ToList()
.ForEach(k => Console.WriteLine($"{k} {dict[k]}"));
}
public List<Data> GetData()
{
return
new List<Data>
{
new Data
{
Id = 1,
ChildIds = new List<int> {123, 234, 345}
},
new Data
{
Id = 1,
ChildIds = new List<int> {123, 234, 345}
},
new Data
{
Id = 2,
ChildIds = new List<int> {678, 789}
},
};
}
public class Data
{
public int Id { get; set; }
public List<int> ChildIds { get; set; }
}
SelectMany linq运算符实际上有一些不太为人所知的重载。其中一个有一个结果收集器,它是您的场景的完美用例 下面是将其转换为字典的示例代码段。请注意,我必须使用Distinct,因为您有两个值为1的id,其中有一些重复的子id,这会给字典带来问题
void Main()
{
// Get the data
var list = GetData();
// Turn it into a dictionary
var dict = list
.SelectMany(d => d.ChildIds, (data, childId) => new {data.Id, childId})
.Distinct()
.ToDictionary(x => x.childId, x => x.Id);
// show the content of the dictionary
dict.Keys
.ToList()
.ForEach(k => Console.WriteLine($"{k} {dict[k]}"));
}
public List<Data> GetData()
{
return
new List<Data>
{
new Data
{
Id = 1,
ChildIds = new List<int> {123, 234, 345}
},
new Data
{
Id = 1,
ChildIds = new List<int> {123, 234, 345}
},
new Data
{
Id = 2,
ChildIds = new List<int> {678, 789}
},
};
}
public class Data
{
public int Id { get; set; }
public List<int> ChildIds { get; set; }
}
有一个SelectMany的集合,它不仅可以使集合变得平坦,还可以让您获得任何形式的结果
var all = data.SelectMany(
data => data.ChildIds, //collectionSelector
(data, ChildId) => new { data.Id, ChildId } //resultSelector
);
现在,如果要将所有内容转换为字典,必须首先删除重复的childId。您可以使用如下所示的GroupBy,然后从每个组中选择最后一项,正如您在问题中所述,您希望在执行时覆盖ID。字典的键也应该是unique=ChildId:
或者,您可以编写一个实现了IEquatable的新类,并将其用作resultSelector的返回类型,而不是new{data.Id,ChildId}。然后写入all.Reverse.Distinct.ToDictionaryx=>x.ChildId;因此,它将根据您自己的Equals方法实现来检测重复项。相反,因为您说您想要最后一次出现的重复项。有一个SelectMany的集合,它不仅使集合平坦,而且允许您获得任何形式的结果
var all = data.SelectMany(
data => data.ChildIds, //collectionSelector
(data, ChildId) => new { data.Id, ChildId } //resultSelector
);
现在,如果要将所有内容转换为字典,必须首先删除重复的childId。您可以使用如下所示的GroupBy,然后从每个组中选择最后一项,正如您在问题中所述,您希望在执行时覆盖ID。字典的键也应该是unique=ChildId:
或者,您可以编写一个实现了IEquatable的新类,并将其用作resultSelector的返回类型,而不是new{data.Id,ChildId}。然后写入all.Reverse.Distinct.ToDictionaryx=>x.ChildId;因此,它将根据您自己的Equals方法实现来检测重复项。反向,bec
因为您说过希望最后一次出现重复项。在这种情况下,您的foreach appproach既可读又高效。所以即使我是LINQ的粉丝,我也会用它。循环的好处是,您可以轻松调试它,或者在必要时添加日志记录,例如无效id 但是,如果您想使用LINQ,我可能会使用and。前者用于展平子集合(如ChildId),后者用于创建与词典非常相似的集合。但一个区别是,它允许重复键,在这种情况下,您可以获得多个值:
ILookup<int, int> idLookup = data
.SelectMany(d => d.ChildIds.Select(c => (Id:d.Id, ChildId:c)))
.ToLookup(x => x.ChildId, x => x.Id);
现在您已经拥有了所需的一切,因为它可以像字典一样使用,具有相同的查找性能。如果仍要创建该词典,可以使用:
Dictionary<int, int> dict = idLookup.ToDictionary(x => x.Key, x => x.First());
如前所述,如果要用新Id覆盖重复项,只需使用Last
.NET FIDLE:在这种情况下,您的foreach appproach既可读又高效。所以即使我是LINQ的粉丝,我也会用它。循环的好处是,您可以轻松调试它,或者在必要时添加日志记录,例如无效id 但是,如果您想使用LINQ,我可能会使用and。前者用于展平子集合(如ChildId),后者用于创建与词典非常相似的集合。但一个区别是,它允许重复键,在这种情况下,您可以获得多个值:
ILookup<int, int> idLookup = data
.SelectMany(d => d.ChildIds.Select(c => (Id:d.Id, ChildId:c)))
.ToLookup(x => x.ChildId, x => x.Id);
现在您已经拥有了所需的一切,因为它可以像字典一样使用,具有相同的查找性能。如果仍要创建该词典,可以使用:
Dictionary<int, int> dict = idLookup.ToDictionary(x => x.Key, x => x.First());
如前所述,如果要用新Id覆盖重复项,只需使用Last
.NET Fiddle:您正在寻找SelectMany吗?这很好,但我不喜欢使用两个循环。我更喜欢使用Linq ToDictionary以功能性的方式构建字典为什么,你的方法有什么问题?您将如何使用这些数据?您是否尝试过Linq,如果没有,请尝试一下,如果您有任何具体问题,请告诉我们。如果你有,发布相关的Linq,我们可以进一步提供帮助。你的方法很好,Linq唯一能做的就是把你已经在做的事情抽象出来。除了额外的错误更正之外,我认为您的实现没有任何问题。这段代码可以工作,但我更喜欢函数代码,而不是改变状态。除此之外,我只是好奇如何使用Linq建立字典-@zaggler完全一样,但是我喜欢Linq的简洁性和可读性。你在找SelectMany吗?这很好,但我不喜欢使用两个循环。我更喜欢使用Linq ToDictionary以功能性的方式构建字典为什么,你的方法有什么问题?您将如何使用这些数据?您是否尝试过Linq,如果没有,请尝试一下,如果您有任何具体问题,请告诉我们。如果你有,发布相关的Linq,我们可以进一步提供帮助。你的方法很好,Linq唯一能做的就是把你已经在做的事情抽象出来。除了额外的错误更正之外,我认为您的实现没有任何问题。这段代码可以工作,但我更喜欢函数代码,而不是改变状态。除此之外,我只是好奇如何使用Linq建立字典-@zaggler完全一样,但是我喜欢Linq的简洁性和可读性。感谢您的解决方案,尽管我得到了以下异常:System.ArgumentException:“已经添加了具有相同密钥的项。如果字典中已经有子项,则需要覆盖,在构建字典之前,此LINQ可能应该先执行GroupBy,然后执行Last,以便从重复项中选择最后一项。@缺少Rogier Distinct,因为您有重复的数据。没错,Equals比较匿名类型中的值。但是,如果在dataYes中将678替换为123,代码仍会引发异常。感谢您的解决方案,尽管我遇到了以下异常:System.ArgumentException:'已添加具有相同键的项。如果字典中已有子项,则需要覆盖,在构建字典之前,此LINQ可能应该先执行GroupBy,然后执行Last,以便从重复项中选择最后一项。@缺少Rogier Distinct,因为您有重复的数据。没错,Equals比较匿名类型中的值。然而,如果您在数据中将678替换为123,您的代码仍然抛出异常。是的,您的第二个示例有效。这是一个极好的答案。我在SelectMany中没有注意到这个重载。如果使用稍微不同的数据集,这段代码会引发异常。把数据中的678替换为123,这是一个很好的答案。我在SelectMany中没有注意到这个重载。如果使用稍微不同的数据集,这段代码会引发异常。只需将数据中的678替换为123。这应标记为正确答案。所有其他答案要么都是错的,要么都是错的
od。这应标记为正确答案。所有其他答案要么是错误的,要么不是那么好。