2011-12-10 15 views
7

Tengo un diccionario con dobles como valores y cadenas como claves.¿Cómo contar las ocurrencias de valores únicos en Dictionary?

Quiero contar las ocurrencias de cada valor en este diccionario y quiero saber este valor (que es, por ejemplo, repetido).

por ejemplo:

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

Quiero obtener una lista:

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

¿Cómo puedo hacerlo?

+1

Un poco más de información: ¿Estás preguntando un recuento de los valores que no se repiten? ¿Podría darnos un ejemplo de datos y su resultado deseado? – Alan

+5

La prueba de dobles para la igualdad es una práctica muy cuestionable. Es posible que desee evitar mencionarlo si quiere una respuesta. El uso de Distinct(). Count() de Linq en la propiedad Values ​​es, de lo contrario, un enfoque que coincide bien con sus etiquetas. –

+2

¿Y cómo quiere probar la igualdad de los dobles aquí? –

Respuesta

9

Lo primero que debe tener en cuenta es que en realidad no le importan las teclas del diccionario. El primer paso, por lo tanto, es ignorarlos como irrelevantes para la tarea en cuestión. Vamos a trabajar con la propiedad Values del diccionario, y el trabajo es muy similar al de cualquier otra colección de enteros (o cualquier otro enumerable de cualquier otro tipo que podamos comparar para la igualdad).

Existen dos enfoques comunes para este problema, que vale la pena conocer.

El primero utiliza otro diccionario para mantener el recuento de valores:

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

Esperemos que esto es bastante sencillo. Otro enfoque es más complicado, pero tiene algunas ventajas:

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

(probablemente Usaríamos var en lugar de la verbosa IEnumerable<IGrouping<int, int>>, pero vale la pena ser preciso al explicar el código).

En una comparación directa, esta versión es inferior, más complicada de entender y menos eficiente. Sin embargo, aprender este enfoque permite algunas variantes concisas y eficientes de la misma técnica, por lo que vale la pena examinarla.

GroupBy() toma una enumeración y crea otra enumeración que contiene pares clave-valor donde el valor también es una enumeración. La lambda x => x significa que está agrupada por sí misma, pero tenemos la flexibilidad para diferentes reglas de agrupamiento. El contenido de grp se ve un poco como:

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

lo tanto, si nos bucle a través de esta una para cada grupo sacamos la Key y llamamos Count() en el grupo, obtenemos los resultados que queremos.

Ahora, en el primer caso construimos nuestro conteo en un solo pase O (n), mientras que aquí construimos el grupo en un pase O (n), y luego obtenemos el conteo en un segundo O (n) pasar, por lo que es mucho menos eficiente. También es un poco más difícil de entender, ¿por qué molestarse en mencionarlo?

Bueno, lo primero es que una vez que entendemos que podemos convertir las líneas:

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

En:

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

que es bastante conciso, y se convierte en idiomática.Es especialmente bueno si queremos continuar y hacer algo más complicado con los pares de recuento de valores, ya que podemos encadenar esto en otra operación.

La versión que pone los resultados en un diccionario puede ser aún más concisa sigue:

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

Allí, toda respuesta a su pregunta en una línea corta, en lugar de los 6 (que cortan los comentarios) para la primera versión.

(Algunos pueden preferir reemplazar dict.Values.GroupBy(x => x) con dict.GroupBy(x => x.Value) que tendrá exactamente los mismos resultados una vez que se corre el Count() en él. Si no está seguro de por qué inmediatamente, trate de resolverlo).

La otra ventaja, es que tenemos más flexibilidad con GroupBy en otros casos. Por estas razones, es probable que las personas que están acostumbradas a usar GroupBy comiencen con la concordancia de una línea de dict.Values.GroupBy(x => x).ToDictinary(g => g.Key, g => g.Count()); y luego cambien a la forma más detallada pero más efciente de la primera versión (donde incrementamos los totales acumulados en el nuevo diccionario) si resultó ser un punto de acceso de rendimiento.

-1

aún más simple sería:

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 

(Sí, es en VB.NET, pero no debería tener muchos problemas para convertir a C# :-))

+0

El usuario está solicitando C# y, por lo tanto, la respuesta se debe presentar en C#. – Neeko

Cuestiones relacionadas