【问题标题】:Un-Nesting List Iterations for Performance取消嵌套列表迭代以提高性能
【发布时间】:2011-09-29 04:44:58
【问题描述】:

我有几个列表需要遍历才能执行计算。总之,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 是我计算的结果,它将包含 List1 中道路段在一天中每 15 分钟的平均速度。 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。

【问题讨论】:

    标签: c# performance linq


    【解决方案1】:

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

    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 次,但它比迭代整个样本列表要好得多。这应该会让你走上正确的道路,希望你能进一步优化最后一点。

    【讨论】:

    • 不错,但它不再是 C#。我觉得很难阅读——是我吗?我缺乏 LINQ 经验和培训吗?还是在如此长而复杂的查询中存在真正的概念问题?
    • @Amittai Shapira:那是合法的 C# - 我不确定你的意思。我发现这比 OP 指定的等效循环更容易阅读;变量、可变列表、if 语句等更少。它还说明了它在做什么(加入),而我不必破译一堆逻辑来找出它的含义。这就是我喜欢 LINQ 的原因:强调 what 而淡化 how
    • 我喜欢这个解决方案,因为我认为它优雅且易于维护。我将如何处理我的 Where 条件消除了所有可能的样本,从而导致我的平均计算失败的情况,因为该序列不包含任何元素?
    • @kittyhawk:我修改了我的答案,以说明特定 15 分钟内的空样本集。我假设您希望样本集为空时的平均速度为 0。
    • @BryanWatts:谢谢!此外,如果样本数量少于一定数量(比如 2),我还想为平均值返回 0 值怎么办?
    【解决方案2】:

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

    其次,我建议不要对子列表进行嵌套迭代,而是将列表迭代一次并创建一个 List&lt;IndividualSpeeds&gt; 的字典,其中包含 StartId、EndId、StartHour 和 EndHour 的 Composite Key。在此词典中进行查找将快得多,而不是为每个 RoadwaySegment 重新迭代。

    【讨论】:

      【解决方案3】:

      以下是对 Chris 和 Bryan 答案的补充:
      既然您说可能有数百万个速度样本记录,您只是不想迭代它们超过可能的最小值 - 即您应该根据它们的开始对它们进行分组(使用 GroupByJoin 运算符)小时和分钟。您可以只迭代每个组,并将每个示例记录添加到 RoadwaySegments 的某个字典(类似于 Dictionary&lt;RoadwaySegment, IEnumerable&lt;IndividalSpeeds&gt;&gt;)中,然后从该字典创建摘要项。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-24
        • 2022-01-02
        • 2013-01-29
        • 1970-01-01
        • 2011-10-14
        • 1970-01-01
        相关资源
        最近更新 更多