为了账号安全,请及时绑定邮箱和手机立即绑定

优化具有多个条件的嵌套 where 子句的最佳方法是什么?

优化具有多个条件的嵌套 where 子句的最佳方法是什么?

C#
陪伴而非守候 2023-04-29 16:35:41
ReturnItems我正在尝试查找退回的单个商品的数量超过该商品的原始订购数量的列表。所以这里有 2 个不同的对象列表 -IEnumerable<ReturnItem>和IEnumerable<OrderItem>. 问题在于,根据进行退货的来源(我们的工作流程中有多个地方可以进行退货),给ItemNumber定的 on aReturnItem可能为空。在这种情况下,我们需要依靠 来ReturnItem.OrderItemId将它与OrderItem.我已经使用 LINQ 解决了这个问题,但它需要一个嵌套的 for 循环(在引擎盖下)所以我试图尽可能避免这种情况同时保持可读性。换句话说,我想避免 O(N ^2) 并寻找 O(N) 或更好但又一次,同时保持可读性(我知道我在这里要求很多但我想我会看看是否有人有创造性的解决方案)。我创建了一个解决方案,其中我有两个用于订单项的字典。其中一个,键是项目编号,另一个键是订单项目 ID。这有效并解决了性能问题,但我完全失去了可读性。这是我的原始 LINQ 语句:// ItemsForReturn = IEnumerable<ReturnItem>// OrderItems = IEnumerable<OrderItem>var invalidQuantityItems = message.ItemsForReturn.Where(returnItem =>{    var matchingOrderItemQuantity = message.OrderItems        .Where(orderItem => orderItem.ItemNumber.Equals(returnItem.ItemNumber) || orderItem.OrderItemId == returnItem.OrderItemId)        .Sum(orderItem => orderItem.Quantity);    return matchingOrderItemQuantity < returnItem.Quantity;});以及上面使用的变量的相应类型:public class ReturnItem{    public int OrderItemId {get; set;}    public string ItemNumber {get; set;}    public int Quantity {get; set;}    // There's more properties but these are the ones that matter{public class OrderItem{    public int OrderItemId {get; set;}    public string ItemNumber {get; set;}    public int Quantity {get; set;}    // There's more properties but these are the ones that matter{我希望这var invalidQuantityItems将是一个IEnumerable<ReturnItems>其单个项目的数量大于订购的数量(即他们试图返回比他们首先订购的更多)。
查看完整描述

3 回答

?
幕布斯7119047

TA贡献1794条经验 获得超8个赞

小修正 - 当前实现的时间复杂度是 O(N*M),你能得到的最好的是 O(N+M)。


问题是如何有效地关联这两个集合。在 LINQ 中,这是通过连接实现的,对于这种一对多类型的关联 -组连接。标准的等价物||将是两个组连接(匹配集)结果的联合。


谈到可读性、LINQ 和连接,最好的方法是使用 LINQ查询语法(有些人也称它为理解语法是有原因的)。


所以有问题的查询可以有效地(并且希望可读)重写如下:


var invalidQuantityItems =

    from returnItem in message.ItemsForReturn

    join orderItem in message.OrderItems on returnItem.ItemNumber equals orderItem.ItemNumber

    into matchingOrderItems1

    join orderItem in message.OrderItems on returnItem.OrderItemId equals orderItem.OrderItemId

    into matchingOrderItems2

    let matchingOrderItemQuantity = matchingOrderItems1.Union(matchingOrderItems2)

        .Sum(orderItem => orderItem.Quantity)

    where matchingOrderItemQuantity < returnItem.Quantity

    select returnItem;


查看完整回答
反对 回复 2023-04-29
?
喵喵时光机

TA贡献1846条经验 获得超7个赞

我认为字典方法是最好的方法。


关于可读性,我认为这应该不会太差:


var quantityByItemNumber = message.OrderItems.

    Where(i => i.ItemNumber != null).

    ToDictionary(

        i => i.ItemNumber,

        i => i.Quantity);


var quantityByOrderItemId = message.OrderItems.ToDictionary(

    i => i.OrderItemId,

    i => i.Quantity);


var invalidQuantityItems = message.ItemsForReturn.Where(returnItem =>

{

    int matchingOrderItemQuantity;

    var isNumberMatch = returnItem.ItemNumber != null) &&

        quantityByItemNumber.TryGetValue(returnItem.ItemNumber, out matchingOrderItemQuantity);


    if (!isNumberMatch)

        quantityByOrderItemId.TryGetValue(returnItem.OrderItemId, out matchingOrderItemQuantity)


    return matchingOrderItemQuantity < returnItem.Quantity;

});

事实上,我认为这更具可读性,因为它不会错误地假装有不止一个匹配OrderItem,必须对哪些数量求和。


查看完整回答
反对 回复 2023-04-29
?
幕布斯6054654

TA贡献1876条经验 获得超7个赞

就优化多个条件而言:

  1. 始终将最有可能结束评估的条件放在首位(您必须根据现有数据或您对系统的了解来确定这一点)。

  2. 如果一种情况比另一种情况更频繁地发生的可能性不大,那么我们可以考虑评估本身。例如,如果int比较比比较快string,则将int比较放在第一位。

此外,您的代码不需要单独的行来获取Sum; 你可以用同一个表达式来做:

var invalidQuantityItems = message.ItemsForReturn.Where(returnItem =>

    message.OrderItems

        .Where(orderItem =>

            orderItem.OrderItemId == returnItem.OrderItemId ||

            orderItem.ItemNumber.Equals(returnItem.ItemNumber))

        .Sum(orderItem => orderItem.Quantity) < returnItem.Quantity);


查看完整回答
反对 回复 2023-04-29
  • 3 回答
  • 0 关注
  • 129 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信