我有一个字典,其中有一个表示年份的整数Key和一个作为对象Channel
的列表的Value。我需要整理数据并从中创建一个新对象。
当前,我的代码如下:
Dictionary<int, List<Channel>> myDictionary;
foreach(var x in myDictionary)
{
var result = (from a in x.Value
from b in anotherList
where a.ChannelId == b.ChannelId
select new NewObject
{
NewObjectYear = x.Key,
NewObjectName = a.First().ChannelName,
}).ToList();
list.AddRange(result);
}
请注意,我正在使用Key
作为属性NewObjectYear
的值。
我想摆脱foreach,因为字典包含大量数据,并且在迭代内进行一些连接使其非常慢。因此,我决定进行重构并提出以下建议:
var flatten = myDictionary.SelectMany(x => x.Value.Select(y =>
new KeyValuePair<int, Channel>(x.Key, y))).ToList();
但是,有了这个,我无法直接获得Key
。使用flatten.Select(x => x.Key)
之类的方法绝对不是正确的方法。因此,我尝试寻找其他方式展平,这对我的情况很有利,但失败了。我还考虑过要创建一个包含年份和扁平化列表的类,但是我不知道如何做。
请帮助我。
此外,还有另一种不需要创建新类的方法吗?
答案 0 :(得分:1)
在我看来,您正在尝试仅进行过滤,因此不需要加入:
var anotherListIDs = new HashSet<int>(anotherList.Select(c => c.ChannelId));
foreach (var x in myDictionary)
{
list.AddRange(x.Value
.Where(c => anotherListIDs.Contains(c.ChannelId))
.Select(c => new NewObject
{
NewObjectYear = x.Key,
NewObjectName = c.First().ChannelName,
}));
}
答案 1 :(得分:0)
您确实意识到,如果特定字典元素中列表的第二个元素具有匹配的channelId,那么您返回此列表的第一个元素,不是吗?
var otherList = new OtherItem[]
{
new OtherItem() {ChannelId = 1, ...}
}
var dictionary = new Dictionary<int, List<Channel>[]
{
{ 10, // Key
new List<Channel>() // Value
{
new Channel() {ChannelId = 100, Name = "100"},
new Channel() {ChannelId = 1, Name = "1"},
},
};
尽管第2个元素具有匹配的ChannelId,但是您返回第一个元素的名称。
无论如何,让我们假设这是您真正想要的。是的,您的功能不是很有效。
您的字典实现IEnumerable<KeyValuePair<int, List<Channel>>
。因此,您的x
中的每个foreach
都是KeyValuePair<int, List<Channel>
。每个x.Value
都是List<Channel>
。
因此,对于字典中的每个元素(即KeyValuePair<int, List<Channel>
),您都要获取完整列表,并使用otherList
对完整列表进行完整的内部联接,并得出结果KeyValuePair
的键和KeyValuePair
中List的第一个元素。
由于FirstOrDefault()
或Take(3)
,尽管您可能不使用完整的结果,而仅使用前几个或前几个,但您还是要对字典中每个列表的每个元素都执行此操作
实际上,您的查询会更加高效。
当您在ChannelIds
中使用OtherList
只是为了发现它是否存在时,主要的改进之一就是将ChannelIds
的{{1}}转换为OtherList
,您可以在其中进行快速查找,以检查Dictionary中值之一的ChannelId是否在HashSet<int>
中。
因此,对于字典中的每个元素,您只需检查列表中的每个HashSet
即可查看其中是否包含在ChannelId
中。找到一个后,您就可以停止并仅返回List和Key的第一个元素。
我的解决方案是Dictionary>的扩展功能。参见Extension Methods Demystified
HashSet
这将调用其他ExtractNew对象:
public static IEnumerable<NewObject> ExtractNewObjects(this Dictionary<int, List<Channel>> dictionary,
IEnumerable<OtherItem> otherList)
{
// I'll only use the ChannelIds of the otherList, so extract them
IEnumerable<int> otherChannelIds = otherList
.Select(otherItem => otherItem.ChannelId);
return dictionary.ExtractNewObjects(otherChannelIds);
}
用法:
public static IEnumerable<NewObject> ExtractNewObjects(this Dictionary<int, List<Channel>> dictionary,
IEnumerable<int> otherChannelIds)
{
var channelIdsSet = new HashSet<int>(otherChannelIds));
// duplicate channelIds will be removed automatically
foreach (KeyValuePair<int, List<Channel>> keyValuePair in dictionary)
{
// is any ChannelId in the list also in otherChannelIdsSet?
// every keyValuePair.Value is a List<Channel>
// every Channel has a ChannelId
// channelId found if any of these ChannelIds in in the HashSet
bool channelIdFound = keyValuePair.Value
.Any(channel => otherChannelIdsSet.Contains(channel.ChannelId);
if (channelIdFound)
{
yield return new NewObject()
{
NewObjectYear = keyValuePair.Key,
NewObjectName = keyValuePair.Value
.Select(channel => channel.ChannelName)
.FirstOrDefault(),
};
}
}
}
我们可以看到四个效率改进:
IEnumerable<OtherItem> otherList = ...
Dictionary<int, List<Channel>> dictionary = ...
IEnumerable<Newobject> extractedNewObjects = dictionary.ExtractNewObjects(otherList);
var someNewObjects = extractedNewObjects
.Take(5) // here we see the benefit from the yield return
.ToList();
可以非常快速地查找HashSet<int>
是否在ChannelId
中OtherList
中找到匹配的Any()
,就使用List<Channel>
停止枚举Channelid
HashSet
可以使您在“字典”中枚举的元素不会超过实际使用的数量。yield return
时使用Select
和FirstOrDefault
可以防止NewObjectName
为空时出现异常