Свести словарь - PullRequest
       6

Свести словарь

0 голосов
/ 14 октября 2018

У меня есть словарь, в котором есть целочисленный ключ, представляющий год, и значение, представляющее собой список объектов Channel.Мне нужно сгладить данные и создать из них новый объект.

В настоящее время мой код выглядит следующим образом:

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 голосов
/ 15 октября 2018

Вы понимаете, что если второй элемент списка в определенном элементе словаря имеет соответствующий идентификатор канала, вы возвращаете первый элемент этого списка, не так ли?

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.

И даже если вы можете использовать не полный результат, а только первыйили первые несколько из-за FirstOrDefault(), или Take(3), вы делаете это для каждого элемента каждого списка в своем Словаре.

Действительно, ваш запрос может быть намного более эффективным.

Поскольку вы используете ChannelIds в своем OtherList только для того, чтобы выяснить, присутствует ли он, одним из основных улучшений было бы преобразование ChannelIds из OtherList в HashSet<int>, где у вас есть быстрый быстрый поиск впроверьте, находится ли ChannelId одного из значений в вашем Словаре в HashSet.

Так что для каждого элемента в вашем словаре, вам нужно только проверить каждый ChannelId в списке, чтобы увидеть, является ли один изих в HashSet.Как только вы нашли его, вы можете остановить и вернуть только первый элемент списка и ключ.

Мое решение - это функция расширения словаря>.См. Расширение методов расширения:

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);
}

Это вызывает другие ExtractNewobjects:

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 предотвращает исключения, если List<Channel> пусто
0 голосов
/ 14 октября 2018

Мне кажется, вы пытаетесь сделать только фильтрацию, для этого вам не нужно объединяться:

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,
        }));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...