6

我有一组名为AnalysisResult. 该数组可以包含数十万个对象;而且,有时我只需要Distinct()该数组的元素。因此,我编写了一个名为的项目比较器类AnalysisResultDistinctItemComparer,并像这样进行调用:

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

但问题是我无法知道我的实际进展是什么。想法?建议?

4

3 回答 3

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

Jon Skeet 有一个看起来像这样的实现(LINQ's Distinct() on a specific 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就是为这类问题而构建的——将条目与唯一键相关联。如果您以这种方式存储数据,则会大大简化问题。不要忽视简单的解决方案!

于 2013-08-15T13:19:22.410 回答
1

鉴于该 Distinct 方法的设计,您每次调用 Distinct 时都在迭代整个集合。您是否考虑过编写一个自定义集合,每次将对象添加到数组时都会添加到索引中?

于 2013-08-15T13:20:26.320 回答
0

On the other hand you may use ThreadPool and WaitHandle to run your "Distinct" and "DisplayProgress" business with multiple threads.

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();
        }
    }
}
于 2013-08-15T14:21:11.903 回答