Получение TKey из словаря с общими значениями, где TValue — список

#c# #dictionary

#c# #словарь

Вопрос:

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

 Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>()
{
    {"a" , new List<string> { "Red","Yellow"} },
    {"b" , new List<string> { "Blue","Red"} },
    {"c" , new List<string> { "Green","Orange"} },
    {"d" , new List<string> { "Black","Green"} },
};
  

Мне нужен вывод в виде словаря из dict , где общим значением в List<string> должен быть ключ, а значением должен быть список ключей.

Например.:

 Red: [a,b]
Green: [c,d]
  

Я не знаю, как решить эту проблему с помощью list in dictionary as TValue .

Пожалуйста, объясните мне, как обрабатывать списки с помощью in dictionary.

Ответ №1:

Вы можете дополнить свой словарь с помощью SelectMany и получить простой список, который выглядит как

 "a" - "Red"
"a" - "Yellow"
"b" - "Blue"
"b" = "Red"
// and so on
  

затем сгруппируйте по значению и создайте новый словарь из этих групп. Попробуйте этот код:

 var commonValues = dict.SelectMany(kv => kv.Value.Select(v => new {key = kv.Key, value = v}))
    .GroupBy(x => x.value)
    .Where(g => g.Count() > 1)
    .ToDictionary(g => g.Key, g => g.Select(x => x.key).ToList());
  

Ответ №2:

Много зацикливаний… Выполните цикл по словарю, затем выполните цикл по каждому значению в списке.

 var result = new Dictionary<string, List<string>>();

// Loop through each key/value pair in the dictionary
foreach (var kvp in dict)
{
    // kvp.Key is the key ("a", "b", etc)
    // kvp.Value is the list of values ("Red", "Yellow", etc)

    // Loop through each of the values
    foreach (var value in kvp.Value)
    {
        // See if our results dictionary already has an entry for this
        // value. If so, grab the corresponding list of keys. If not,
        // create a new list of keys and insert it.
        if (!result.TryGetValue(value, out var list))
        {
            list = new List<string>();
            result.Add(value, list);
        }

        // Add our key to this list of keys
        list.Add(kvp.Key);
    }
}
  

Если вы хотите отфильтровать это по записям, содержащим более одного элемента, то вы можете сделать:

 result = result.Where(x => x.Value.Count > 1).ToDictionary(x => x.Key, x => x.Value);
  

В качестве альтернативы вы можете избежать циклов и вместо этого использовать Linq:

 // Flatten the dictionary into a set of tuples
// e.g. (a, Red), (a, Yellow), (b, Blue), (b, Red), etc
var result = dict.SelectMany(kvp => kvp.Value.Select(color => (key: kvp.Key, color)))
    // Group by the value, taking the color as the elements of the group
    // e.g. (Red, (a, b)), (Yellow, (a)), etc
    .GroupBy(item => item.color, item => item.key)
    // Filter to the ones with more than one item
    .Where(group => group.Count() > 1)
    // Turn it into a dictionary, taking the key of the grouping
    // (Red, Green, etc), as the dictionary key
    .ToDictionary(group => group.Key, group => group.ToList());
  

Вы также можете использовать синтаксис запроса linq, который немного длиннее, но позволяет избежать путаницы вокруг SelectMany :

 var result =
    (
        from kvp in dict
        from color in kvp.Value
        group kvp.Key by color into grp
        where grp.Count() > 1
        select grp
    ).ToDictionary(grp => grp.Key, grp => grp.ToList());
  

Комментарии:

1. В foreach цикле это не позволяет мне инициализировать list в TryGetValue() методе. Итак, я делал это раньше, почему это так?

2. @Watarap вы используете более старую версию C #. «Переменные Out» были введены в C # 7.0.

3. Я использую @canton7 . NET framework 4.7.2, поэтому я думаю, что c # версии 7.0 plus. У меня это работает, это не позволяет мне объявлять подобным образом if (!result.TryGetValue(value, out var list)) . Я должен объявить переменную list List<string> list; if (!result.TryGetValue(value, out list)) , чтобы она скомпилировалась.

4. @Watarap Для C # 7.0 вам понадобится Visual Studio 2017. Убедитесь, что он выбран в Project -> Properties -> Build -> Advanced -> Language version. Впрочем, это не имеет значения — можно сделать то, что вы сделали, и результат будет тот же.

5. @canton7 вы правы, я действительно использую c # 6.0 в Visual Studio 2015, только что проверил это. И последний вопрос, почему при его использовании объект должен реализовывать IConvertible . Этот проект не основан на консольном приложении, и я не смог выяснить, что вызывает эту ошибку.