LINQ - 按相同功能过滤和排序的最有效方法
Posted
技术标签:
【中文标题】LINQ - 按相同功能过滤和排序的最有效方法【英文标题】:LINQ - most efficent way to filter and sort by same function 【发布时间】:2019-10-07 17:55:05 【问题描述】:我正在寻找一种有效的方法来按函数对集合进行排序,同时根据对所述函数的评估过滤集合。举例说明:
var materialsByExpiry = from m in materials
where m.ExpiryDate() >= today
orderby m.ExpiryDate()
select m;
由于materials
是一个大型集合,而ExpiryDate
是一个重要的计算,我想尽量减少对ExpiryDate
的调用次数。显然ExpiryDate
只需要为每种材料调用一次,但这段代码调用它 n + q 次,其中 n 是总数元素的数量,q 是通过过滤器的数量。
我可以看到一种可能性是定义一个结构来存储材料及其到期日期并使用它。但是有没有更有效的方法(不用花时间制定我自己的排序/过滤算法)?
【问题讨论】:
如果您从materials
中选择m
,为什么需要orderby
?如果ExpiryDate
只需要计算一次,是不是最好运行一个单独的循环,在linq之前计算和更新一个属性?
为什么不在 ExpiryDate 函数上挂一个惰性求值器(LazyExpiryDate
是有原因的吗?它可以在运行时改变吗?
@MongZhu 这是系统的工作方式。 materials
来自我正在编写的代码之外,我需要调用其类的方法来提取我们排序/过滤的值。
【参考方案1】:
你可以使用let关键字来临时保存ExpiryDate()
返回的值
var materialsByExpiry = from m in materials
let date = m.ExpiryDate()
where date >= today
orderby date
select m;
这会将调用次数减少到Where
子句所需的次数。
这是 LINQPad 的示例:
void Main()
DateTime today = DateTime.Now;
List<Material> materials = new List<UserQuery.Material>();
materials.Add(new Material Hours = 6 );
materials.Add(new Material Hours = 2 );
materials.Add(new Material Hours = -6 );
materials.Add(new Material Hours = -6 );
materials.Add(new Material Hours = 4 );
// normal version
var materialsByExpiry = (from m in materials
where m.ExpiryDate() >= today
orderby m.ExpiryDate()
select m).ToList();
Material.count.Dump("COUNT normal");
Material.count = 0;
// LET version
var materialsByExpiry_Let = (from m in materials
let date = m.ExpiryDate()
where date >= today
orderby date
select m).ToList();
Material.count.Dump("COUNT using LET");
materialsByExpiry.Dump();
materialsByExpiry_Let.Dump();
public class Material
public static int count = 0;
public int Hours get; set;
public DateTime ExpiryDate()
count++;
return DateTime.Now.AddHours(Hours);
这是比较输出:
【讨论】:
谢谢!let
简单、直接,并且加快了速度。我很想知道它在脱糖形式中是什么样子(写成 LINQ 方法调用而不是花哨的 LINQ 语法)。我在 System.Linq 中没有看到 Let 方法。
@Stewart 不客气。我猜它在 lambda like this 中看起来并不那么花哨;)它看起来也会在整个集合中重复 1 次。【参考方案2】:
您可以创建一个通用扩展方法来调用计算密集型方法,并将输出与对象本身一起存储在 ValueTuple
中。
public static IEnumerable<(TSource Item, TProperty1 Property1)>
Precompute<TSource, TProperty1>(this IEnumerable<TSource> source,
Func<TSource, TProperty1> property1Selector)
return source.Select(item => (item, property1Selector(item)));
那么你可以像这样使用它:
var materialsByExpiry = from m in materials.Precompute(m => m.ExpiryDate())
where m.Property1 >= today
orderby m.Property1
select m.Item;
虽然命名很尴尬。您不能以这种方式为元组的属性赋予有意义的名称。
【讨论】:
以上是关于LINQ - 按相同功能过滤和排序的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章