2013-04-24 20 views
7

Ho il seguente modello:gruppo voci di elenco con LINQ

public class Entry 
{ 
    public int UseraccountId { get; set; } 
    public int CompanyId { get; set; } 
    public DateTime CreationDate { get; set; } 
    public string Target { get; set; } 
    public string Message { get; set; } 
} 

e una lista con un sacco di voci:

List<Entry> entries = ... //get all entries. 

Esempio:

Example before grouping

I' d ora mi piace raggruppare le righe 2 e 3 perché hanno lo stesso UserId, lo stesso CompanyId, lo stesso target e quasi (e questa è la parte difficile), diciamo in un intervallo di 5 secondi, la stessa data.

Dopo il raggruppamento mia lista dovrebbe essere simile a questo:

enter image description here

C'è un facile approccio per questo problema? Qualche consiglio? Scommetto che Linq mi aiuterà in giro ma non sono sicuro di come.

Modifica: Grazie a tutti per il vostro feedback. Ho deciso di cambiare il design e di garantire che il datetime sia ora lo stesso. Quindi il raggruppamento con linq ora è molto semplice.

+0

Quando si dice "quasi", come si desidera raggruppare, nello stesso minuto, gli stessi 10 secondi? –

+0

Diciamo in un intervallo di 5 secondi. – mosquito87

+3

Il raggruppamento con gli stessi valori è facile; dai un'occhiata a [molte domande] (http://stackoverflow.com/search?q=%5Bc%23%5D+linq+group+by+multiple+keys) su SO relativo a questo. Raggruppare di quasi gli stessi valori è un po 'più difficile, perché se X è quasi lo stesso di Y, e Y è quasi lo stesso di Z, allora non significa che X sia quasi lo stesso di Z. tipo di [algoritmo di clustering] (http://en.wikipedia.org/wiki/Cluster_analysis). – dtb

risposta

0

Non c'è una risposta semplice in quanto dipende da ciò che si considera una corrispondenza. Ci sono approcci semplici e complicati e ovunque nel mezzo. Avrai bisogno di venire con l'algoritmo per quello. Un approccio semplice sarebbe quello di radere i secondi e abbinare semplicemente al minuto, ma potrebbe essere troppo lungo. È possibile scrivere un metodo normalizzando i timestamp fino a 5 o 10 secondi e raggruppare su quelli già suggeriti.

Se si desidera raggruppare due messaggi entro x secondi, questo approccio funzionerà principalmente. Ci saranno sempre quei valori che rientrano nell'intervallo ma cadranno su entrambi i lati del cutoff. Se stai bene con questo e la semplicità del valore, allora la risposta sopra funzionerà.

Se ciò non funziona e si desidera raggruppare il limite artificiale, sarà necessario un altro approccio.Un approccio semplice in questo caso potrebbe essere l'utilizzo di LINQ per raggruppare tutto tranne il timestamp. Questo farà un raggruppamento preliminare dei tuoi dati. Quindi è possibile scorrere tutti i gruppi e confrontare ciascun valore temporale con ciascun valore temporale nello stesso gruppo e determinare se è compreso nell'intervallo. Quindi afferrare manualmente quei valori che rientrano nell'intervallo specificato e raggrupparli insieme.

Questo ha un caso limite aggiuntivo che è necessario prendere una decisione. Se si decide di raggruppare entro 1 secondo e si hanno tre voci i cui secondi sono (semplificati) 1, 2 e 3. 1 e 2 sono entro un secondo e 2 e 3 sono anche all'interno di un secondo ma 1 e 3 aren ' t. Riusciresti a raggruppare questi in base al fatto che 2 si trova entro un secondo dagli altri, oppure raggrupperesti 1 e 2, rendendo 2 non idonei da raggruppare con 3 e 3 che sarebbero da soli.

Finirai per finire con una soluzione in grado di far crescere i bucket in base al concatenamento dei valori o a un diverso taglio artificiale basato sul primo gruppo creato piuttosto che su interruzioni temporali. Il tempo difficile è molto più semplice quindi, a meno che non si abbiano secchi in crescita, mi raccomando di andare con un timestamp normalizzato e di raggrupparlo.

È necessario definire ciò che si intende per quasi e pianificare di conseguenza.

+0

Grazie per i tuoi pensieri. Mi ha fatto pensare al mio design e ho deciso di cambiarlo in modo che il datetime ora fosse davvero lo stesso e il raggruppamento ora è abbastanza semplice. – mosquito87

0

Ciò fornirebbe un intervallo di -5 secondi, ma per un algoritmo di corrispondenza (clustering) completo nel Data Creazione, suppongo che sarà molto più difficile. Hai capito il senso però.

List<Entry> entries = entries.GroupBy(a => a.UserId) 
          .ThenBy(a => a.CompanyId) 
          .ThenBy(a => a.CreationDate.AddSeconds(-5)); 
+0

In realtà, anche se non funzionasse correttamente per il CreationDate, dovresti prima piangere i secondi. –

+0

Prova la divisione intero? .ThenBy (a => TimeSpan.FromTicks (a.CreationDate.Ticks) .TotalSeconds/5); – penguat

+0

Non penso che sia possibile raggruppare i raggruppamenti usando 'ThenBy', a meno che non faccia parte di alcune librerie di estensioni Linq. –

1

Come @dtb menitons, il raggruppamento per "chiudere" è difficile perché si può finire con un "secchio" più grande di quanto previsto. Ad esempio, se hai 100 voci che vengono create a 4 secondi di distanza l'una dall'altra, il raggruppamento di elementi che si trovano entro 5 secondi dall'elemento "successivo" li metterebbe tutti in un solo secchio!

Se, invece, si vuole turno la data di creazione al più vicino, diciamo, 5 secondi e poi di gruppo, è possibile utilizzare:

TimeSpan ts = new TimeSpan(0, 0, 5); // 5 seconds 
entries.GroupBy(i => new { 
          UserId = i.UserId, 
          CompanyId = i.CompanyId, 
          Target = i.Target, 
          RoundedTime = DateTime.MinValue.AddTicks(
              (long)(Math.Round((decimal)i.CreationDate.Ticks/ts.Ticks) * ts.Ticks) 
             ) ; 
         )) 
     .Select(g => new { 
         UserId = g.Key.UserId, 
         CompanyId = g.Key.CompanyId, 
         Target = g.Key.Target, 
         RoundedTime = g.Key.RoundedTime, 
         Message = string.Join(", ",g.Select(i=> i.Message).ToArray()) 
         }); 

Che gruppo da elementi che sono arrotondato fino ai 5 secondi successivi - è possibile che due articoli distanti un secondo si trovino in secchi diversi, ma non si ha il problema con la cummutatività che il requisito dichiarato ha.