2013-08-15 16 views
6

Ho una matrice di oggetti personalizzati denominati AnalysisResult. La matrice può contenere centinaia di migliaia di oggetti; e, occasionalmente, ho bisogno solo degli elementi Distinct() di quell'array. Così, ho scritto una classe oggetto di confronto chiamato AnalysisResultDistinctItemComparer e fare la mia chiamata in questo modo:Come segnalare l'avanzamento di una lunga chiamata a .Distinct() in C#

public static AnalysisResult[] GetDistinct(AnalysisResult[] results) 
{ 
    return results.Distinct(new AnalysisResultDistinctItemComparer()).ToArray(); 
} 

Il mio problema è che questo chiamata può richiedere molto tempo (nell'ordine di minuti) quando la matrice è particolarmente grande (più di 200.000 oggetti).

Attualmente chiamo questo metodo in un operatore in background e visualizzo una gif in rotazione per avvisare l'utente che il metodo viene eseguito e che l'applicazione non è stata congelata. Tutto va bene e bene, ma non fornisce all'utente alcuna indicazione del progresso attuale.

Devo davvero essere in grado di indicare all'utente l'avanzamento corrente di questa azione; ma, non sono riuscito a trovare un buon approccio. Stavo giocando con il fare qualcosa di simile:

public static AnalysisResult[] GetDistinct(AnalysisResult[] results) 
{ 
    var query = results.Distinct(new AnalysisResultDistinctItemComparer()); 

    List<AnalysisResult> retVal = new List<AnalysisResult>(); 
    foreach(AnalysisResult ar in query) 
    { 
     // Show progress here 
     retVal.Add(ar); 
    } 

    return retVal.ToArray(); 
} 

Ma il problema è che non ho modo di sapere ciò che il mio progresso reale è. Pensieri? Suggerimenti?

+0

Il problema che penso si avrà è che avresti bisogno di sapere quanti valori distinti hai davanti per impostare il valore massimo dell'indicatore di progresso. Ovviamente non conoscerai questo valore fino a quando la query non sarà effettivamente in esecuzione ... Potresti sempre provare a velocizzare questo processo usando un parallelismo (non sicuro del tuo framework) http://msdn.microsoft.com/en -us/library/dd383943.aspx – Damon

risposta

4

Non chiamare ToArray() alla fine del metodo, utilizzare solo yield return. Fate così:

public static IEnumerable<AnalysisResult> Distinct(AnalysisResult[] results) 
{ 
    var query = results.Distinct(new AnalysisResultDistinctItemComparer()); 

    foreach(AnalysisResult ar in query) 
    { 
     // Use yield return here, so that the iteration remains lazy. 
     yield return ar; 
    } 
} 

In sostanza, yield return fa qualche magia compilatore per garantire che l'iterazione rimane pigro, in modo da non dover aspettare per una nuova collezione completa da creare prima di ritornare al chiamante. Invece, quando ogni articolo viene calcolato, si restituisce l'articolo immediatamente al numero al consumatore (che può quindi eseguire la logica di aggiornamento - per articolo se necessario). È possibile utilizzare la stessa tecnica anche nel metodo GetDistinct.

Jon Skeet ha un'implementazione che assomiglia a questo (LINQ's Distinct() on a particular property):

public static IEnumerable<TSource> DistinctBy<TSource, TKey> 
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) 
{ 
    HashSet<TKey> seenKeys = new HashSet<TKey>(); 
    foreach (TSource element in source) 
    { 
     if (seenKeys.Add(keySelector(element))) 
     { 
      yield return element; 
     } 
    } 
} 

Avviso qui che usa un HashSet, che è costruito per non consentire i duplicati. Basta controllare per vedere se l'oggetto è già stato aggiunto, e in caso contrario, quindi restituirlo.

Ciò detto, ricorda che questa è una domanda tipo Algoritmi e Strutture dati. Sarebbe molto più facile fare qualcosa di simile:

Dictionary<Key, Value> distinctItems = new Dictionary<Key, Value>(); 

foreach (var item in nonDistinctSetOfItems) { 
    if (distinctItems.ConatainsKey(item.KeyProperty) == false) { 
     distinctItems.Add(item.KeyProperty, item); 
    } 
} 

... = distinctItems.Values // This would contain only the distinct items. 

cioè una tabella dei simboli/Dictionary è costruito proprio per questo tipo di problema - associare voci con chiavi univoche. Se conservi i tuoi dati memorizzati in questo modo, semplifica enormemente il problema. Non trascurare la soluzione semplice!

+0

Grazie per le informazioni sull'approccio di Jon a ottenere oggetti distinti. Il mio è simile ma il suo è migliore. :-) –

+0

Sto accettando questa risposta per tre motivi. 1) Hai fornito tre approcci per determinare elementi distinti. 2) Mentre i primi due approcci danno gli item Distinti essi non aiutano a determinare il "progresso"; ma, il terzo approccio, ovviamente, mi consente di sapere dove siamo nel processo di estrazione degli articoli Distinti. 3) Ho avuto la mia testa da qualche parte oscura e non ho nemmeno pensato di fare qualcosa di semplice come il terzo approccio. –

+0

Ehi, molto apprezzato. Ma sì, mentre i nuovi strumenti sono interessanti, è bene ricordare che le tecniche classiche sono classiche per una ragione! – sircodesalot

1

Dato il design del metodo Distinct, stai ripetendo l'intera collezione ogni volta che chiami Distinto. Hai considerato di scrivere una collezione personalizzata che si aggiunge a un indice ogni volta che aggiungi un oggetto all'array?

+0

Approccio interessante. Avrei bisogno di mantenere riferimenti sia alla matrice originale non modificata che alla matrice distinta. Avete qualche esempio di questo? –

+0

Non ho un esempio, ma il concetto sarebbe di valutare su add anziché su query. Se il confronto è fisso, il metodo dell'indice potrebbe utilizzare un indice di riferimento per evitare la duplicazione. Il metodo yield return di @sircodesalot è più flessibile, offrendoti la possibilità di determinare il confronto con un lambda in fase di progettazione. –

0

D'altra parte è possibile utilizzare ThreadPool e WaitHandle per eseguire l'attività "Distinct" e "DisplayProgress" con più thread.

public class Sample 
{ 
    public void Run() 
    { 
     var state = new State(); 
     ThreadPool.QueueUserWorkItem(DoWork, state); 
     ThreadPool.QueueUserWorkItem(ShowProgress, state); 
     WaitHandle.WaitAll(new WaitHandle[] {state.AutoResetEvent}); 
     Console.WriteLine("Completed"); 
    } 

    public void DoWork(object state) 
    { 
     //do your work here 
     for (int i = 0; i < 10; i++) 
     { 
      ((State) state).Status++; 
      Thread.Sleep(1000); 
     } 

     ((State) state).AutoResetEvent.Set(); 
    } 

    public void ShowProgress(object state) 
    { 
     var s = (State) state; 
     while (!s.IsCompleted()) 
     { 

      if (s.PrintedStatus != s.Status) 
       Console.WriteLine(s.Status); 
      s.PrintedStatus = s.Status; 
     } 
    } 

    public class State 
    { 
     public State() 
     { 
      AutoResetEvent = new AutoResetEvent(false); 
     } 

     public AutoResetEvent AutoResetEvent { get; private set; } 
     public int Status { get; set; } 
     public int PrintedStatus { get; set; } 
     private bool _completed; 
     public bool IsCompleted() 
     { 
      return _completed; 
     } 
     public void Completed() 
     { 
      _completed = true; 
      AutoResetEvent.Set(); 
     } 
    } 
} 
Problemi correlati