【问题标题】:What is the best way to optimize a nested where clause that has multiple conditions?优化具有多个条件的嵌套 where 子句的最佳方法是什么?
【发布时间】:2019-11-04 12:44:04
【问题描述】:

我正在尝试查找ReturnItems 的列表,其中退回的单个商品的数量超过了该商品的原始订购数量。所以这里有 2 个不同的对象列表 - IEnumerable<ReturnItem>IEnumerable<OrderItem>。问题在于,根据退货的来源(我们的工作流程中有多个可以退货的地方),给定ReturnItem 上的ItemNumber 可能为空。在这种情况下,我们需要依靠ReturnItem.OrderItemId 来匹配OrderItem

我已经使用 LINQ 解决了这个问题,但它需要一个嵌套的 for 循环(在引擎盖下),所以我尽量避免这种情况,同时还要保持可读性。换句话说,我想避免运行时间O(N^2) 并寻找 O(N) 或更好,但同时保持可读性(我知道我在这里要求很多,但我想我会看看是否有人有创造性的解决方案)。我创建了一个解决方案,其中我有两个用于订单项的字典。其中一个,key是item number,另一个key是order Item 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&lt;ReturnItems&gt;,其单个商品的数量大于订购的数量(即,他们试图返回的数量超过他们最初订购的数量)。

干杯!

【问题讨论】:

  • 一般来说,您可以通过将最有可能结束评估的条件放在首位来优化多个条件。
  • 同意@Ru​​fusL 你希望你最有可能首先是错误的条件,但至于优化,我不会太担心,SQL 查询规划器将处理大部分的你,前提是你的索引设置正确。
  • 为什么不总是使用int OrderItemId 字段进行比较?
  • 因为根据返回创建的来源,它可能不存在。 IE。有时商品编号和订单商品 ID 都存在,有时是其中之一。
  • OrderItem 是如何使用的? OrderItemIdItemNumber 有区别吗?

标签: c# .net algorithm performance


【解决方案1】:

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

问题是如何有效地关联这两组。在 LINQ 中,这是通过 joins 实现的,对于这种一对多类型的关联 - group join|| 条件的等效项将是两个组连接(匹配集)的结果的Union

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

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

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;

【讨论】:

  • 如果我错了,请纠正我,但如果 MN 可以是相同的大小,那么复杂性不是 O(N^2)
【解决方案2】:

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

关于可读性,我觉得应该还不错:

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,而这些数量必须相加。

【讨论】:

  • 如果我错了,请纠正我,但如果提供的密钥是nullToDictionary() 不会抛出异常吗? IE。如果i.ItemNumbernullToDictionary(i =&gt; i.ItemNumber,... 将不起作用?除此之外,这很棒。
  • 你说得对,调整了我的答案。但我在想至少OrderItem 会被完全填满。
【解决方案3】:

就优化多个条件而言:

  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);

【讨论】:

  • 将最可能的情况放在首位是一个很好的观点。但是,我一直在寻找如何避免执行嵌套的 for 循环(因为这反过来又会评估为嵌套的 foreach 循环)。如果可能的话,我想将复杂度降低到 O(N)。
猜你喜欢
  • 2012-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
  • 2022-01-26
  • 1970-01-01
相关资源
最近更新 更多