我有两个系列,每个系列包含大约40,000个项目。
列表2中的元素通过外键链接到列表1的元素。
对于列表一的每个元素,我想在列表二中找到相应的元素。
这样的事情:
foreach(var item in list1)
{
var match = list2.Where(child => child.ID == item.ChildID).FirstOrDefault();
item.Child = match;
}
这很有效,但它很慢。
现在,list1和list 2都是按照这些键从数据库中排序的。因此list1按ChildID排序,list2按ID排序(相同值)。
我认为二进制搜索会大大提高速度,但我在某处读到Linq会在Where子句中为列表选择最合适的策略。也许我需要明确地转换为排序列表?或者我可能需要实现一个带比较器的自定义二进制搜索算法?
任何见解都表示赞赏。
感谢。
答案 0 :(得分:11)
为什么不使用加入?
var query =
from a in list1
join b in list2 on a.ChildID equals b.ID
select new {Item1 = a, Item2 = b};
foreach(var item in query)
{
item.Item1.Child = item.Item2;
}
答案 1 :(得分:1)
之前我遇到过这个问题,基于LINQ的搜索与基于DB的搜索相比非常慢,因为它没有使用任何索引。
您是否考虑过使用Dictionary代替List?
您可以实现字典,然后使用ContainsKey而不是使用Where,如果确实存在,请使用index accessor获取值。
示例代码:
Dictionary<int, Child> list2 = ...;
...
foreach(var item in list1)
{
if (list2.ContainsKey(item.ChildID))
item.Child = list2[item.ChildID];
}
使用索引进行访问将比搜索列表快得多,因为索引需要额外内存的成本。
答案 2 :(得分:1)
这个怎么样:
var joined = list1.Join(list2, x => x.ChildID, x => x.ID, (x, y) => new { x, y });
foreach (var j in joined)
{
j.x.Child = j.y;
}
答案 3 :(得分:1)
由于两个列表都按相同的值排序,您可以并行循环:
int index1 = 0, index2 = 0;
while (index1 < list1.Count && index2 < list2.Count) {
while (index1 < list1.Count && list1[index1].ChildId < list2[index2].Id) index1++;
if (index1 < list1.Count) {
while (index2 < list2.Count && list2[index2].Id < list1[index1].ChildId) index2++;
if (index2 < list2.Count && list1[index1].ChildId == list2[index2].Id) {
list1[index].Child = list2[index2];
index1++;
index2++;
}
}
}
或:
int index1 = 0, index2 = 0;
while (index1 < list1.Count && index2 < list2.Count) {
if (list1[index1].ChildId == list2[index2].Id) {
list1[index].Child = list2[index2];
index1++;
index2++;
} else {
if (list1[index1].ChildId < list2[index2].Id) {
index1++;
} else {
index2++;
}
}
}
另一个有效的替代方案,但没有利用列表的顺序,是通过将一个列表放在字典中来创建索引:
Dictionary<int, TypeOfChild> index = new Dictionary<int, TypeOfChild>();
foreach (TypeOfChild child in list2) {
index.Add(child.Id, child);
}
foreach (TypeOfParent parent in list1) {
TypeOfChild child;
if (index.TryGetValue(parent.ChildId, out child) {
parent.Child = child;
}
}
答案 4 :(得分:1)
我忍不住回答: - )
您的代码变慢的主要原因是您的项目将被多次readed。速度的艺术是:只在您需要时才读取存储器,如果您需要阅读它,请尽量少读它。
以下是一个例子:
public class Item
{
private int _id;
private List<ItemDetails> _detailItems = new List<ItemDetails>();
public Item(int id)<br>
{
_id = id;
}
public void AddItemDetail(ItemDetails itemDetail)
{
_detailItems.Add(itemDetail);
}
public int Id
{
get { return _id; }
}
public ReadOnlyCollection<ItemDetails> DetailItems
{
get { return _detailItems.AsReadOnly(); }
}
}
public class ItemDetails
{
private int _parentId;
public ItemDetails(int parentId)
{
_parentId = parentId;
}
public int ParentId
{
get { return _parentId; }
}
}
主要目标是扫描列表并比较当前索引上的item和itemdetail。当parentid等于它的父母id。将其添加到列表中,然后继续下一个细节。如果不同则继续下一个父母。
// for performance tests..
DateTime startDateTime;
// create 2 lists (master/child list)
List<Item> itemList = new List<Item>();
List<ItemDetails> itemDetailList = new List<ItemDetails>();
Debug.WriteLine("# Adding items");
startDateTime = DateTime.Now;
// add items (sorted)
for (int i = 0; i < 400000; i++)
itemList.Add(new Item(i));
// show how long it took
Debug.WriteLine("Total milliseconds: " + (DateTime.Now - startDateTime).TotalMilliseconds.ToString("0") + "ms" );
// adding some random details (also sorted)
Debug.WriteLine("# Adding itemdetails");
Random rnd = new Random(DateTime.Now.Millisecond);
startDateTime = DateTime.Now;
int index = 0;
for (int i = 0; i < 800000; i++)
{
// when the random number is bigger than 2, index will be increased by 1
index += rnd.Next(5) > 2 ? 1 : 0;
itemDetailList.Add(new ItemDetails(index));
}
Debug.WriteLine("Total milliseconds: " + (DateTime.Now - startDateTime).TotalMilliseconds.ToString("0") + "ms");
// show how many items the lists contains
Debug.WriteLine("ItemList Count: " + itemList.Count());
Debug.WriteLine("ItemDetailList Count: " + itemDetailList.Count());
// matching items
Debug.WriteLine("# Matching items");
startDateTime = DateTime.Now;
int itemIndex = 0;
int itemDetailIndex = 0;
int itemMaxIndex = itemList.Count;
int itemDetailMaxIndex = itemDetailList.Count;
// while we didn't reach any end of the lists, continue...
while ((itemIndex < itemMaxIndex) && (itemDetailIndex < itemDetailMaxIndex))
{
// if the detail.parentid matches the item.id. add it to the list.
if (itemList[itemIndex].Id == itemDetailList[itemDetailIndex].ParentId)
{
itemList[itemIndex].AddItemDetail(itemDetailList[itemDetailIndex]);
// increase the detail index.
itemDetailIndex++;
}
else
// the detail.parentid didn't matches the item.id so check the next 1
itemIndex++;
}
Debug.WriteLine("Total milliseconds: " + (DateTime.Now - startDateTime).TotalMilliseconds.ToString("0") + "ms");
我花了10倍以上的项目才能看到更好的结果:
添加项目:
总毫秒:140毫秒
添加itemdetails:
总毫秒:203毫秒
ItemList数量:400000
ItemDetailList数量:800000
匹配项目:
总毫秒:265毫秒
这是快速打字,可能更清洁。所以我希望你能读懂它。玩它。
格尔茨, 的Jeroen。
答案 5 :(得分:0)
不确定这是否会实际加速,但你可以将谓词移到FirstOrDefault()子句中并完全摆脱它。
item.Child = list2.FirstOrDefault(child => child.ID == item.ChildID)
它可能实际上没有帮助,因为这可能隐含地只调用Where()。
这是一个问题,这个方法实际上是慢还是只有在编译后第一次运行时它才会变慢?查看讨论on this post。