2013-08-15 4 views
6

У меня есть массив пользовательских объектов с именем AnalysisResult. Массив может содержать сотни тысяч объектов; и, иногда мне нужны только элементы Distinct() этого массива. Итак, я написал класс пункт Comparer называется AnalysisResultDistinctItemComparer и сделать свой вызов, как это:Как сообщить о прогрессе в длинном вызове .Distinct() в C#

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

Моя проблема в том, что этот вызов может занять длительное время (порядка нескольких минут), когда массив является особенно большой (более 200 000 объектов).

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

Мне действительно нужно уметь указывать пользователю текущий ход этого действия; но я не смог придумать хороший подход. Я играл с делать что-то вроде этого:

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

Но проблема в том, что у меня нет возможности узнать, что мой фактический прогресс. Мысли? Предложения?

+0

Проблема, которую, я думаю, вам понадобится, заключается в том, что вам нужно знать, сколько разных значений у вас впереди, чтобы установить максимальное значение индикатора прогресса. Вы, конечно, не будете знать это значение, пока ваш запрос не будет запущен ... Вы всегда можете попробовать и ускорить этот процесс, используя некоторый параллализм (не уверенный в своей структуре) http://msdn.microsoft.com/ru -us/library/dd383943.aspx – Damon

ответ

4

Не звоните ToArray() в конце вашего метода, просто используйте yield return. Так что это:

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; 
    } 
} 

В принципе, yield return делает некоторые компилятор магии, чтобы гарантировать, что итерация остается ленивым, так что вам не нужно ждать полной новой коллекции будет создан, прежде чем вернуться к абоненту. Вместо этого, по мере вычисления каждого элемента, вы возвращаете этот элемент сразу потребителю (который может затем выполнить логику обновления - если необходимо). Вы можете использовать ту же технику в своем методе GetDistinct.

Джон Скит имеет реализацию, которая выглядит следующим образом (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; 
     } 
    } 
} 

Заметьте здесь, что он использует HashSet, который построен, чтобы запретить дубликаты. Просто проверьте, добавлен ли элемент, а если нет, верните его.

Что все сказано, помните, что это вопрос типа «Алгоритмы и данные». Было бы гораздо легче сделать что-то вроде этого:

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. 

То есть, таблица символов/Dictionary построен именно для этого рода проблемы - ассоциирования записей с уникальными ключами. Если вы сохраните свои данные таким образом, это значительно упростит проблему. Не упустите простое решение!

+0

Спасибо за информацию о подходе Джона к получению отличительных объектов. Моя похожа, но лучше. :-) –

+0

Я принимаю этот ответ по трем причинам. 1) Вы указали три подхода к определению отличительных элементов. 2) Хотя первые два подхода действительно дают отличительные элементы, они не помогают определить «прогресс»; но, третий подход, конечно, позволяет мне знать, где мы находимся в процессе вытягивания отдельных предметов. 3) Я поднял голову где-то в темноте и даже не думал о том, чтобы делать что-то столь же просто, как третий подход. –

+0

Эй, очень ценится. Но да, в то время как новые инструменты классные, хорошо помнить, что классические методы классики по какой-то причине! – sircodesalot

1

Учитывая дизайн этого метода Distinct, вы выполняете итерацию по всей коллекции каждый раз, когда вы вызываете Distinct. Рассматривали ли вы запись пользовательской коллекции, добавляющей к индексу каждый раз, когда вы добавляете объект в массив?

+0

Интересный подход. Мне нужно будет поддерживать ссылки как на немодифицированный исходный массив, так и на отдельный массив. У вас есть примеры этого? –

+0

У меня нет примера, но концепция должна была бы оценивать при добавлении, а не в запросе. Если ваше сравнение фиксировано, метод индекса может использовать ссылочный индекс, чтобы избежать дублирования. Метод возврата доходности от @sircodesalot более гибкий, что дает вам возможность определить сравнение с лямбдой во время разработки. –

0

С другой стороны, вы можете использовать ThreadPool и WaitHandle для запуска бизнеса «Distinct» и «DisplayProgress» с несколькими потоками.

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(); 
     } 
    } 
} 
Смежные вопросы