1

我有一些非常简单的代码,我正在尝试使用 LINQ 而不是标准代码来稍微加快运行速度(代码周围点缀着许多这些小类型的调用,似乎在减慢速度)。

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

原始代码如下所示

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

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

4

3 回答 3

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 不会表现得更好,你的证据在哪里?(以下内容基于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 有利:

循环总数:99999900000

循环经过时间:25

LINQ 总数:99999900000

LINQ 已用时间:17

LINQ(糖语法)总计:99999900000

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

于 2013-06-11T21:52:24.500 回答
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是像 a 这样的 .NET 类型List<Crop>,那么您可以像这样创建一个类。)

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

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

于 2013-06-11T22:08:30.890 回答
1

这是一个非常直接的总和,所以我怀疑你会从使用 LINQ 中看到任何好处。

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

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

于 2013-06-11T22:18:34.950 回答