C # сравнение двух больших списков элементов по определенному свойству

2

У меня есть два больших списка элементов, класс которых выглядит следующим образом (оба списка одного типа):

public class Items
{
 public string ItemID { get; set; }
 public int QuantitySold { get; set; }
}


var oldList = new List<Items>(); // oldList

var newList = new List<Items>(); // new list

Старый список содержит элементы из базы данных, а новый список представляет элементы, извлеченные из API;

Оба списка могут быть очень большими с элементами 10000+ в каждом (всего 20000)

Мне нужно сравнить элементы из newList с элементами из "oldList" и посмотреть, какие элементы с одинаковым значением itemID имеют разные значения "QuantitySold", а те, которые имеют разные значения "QuantitySold", должны храниться в третьем списке с именем " differentQuantityItems".

Я мог бы просто сделать двойной список foreach и сравнить значения, но так как оба списка велики, производительность с двойным циклом foreach ужасна, и я не могу это сделать...

Может ли кто-нибудь помочь мне с этим?

@YamamotoTetsua Я уже использую IEqualityComparer для получения желаемого результата, однако он не дает ожидаемых результатов. Вот почему... У меня есть первый IEqualityComparer, который выглядит следующим образом:

 public class MissingItemComparer : IEqualityComparer<SearchedUserItems>
    {
        public static readonly IEqualityComparer<SearchedUserItems> Instance = new MissingItemComparer();

        public bool Equals(SearchedUserItems x, SearchedUserItems y)
        {
            return x.ItemID == y.ItemID;
        }

        public int GetHashCode(SearchedUserItems x)
        {
            return x.ItemID.GetHashCode();
        }
    }

Использование этого IEqualityComparer в основном дает мне элементы из newList, которых нет в моей базе данных, например:

var missingItems= newItems.Except(competitor.SearchedUserItems.ToList(), MissingItemComparer.Instance).ToList();

Теперь в этом списке у меня будет список элементов, которые являются новыми от API и которых нет в моей БД...

Второй IEqualityComparer основан на различных количествах, проданных из старого и нового списка:

public class ItemsComparer : IEqualityComparer<SearchedUserItems>
    {
        public static readonly IEqualityComparer<SearchedUserItems> Instance = new ItemsComparer();
        public bool Equals(SearchedUserItems x, SearchedUserItems y)
        {
            return (x.QuantitySold == y.QuantitySold);
        }
        public int GetHashCode(SearchedUserItems x)
        {
            return x.ItemID.GetHashCode();
        }
    }

Пример использования:

var differentQuantityItems = newItems.Except(competitor.SearchedUserItems.ToList(), ItemsComparer.Instance).ToList();

Проблема с этими двумя сравнителями равенства заключается в том, что первый из них, например, вернет отсутствующие itemID:

123124124

123124421

512095902

И они действительно отсутствуют в моем старом списке... Однако второй IEQualityComparer также вернет эти элементы как различные элементы количества, они действительно есть, но их нет в старом списке. Поэтому их не следует включать во второй список.,

  • 1
    «Производительность с двойным циклом foreach ужасна, и я не могу этого сделать». Вы проверили это путем измерения или это только предположение ?
  • 0
    @HimBromBeere yes проверено несколько раз, когда оба списка велики, время выполнения> 30 секунд
Показать ещё 10 комментариев
Теги:
linq
performance
list
c#-4.0

4 ответа

6
Лучший ответ

Это идеальный кандидат для LINQ Join:

var differentQuantityItems =
    (from newItem in newList
     join oldItem in oldList on newItem.ItemID equals oldItem.ItemID
     where newItem.QuantitySold != oldItem.QuantitySold
     select newItem).ToList();

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

var differentQuantityItems =
    (from newItem in newList
     join oldItem in oldList on newItem.ItemID equals oldItem.ItemID into oldItems
     from oldItem in oldItems.DefaultIfEmpty()
     where oldItem == null || newItem.QuantitySold != oldItem.QuantitySold
     select newItem).ToList();

В обоих случаях оператор соединения используется для быстрой корреляции элементов с одним и тем же ItemID. Затем вы можете сравнить количество проданных или любые другие свойства.

  • 0
    Привет спасибо за ответ Иван! :) По сравнению с IEqualityComparer, как быстро это будет работать? :)
  • 0
    @ User987 Откуда нам знать? Твоя мера.
Показать ещё 11 комментариев
1

Этот код будет выполняться менее чем за секунду, даже если совпадений не будет вообще (также менее чем за секунду, если все совпадения).

Он вернет все элементы, которые существуют в обоих списках (то есть тот же ItemID), но с другим QuantitySold ItemID.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp5
{
    class Program
    {
        public class Items
        {
            public string ItemID { get; set; }
            public int QuantitySold { get; set; }
        }

        static void Main(string[] args)
        {
            // Sample data
            var oldList = new List<Items>();
            oldList.AddRange(Enumerable.Range(0, 20000).Select(z => new Items() { ItemID = z.ToString(), QuantitySold = 4 }));

            var newList = new List<Items>();
            newList.AddRange(Enumerable.Range(0, 20000).Select(z => new Items() { ItemID = z.ToString(), QuantitySold = 5 }));

            var results = oldList.Join(newList,
                                            left => left.ItemID,
                                            right => right.ItemID,
                                            (left, right) => new { left, right })
                                .Where(z => z.left.QuantitySold != z.right.QuantitySold).Select(z => z.left);

            Console.WriteLine(results.Count());
            Console.ReadLine();
        }
    }
}

Использование z.left означает, что будет возвращен только один из элементов - если вы хотите использовать старый и новый, вместо этого используйте:

var results = oldList.Join(newList,
                                left => left.ItemID,
                                right => right.ItemID,
                                (left, right) => new { left, right })
                    .Where(z => z.left.QuantitySold != z.right.QuantitySold)
                    .Select(z => new[] { z.left, z.right })
                    .SelectMany(z => z);
  • 0
    Вы по сути копируете мой ответ с синтаксисом метода LINQ. Который, кстати, показывает, почему синтаксис запроса LINQ намного лучше читается, когда запрос содержит объединения.
  • 0
    Я вижу, как вы пришли к такому выводу @IvanStoev. Извиняюсь, если вы думаете, что я плагиат (я не сделал - мне просто понадобилось некоторое время, чтобы написать). Есть некоторые тонкие различия. Я рассмотрел сценарий того, что, если оба значения должны были быть возвращены. Я также предоставил некоторые примерные данные в самом вопросе, чтобы облегчить ОП тестирование. Тем не менее, как вы говорите, два решения очень похожи. Ваш ответ определенно хорош - вот почему я проголосовал за него.
1

С точки зрения сложности большой O, просто сравнение списков во вложенном цикле for будет в классе O (n * m), где n - это размер списка в БД, а m - размер списка. взято из API.

Чтобы повысить производительность, вы можете отсортировать два списка, которые будут стоить O (n log (n) + m log (m)), а затем вы сможете найти новые элементы в O (n + m). Следовательно, общая сложность вашего алгоритма будет тогда в классе O (n log (n) + m log (m)).

Здесь представление о времени, которое потребовалось бы, сравнивая квадратичное решение со сверхлинейным.

0

Вы можете подумать об использовании предложения Except с пользовательским написанным IEqualityComparer как IEqualityComparer ниже

var oldList = new List<Item>(); // oldList
var newList = new List<Item>(); // new list
var distinctList = newList.Except(oldList,new ItemEqualityComparer()).ToList();



class ItemEqualityComparer : IEqualityComparer<Item>
        {
            public bool Equals(Item i1, Item i2)
            {
                if (i1.ItemID == i2.ItemID && i1.QuantitySold != i2.QuantitySold)
                    return false;
                return true;
            }

            public int GetHashCode(Item item)
            {
                return item.ItemID.GetHashCode();
            }
        }

public class Item
        {
            public string ItemID { get; set; }
            public int QuantitySold { get; set; }
        }
  • 0
    @HimBromBeere, так что этот не будет работать?
  • 0
    @HimBromBeere хороший улов; выпрямляется.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню