У меня есть два больших списка элементов, класс которых выглядит следующим образом (оба списка одного типа):
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 также вернет эти элементы как различные элементы количества, они действительно есть, но их нет в старом списке. Поэтому их не следует включать во второй список.,
Это идеальный кандидат для 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. Затем вы можете сравнить количество проданных или любые другие свойства.
Этот код будет выполняться менее чем за секунду, даже если совпадений не будет вообще (также менее чем за секунду, если все совпадения).
Он вернет все элементы, которые существуют в обоих списках (то есть тот же 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);
С точки зрения сложности большой 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)).
Здесь представление о времени, которое потребовалось бы, сравнивая квадратичное решение со сверхлинейным.
Вы можете подумать об использовании предложения 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; }
}