用LINQ替换foreach

时间:2013-06-11 21:43:30

标签: linq c#-4.0

我有一些非常简单的代码,我试图使用LINQ而不是标准代码来快速运行(有很多这些小类型的调用遍布代码,这似乎会减慢速度)。 / p>

问题是这样的 - 我在LINQ之外有一个变量,LINQ查询的结果需要添加它。

原始代码如下所示

double total = 0
foreach(Crop c in p.Crops)
{
    if (c.CropType.Type == t.Type)
       total += c.Area;
}
return total;

这个方法在循环开始变大之前并不慢,然后在手机上变慢。这种代码可以转移到一个相对快速和简单的LINQ吗?

3 个答案:

答案 0 :(得分:3)

看起来你可以使用sum :(编辑:我的语法错误)

total = (from c in p.Crops
            where c.CropType.Type == t.Type
            select c.Area).Sum();

或者以扩展方式格式:

total = p.Crops.Where(c => c.CropType.Type == t.Type).Sum(c => c.area);

至于那些说LINQ不会表现得更好的人,你的证据在哪里? (以下是基于post from Hanselman?我在linqpad中运行了以下内容:(您需要下载并参考nbuilder才能运行它)

void Main()
{
    //Nbuilder is used to create a chunk of sample data
    //http://nbuilder.org
    var crops = Builder<Crop>.CreateListOfSize(1000000).Build();
    var t = new Crop();
    t.Type = Type.grain;

    double total = 0;

    var sw = new Stopwatch();
    sw.Start();

    foreach(Crop c in crops)
    {
        if (c.Type == t.Type)
            total += c.area;
    }
    sw.Stop();
    total.Dump("For Loop total:");
    sw.ElapsedMilliseconds.Dump("For Loop Elapsed Time:");


    sw.Restart();
    var result = crops.Where(c => c.Type == t.Type).Sum(c => c.area);
    sw.Stop();

    result.Dump("LINQ total:");
    sw.ElapsedMilliseconds.Dump("LINQ Elapsed Time:");


    sw.Restart();
    var result2 = (from c in crops
            where c.Type == t.Type
            select c.area).Sum();

    result.Dump("LINQ (sugar syntax) total:");
    sw.ElapsedMilliseconds.Dump("LINQ (sugar syntax) Elapsed Time:");
}


public enum Type
{
    wheat,
    grain,
    corn,
    maize,
    cotton
}

public class Crop
{
    public string Name { get; set; }
    public Type Type { get; set; }
    public double area;
}

结果对LINQ非常有利:

  

For Loop total:99999900000

     

For Loop Elapsed Time:25

     

LINQ总计:99999900000

     

LINQ经过的时间:17

     

LINQ(糖语法)总计:99999900000

     

LINQ(糖语法)经过时间:17

答案 1 :(得分:1)

优化此功能的主要方法是更改​​p,这可能会也可能不会。

假设pP,看起来像这样:

internal sealed class P
{
   private readonly List<Crop> mCrops = new List<Crop>();

   public IEnumerable<Crop> Crops { get { return mCrops; } }

   public void Add(Crop pCrop)
   {
      mCrops.Add(pCrop);
   }
}

(如果p是类似List<Crop>的.NET类型,则可以创建这样的类。)

您可以通过维护字典来优化循环:

internal sealed class P
{
   private readonly List<Crop> mCrops = new List<Crop>();

   private readonly Dictionary<Type, List<Crop>> mCropsByType
      = new Dictionary<Type, List<Crop>>();

   public IEnumerable<Crop> Crops { get { return mCrops; } }

   public void Add(Crop pCrop)
   {
      if (!mCropsByType.ContainsKey(pCrop.CropType.Type))
         mCropsByType.Add(pCrop.CropType.Type, new List<Crop>());

      mCropsByType[pCrop.CropType.Type].Add(pCrop);
      mCrops.Add(pCrop);
   }

   public IEnumerable<Crop> GetCropsByType(Type pType)
   {
      return mCropsByType.ContainsKey(pType)
         ? mCropsByType[pType]
         : Enumerable.Empty<Crop>();
   }
}

您的代码将变为:

double total = 0
foreach(Crop crop in p.GetCropsByType(t.Type))
   total += crop.Area;

return total;

另一种可能更快的可能性是:

internal sealed class P
{
   private readonly List<Crop> mCrops = new List<Crop>();

   private double mTotalArea;

   public IEnumerable<Crop> Crops { get { return mCrops; } }

   public double TotalArea { get { return mTotalArea; } }

   public void Add(Crop pCrop)
   {   
      mCrops.Add(pCrop);
      mTotalArea += pCrop.Area;
   }
}

您的代码将只是访问TotalArea属性,您甚至不需要循环:

return p.TotalArea;

您可能还会考虑将管理Crops数据的代码提取到单独的类中,具体取决于P

答案 2 :(得分:1)

这是一个相当直接的总和,所以我怀疑你会看到使用LINQ带来的任何好处。

你还没有告诉我们很多关于这里的设置,但这是一个想法。如果p.Crops很大且序列中只有少数项目属于所需类型,则可以构建另一个仅包含所需项目的序列。

我假设您在插入p.Crops时知道类型。如果是这种情况,您可以轻松地将相关项插入另一个集合中,并将其用于sum循环。这将减少N并摆脱比较。它仍然是O(N)。