拼合字典<int,list <object =“” >>

时间:2018-10-14 18:02:23

标签: c# linq dictionary key-value

我有一个字典,其中有一个表示年份的整数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)之类的方法绝对不是正确的方法。因此,我尝试寻找其他方式展平,这对我的情况很有利,但失败了。我还考虑过要创建一个包含年份和扁平化列表的类,但是我不知道如何做。 请帮助我。

此外,还有另一种不需要创建新类的方法吗?

2 个答案:

答案 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时使用SelectFirstOrDefault可以防止NewObjectName为空时出现异常