取消嵌套列表性能的迭代

时间:2011-09-27 18:22:35

标签: c# performance linq

我有几个列表,我需要迭代才能执行计算。总之,List1是道路起点和终点(ID)的列表,List2是这些终点的各个速度样本的列表(每组终点有多个速度样本)。 List1的定义如下:

class RoadwaySegment
{ 
   public int StartId {get; set;} 
   public int EndId {get; set;}
}

List2的定义如下:

class IndividualSpeeds
{ 
   public int StartHour {get; set;} 
   public int StartMin {get; set;} //either 0,15,30,or 45
   public int Speed {get; set;} 
   public int StartId {get; set;} 
   public int EndId {get; set;}
}

List3是我计算的结果,将包含每天15分钟的List1中道路段的平均速度。 List3看起来像这样:

class SummaryData
{ 
  public string SummaryHour {get; set;} 
  public string SummaryMin {get; set;} 
  public int StartId {get; set;} 
  public int EndId {get; set;} 
  public int AvgSpeed {get; set;}
}

目前,为了生成List3,我遍历List1,然后遍历每天的24小时,然后每隔15分钟一小时。对于这些迭代中的每一个,我检查List2中的单个速度样本是否应该包含在我的道路段的平均速度计算中。所以,它看起来像这样:

var summaryList = new List<SummaryData>();
foreach (var segment in RoadwaySegments)
{
   for(int startHour = 0; startHour < 24; startHour++)
   {
      for(int startMin = 0; startMin < 60; startMin+= 15)
      {
         int totalSpeeds = 0;
         int numSamples = 0;
         int avgSpeed = 0;

         foreach(var speedSample in IndividualSpeeds)
         {
            if((segment.StartId == speedSample.StartId)&&(segment.EndId == speedSample.EndId)&&(speedSample.StartHour == startHour)&&(speedSample.StartMin == startMin))
            {
               if(speedSample.Speed > 0)
               {
                  totalSpeeds += speedSample.Speed;
                  numSamples += 1;
               }
            }
         }
         SummaryData summaryItem = new SummaryData {SummaryHour = startHour, SummaryMin = startMin, StartId = segment.StartId, EndId = segment.EndId, AvgSpeed = totalSpeeds/numSamples;
         summaryList.Add(summaryItem);
      }
   }
}

此代码的问题是List1可能有一百个道路段,但List2可能包含一百万或更多的速度样本记录,因此列表的子迭代非常耗时。有没有办法使用GroupBy / LINQ来提高此代码的性能和可读性?注意包含平均速度的条件 - 它必须大于0。

3 个答案:

答案 0 :(得分:3)

这是未经测试的,但我认为它会起作用:

from segment in RoadwaySegments
join sample in IndividualSpeeds on new { segment.StartId, segment.EndId } equals new { sample.StartId, sample.EndId } into segmentSamples
from startHour in Enumerable.Range(0, 24)
from startMin in new[] { 0, 15, 30, 45 }
let startMinSamples = segmentSamples
    .Where(sample => sample.Speed > 0)
    .Where(sample => sample.StartHour == startHour)
    .Where(sample => sample.StartMin == startMin)
    .Select(sample => sample.Speed)
    .ToList()
select new SummaryItem
{
    StartId = segment.StartId,
    EndId = segment.EndId,
    SummaryHour = startHour,
    SummaryMin = minute,
    AvgSpeed = startMinSamples.Count <= 2 ? 0 : startMinSamples.Average()
};

主要思想是迭代段和样本列表一次,为每个段生成一组样本。然后,对于每个组,您将为每个组合生成小时和分钟以及摘要项。最后,计算小时/分钟组合中所有非零样本的平均速度。

这不太理想,因为您仍然会将段的样本迭代24 * 4次,但它比迭代整个样本列表要好得多。这应该让你走上正确的道路,希望你能进一步优化最后一点。

答案 1 :(得分:2)

如果您使用的是.net 4,我建议将其与Parallel Linq并行化。这在RoadwaySegments上是令人尴尬的平行。

其次,我不建议对子列表进行嵌套迭代,而是建议迭代该列表一次并创建一个List<IndividualSpeeds>字典,其中包含Composite Key的StartId,EndId,StartHour和EndHour。在这个词典中进行查找将更快,而不是重复迭代每个RoadwaySegment。

答案 2 :(得分:0)

以下是克里斯和布莱恩回答的补充说明:
既然你说可能有数百万个速度样本记录,你只是不希望迭代它们的次数超过最小值 - 即你应该对它们进行分组(使用GroupByJoin运算符),根据他们的开始时间和分钟。比你可以迭代每个组,并将每个样本记录添加到RoadwaySegments的某些字典(类似Dictionary<RoadwaySegment, IEnumerable<IndividalSpeeds>>)并从该字典创建摘要项目。