Как минимизировать время выполнения с большим набором данных (Составьте список уникальных объектов из списка 93 773 объектов) - PullRequest
0 голосов
/ 27 ноября 2018

Мы извлекаем большое количество JSON-объектов из EVE Online API и десериализуем их в EveObjModel, используя Newtonsoft.Json.JsonConvert.Оттуда мы хотим создать список уникальных объектов, то есть самых дорогих из каждого 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; }

    }
}

Ответы [ 3 ]

0 голосов
/ 27 ноября 2018

Не удивительно, что процесс медленный, потому что используемый вами алгоритм (с вложенным циклом) имеет как минимум квадратичную сложность времени 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 loop) для этого:

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.

0 голосов
/ 27 ноября 2018

Мы можем отсортировать данный список по возрастанию type_id, а затем по возрастанию price и перевернуть его.Таким образом, EveObjModel объект с higher price стоит первым для каждого уникального type_id.

Затем мы можем снова пройти по объекту List и подобрать уникальный type_id, который появляется вначале и пропустить тот же type_id, после чего.

Поскольку мы сортируем только один раз, это вызовет временную сложность O(n * log n).Поскольку n = 93773, логарифм 93773 в основании 2 почти = 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";
    }
}
0 голосов
/ 27 ноября 2018

Вы должны быть в состоянии использовать 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...