Как посчитать вхождения уникальных значений в словарь? - PullRequest
7 голосов
/ 11 декабря 2011

У меня есть словарь с двойными значениями и строки в качестве ключей.

Я хочу подсчитать вхождения каждого значения в этом словаре, и я хочу знать это значение (например, повторное).

например:

key1, 2
key2, 2
key3, 3
key4, 2
key5, 5
key6, 5

Я хочу получить список:

2 - 3 (times)
3 - 1 (once)
5 - 2 (twice)

Как я могу это сделать?

Ответы [ 2 ]

10 голосов
/ 11 декабря 2011

Первое, что нужно отметить, это то, что вам на самом деле не нужны ключи словаря. Следовательно, первый шаг - игнорировать их как не относящиеся к поставленной задаче. Мы собираемся работать со свойством Values словаря, и работа практически такая же, как и для любого другого набора целых чисел (или любого другого перечислимого любого другого типа, который мы можем сравнить на равенство).

Существует два распространенных подхода к этой проблеме, оба из которых стоит знать.

Первый использует другой словарь для хранения количества значений:

//Start with setting up the dictionary you described.
Dictionary<string, int> dict = new Dictionary<string, int>{
    {"key1", 2},
    {"key2", 2},
    {"key3", 3},
    {"key4", 2},
    {"key5", 5},
    {"key6", 5}
};
//Create a different dictionary to store the counts.
Dictionary<int, int> valCount = new Dictionary<int, int>();
//Iterate through the values, setting count to 1 or incrementing current count.
foreach(int i in dict.Values)
    if(valCount.ContainsKey(i))
        valCount[i]++;
    else
        valCount[i] = 1;
//Finally some code to output this and prove it worked:
foreach(KeyValuePair<int, int> kvp in valCount)//note - not sorted, that must be added if needed
    Console.WriteLine("{0} - {1}", kvp.Key, kvp.Value);

Надеюсь, это довольно просто. Другой подход более сложный, но имеет свои плюсы:

//Start with setting up the dictionary you described.
Dictionary<string, int> dict = new Dictionary<string, int>{
    {"key1", 2},
    {"key2", 2},
    {"key3", 3},
    {"key4", 2},
    {"key5", 5},
    {"key6", 5}
};
IEnumerable<IGrouping<int, int>> grp = dict.Values.GroupBy(x => x);
//Two options now. One is to use the results directly such as with the
//equivalent code to output this and prove it worked:
foreach(IGrouping<int, int> item in grp)//note - not sorted, that must be added if needed
    Console.WriteLine("{0} - {1}", item.Key, item.Count());
//Alternatively, we can put these results into another collection for later use:
Dictionary<int, int> valCount = grp.ToDictionary(g => g.Key, g => g.Count());
//Finally some code to output this and prove it worked:
foreach(KeyValuePair<int, int> kvp in valCount)//note - not sorted, that must be added if needed
    Console.WriteLine("{0} - {1}", kvp.Key, kvp.Value);

(Мы, вероятно, будем использовать var вместо многословного IEnumerable<IGrouping<int, int>>, но при объяснении кода стоит быть точным).

В прямом сравнении эта версия уступает - и более сложна для понимания, и менее эффективна. Однако изучение этого подхода допускает несколько кратких и эффективных вариантов одного и того же метода, поэтому его стоит изучить.

GroupBy() принимает перечисление и создает другое перечисление, которое содержит пары ключ-значение, где значение также является перечислением. Лямбда x => x означает, что она группируется сама по себе, но у нас есть гибкость для других правил группировки, чем эта. Содержимое grp выглядит примерно так:

{
  {Key=2, {2, 2, 2}}
  {Key=3, {3}}
  {Key=5, {5, 5}}
}

Итак, если мы пройдем через это an для каждой группы, вытащим Key и вызовем Count() для группы, мы получим желаемые результаты.

Теперь, в первом случае мы построили наш счет за один проход O (n), в то время как здесь мы построили группу за проход O (n), а затем получили счет за второй O (n) пройти, что делает его гораздо менее эффективным. Это также немного сложнее понять, так зачем говорить об этом?

Ну, во-первых, как только мы поймем это, мы можем повернуть линии:

IEnumerable<IGrouping<int, int>> grp = dict.Values.GroupBy(x => x);
foreach(IGrouping<int, int> item in grp)
    Console.WriteLine("{0} - {1}", item.Key, item.Count());

В

foreach(var item in dict.Values.GroupBy(x => x))
  Console.WriteLine("{0} - {1}", item.Key, item.Count());

Что довольно лаконично и становится идиоматичным. Особенно хорошо, если мы захотим затем сделать что-то более сложное с парами счетчик значений, поскольку мы можем связать это в другую операцию.

Версия, которая помещает результаты в словарь, может быть еще более краткой:

var valCount = dict.Values.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());

Там, на весь ваш вопрос ответили в одной короткой строке, а не 6 (вырезать комментарии) для первой версии.

(Некоторые могут предпочесть заменить dict.Values.GroupBy(x => x) на dict.GroupBy(x => x.Value), что будет иметь точно такие же результаты, как только мы запустим Count(). Если вы не уверены, почему, попробуйте решить это).

Другое преимущество заключается в том, что у нас больше гибкости с GroupBy в других случаях. По этим причинам люди, которые привыкли использовать GroupBy, вполне могут начать с краткого однострочного dict.Values.GroupBy(x => x).ToDictinary(g => g.Key, g => g.Count());, а затем перейти к более подробной, но более эффективной форме первой версии (где мы увеличиваем промежуточные итоги). в новом словаре), если это доказало эффективность точки доступа.

0 голосов
/ 07 апреля 2013

Еще проще было бы:

Private Function CountOccurenceOfValue(dictionary As Dictionary(Of Integer, Integer), valueToFind As Integer) As Integer
    Return (From temp In dictionary Where temp.Value.Equals(valueToFind) Select temp).Count()
End Function

(Да, это в VB.NET, но у вас не должно быть особых проблем с конвертацией в C # :-))

...