2011-12-10 18 views
7

Ho un dizionario con i doppi come valori e stringhe come chiavi.Come contare le occorrenze di valori unici nel dizionario?

Voglio contare le occorrenze di ogni valore in questo dizionario e voglio sapere questo valore (che è ripetuto ad esempio).

per esempio:

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

voglio ottenere un elenco:

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

Come posso fare?

+1

Un po 'più di informazioni: Sei chiedendo un conteggio dei valori che non vengono ripetuti? Potresti fornirci un esempio di dati e l'output desiderato? – Alan

+5

La verifica dei doppi per l'uguaglianza è una pratica molto discutibile. Potresti voler evitare di menzionarlo se vuoi una risposta. L'uso di Linq's Distinct(). Count() sulla proprietà Values ​​è altrimenti un approccio che corrisponde bene ai tag. –

+2

E come vuoi testare l'uguaglianza dei doppi qui? –

risposta

9

La prima cosa da notare è che in realtà non ti importa delle chiavi del dizionario. Il primo passo è quindi ignorarli come irrilevanti per il compito in corso. Lavoreremo con la proprietà Values del dizionario e il lavoro è molto simile a qualsiasi altra raccolta di numeri interi (o, in effetti, qualsiasi altra enumerabile di qualsiasi altro tipo che possiamo confrontare per l'uguaglianza).

Ci sono due approcci comuni a questo problema, entrambi vale la pena conoscere.

Il primo utilizza un altro dizionario per tenere il conteggio dei valori:

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

Speriamo che questo è abbastanza semplice. Un altro approccio è più complicato, ma ha alcuni vantaggi:

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

(probabilmente avremmo usato var piuttosto che il verbose IEnumerable<IGrouping<int, int>>, ma vale la pena di essere preciso in cui spiega il codice).

In un confronto diretto, questa versione è inferiore, sia più complicata da comprendere che meno efficiente. Tuttavia, l'apprendimento di questo approccio consente alcune varianti concise ed efficienti della stessa tecnica, quindi vale la pena esaminare.

GroupBy() prende un'enumerazione e crea un'altra enumerazione che contiene coppie chiave-valore in cui il valore è anche un'enumerazione. Il lambda x => x significa che ciò che è raggruppato da se stesso, ma abbiamo la flessibilità per le diverse regole di raggruppamento. Il contenuto di grp sembra un po 'come:

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

Quindi, se si ciclo attraverso questa una per ogni gruppo tiriamo fuori la Key e chiediamo Count() sul gruppo, otteniamo i risultati che vogliamo.

Ora, nel primo caso abbiamo accumulato il nostro conteggio in un singolo passaggio O (n), mentre qui costruiamo il gruppo in un passaggio O (n), e quindi otteniamo il conteggio in un secondo O (n) passare, rendendolo molto meno efficiente. È anche un po 'più difficile da capire, quindi perché preoccuparsi di menzionarlo?

Bene, la prima è che una volta che facciamo capirlo possiamo trasformare le linee:

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

Into:

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

che è abbastanza conciso, e diventa idiomatica.È particolarmente bello se vogliamo andare avanti e fare qualcosa di più complicato con le coppie conteggio dei valori, dato che possiamo incatenarle in un'altra operazione.

La versione che mette i risultati in un dizionario può essere ancora più conciso ancora:

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

Lì, tutta la risposta alla tua domanda in una linea breve, piuttosto che le (commenti di taglio fuori) 6 per la prima versione.

(Alcuni potrebbero preferire per sostituire dict.Values.GroupBy(x => x) con dict.GroupBy(x => x.Value) che avrà esattamente gli stessi risultati, una volta corriamo il Count() su di esso. Se non sei sicuro del perché subito, provare a lavorare fuori).

L'altro vantaggio, è che abbiamo più flessibilità con GroupBy negli altri casi. Per queste ragioni, le persone che sono abituate a usare GroupBy sono in grado di iniziare con la concisione su una riga di dict.Values.GroupBy(x => x).ToDictinary(g => g.Key, g => g.Count()); e quindi passare alla forma più prolissa ma più effimera della prima versione (dove incrementiamo i totali in esecuzione nel nuovo dizionario) se ha dimostrato un hotspot di prestazioni.

-1

Ancora più semplice sarebbe:

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ì, è in VB.NET, ma non dovreste avere troppi problemi a convertire in C# :-))

+0

L'utente sta chiedendo C#, e quindi la risposta dovrebbe essere presentata in C#. – Neeko

Problemi correlati