【问题标题】:How to minimize run time with large data set (Make list of unique objects from list of 93,773 objects)如何使用大数据集最小化运行时间(从 93,773 个对象列表中列出唯一对象)
【发布时间】:2018-11-27 08:32:08
【问题描述】:

我们正在从 EVE Online API 中提取大量 JSON 对象,并使用 Newtonsoft.Json.JsonConvert 将它们反序列化为 EveObjModel 对象。从那里我们想要创建一个唯一对象列表,即每个 type_id 中最昂贵的。我也粘贴了下面的 dataContract。

问题:下面的代码可以处理较小的数据集,但它不适用于大量数据。目前,我们正在运行它,它需要 50 多分钟(并且还在计数)。我们可以做些什么来减少运行更大的数据集所需的时间到可以接受的水平?

感谢您的宝贵时间。手指交叉。

    // The buyList contains about 93,000 objects. 
    public void CreateUniqueBuyList(List<EveObjModel> buyList)
    {

        List<EveObjModel> uniqueBuyList = new List<EveObjModel>();

        foreach (EveObjModel obj in buyList)
        {
            int duplicateCount = 0;

            for (int i = 0; i < uniqueBuyList.Count; i++)
            {
                if (uniqueBuyList[i].type_id == obj.type_id)
                       duplicateCount++;
            }

            if (duplicateCount == 1)
            {
                foreach (EveObjModel objinUnique in uniqueBuyList)
                {
                    if (obj.type_id == objinUnique.type_id && obj.price > objinUnique.price)
                    {
                        // instead of adding obj, the price is just changed to the price in the obj. 
                        objinUnique.price = obj.price;

                    }
                    else if (obj.type_id == objinUnique.type_id && obj.price == objinUnique.price)
                    {
                        //uniqueBuyList.RemoveAll(item => item.type_id == obj.type_id);

                    }
                    else 
                    {
                        // Hitting this  mean that there are other objects with same type and higher price OR its not the same type_id
                    }

                }
            }
            else if (duplicateCount > 1)
            {
                // shud not happn...
            }
            else
            {

                uniqueBuyList.Add(obj);
            }


            continue;
        }
        foreach (EveObjModel item in uniqueBuyList.OrderBy(item => item.type_id))
        {
            buyListtextField.Text += $"Eve Online Item! Type-ID is: {item.type_id}, Price is {item.price}\n";
        }
    }

这是我们的 EveObjModel 类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading.Tasks;

    namespace EveOnlineApp
    {
    [DataContract]
         public class EveObjModel
    {
    [DataMember]
    public bool is_buy_order { get; set; }

    [DataMember]
    public double price { get; set; }

    [DataMember]
    public int type_id { get; set; }

    }
}

【问题讨论】:

  • 耗时长的部分是什么?对象有多大?下载近 94,000 个对象可能需要一段时间(取决于它们的大小)
  • @MindSwipe 下载对象大约需要 2.5 分钟。我们也将不得不对此进行调查,但上述问题仍占运行时间的 99% 以上。感谢您的宝贵时间。

标签: c# algorithm json.net runtime


【解决方案1】:

这个过程很慢并不奇怪,因为您正在使用的算法(带有嵌套循环)至少具有二次 O(N*N) 时间复杂度,对于这样大小的数据集来说真的很慢。

一种方法是使用 LINQ GroupBy 运算符,它在内部使用基于哈希的查找,因此理论上具有 O(N) 时间复杂度。因此,您按type_id 分组,并为每个组(具有相同键的元素列表)取最大price 的组:

var uniqueBuyList = buyList
    .GroupBy(e => e.type_id)
    .Select(g => g.OrderByDescending(e => e.price).First())
    .ToList();

当然,您无需对列表进行排序即可获取最大 price 的元素。更好的版本是使用Aggregate 方法(基本上是foreach 循环):

var uniqueBuyList = buyList
    .GroupBy(e => e.type_id)
    .Select(g => g.Aggregate((e1, e2) => e1.price > e2.price ? e1 : e2))
    .ToList();

另一种非基于 LINQ 的方法是按type_id 升序、price 降序对输入列表进行排序。然后在排序列表上执行一个循环并获取每个 type_id 组的第一个元素(它将具有最大值 price):

var comparer = Comparer<EveObjModel>.Create((e1, e2) =>
{
    int result = e1.type_id.CompareTo(e2.type_id);
    if (result == 0) // e1.type_id == e2.type_id
        result = e2.price.CompareTo(e1.price); // e1, e2 exchanged to get descending order
    return result;
});
buyList.Sort(comparer);
var uniqueBuyList = new List<EveObjModel>();
EveObjModel last = null;
foreach (var item in buyList)
{
    if (last == null || last.type_id != item.type_id)
        uniqueBuyList.Add(item);
    last = item;
}

该算法的复杂度为 O(N*log(N)),因此它比基于散列的算法差(但比原始算法好得多)。好处是它使用更少的内存,并且结果列表已经按type_id排序,所以你不需要使用OrderBy

【讨论】:

  • 非常感谢您的回答。很有帮助!
【解决方案2】:

您应该能够使用Enumerable.GroupBy() 相当有效地执行此操作:

var grouped = buyList.GroupBy(item => item.type_id);

var uniqueBuyList = new List<EveObjModel>();

foreach (var group in grouped)
{
    var combined = group.First();
    combined.price = group.Max(item => item.price);
    uniqueBuyList.Add(combined);
}

或者(但更难阅读):

var uniqueBuyList = buyList.GroupBy(item => item.type_id).Select(group =>
{
    var combined = group.First();
    combined.price = group.Max(item => item.price);
    return combined;
}).ToList();

【讨论】:

  • 非常感谢您的回答。我们将尝试两种解决方案并计时。
【解决方案3】:

我们可以通过升序type_id然后通过升序price对给定的列表进行排序并反转它。因此,EveObjModel 对象和 higher price 对每个唯一的 type_id 都排在第一位。

然后,我们可能会再次遍历对象列表,并选择第一个出现的唯一 type_id,然后跳过相同的 type_id。

由于我们只排序一次,这将导致我们的时间复杂度为O(n * log n)。由于 n = 93773 ,以 2 为底的 93773 的对数几乎 = 17。因此,排序将需要整个 n * log n = 93773 * 17 = 1594141 操作,这可以在非常短的时间内完成。

希望以下代码对您有所帮助!

public void CreateUniqueBuyList(List<EveObjModel> buyList)
{
    //sort by ascending type_id and then by ascending price and reverse it. so that,
    // object with higher price come first
    List<EveObjModel>tempList = buyList.OrderBy(x => x.type_id).ThenBy(x => x.price).Reverse().ToList();
    List<EveObjModel> uniqueBuyList = new List<EveObjModel>();
    for (int i = 0; i < tempList.Count; ++i) {
        if ((i > 1) && tempList[i - 1].type_id == tempList[i].type_id) continue; // if duplicate type_id then don't take it again
        uniqueBuyList.Add(tempList[i]);
    }

    foreach (EveObjModel item in uniqueBuyList.OrderBy(item => item.type_id))
    {
        buyListtextField.Text += $"Eve Online Item! Type-ID is: {item.type_id}, Price is {item.price}\n";
    }
}

【讨论】:

  • 非常感谢您的回答。我们将试用您的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-25
相关资源
最近更新 更多