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

时间:2019-06-21 16:26:29

标签: c# .net algorithm performance

我正在尝试找到ReturnItems的列表,该列表中退回的单个商品的数量大于该商品的原始订购数量。因此,这里有2个不同的对象列表在起作用-IEnumerable<ReturnItem>IEnumerable<OrderItem>。问题是,取决于进行退货的来源(我们的工作流中有多个地方可以进行退货),给定ItemNumber上的ReturnItem可能为空。在这种情况下,我们将需要依靠ReturnItem.OrderItemId将其与OrderItem进行匹配。

我已经使用LINQ解决了这个问题,但是它需要一个嵌套的for循环(在幕后),因此我试图避免这种情况,同时还要保持可读性。换句话说,我想避免运行时间为O(N ^ 2)并寻找O(N)或更好,但是在保持可读性的同时(我知道我在这里要求很多,但我想知道是否有人有创意的解决方案)。我创建了一个解决方案,其中针对订单项有两个字典。其中一个,关键是商品编号,另一个关键是订单商品编号。这样可以解决性能问题,但是我完全失去了可读性。

这是我原来的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>,其单个商品的数量大于所订购的数量(即,他们试图退回的商品比订购的商品多)。 / p>

干杯!

3 个答案:

答案 0 :(得分:3)

小校正-当前实现的时间复杂度为O(N * M),而您可以获得的最好结果是O(N + M)。

问题在于如何有效地关联这两个集合。在LINQ中,这是通过joins来实现的,并且对于这种一对多的相关性-group join。相当于a.out的条件将是两个组联接(匹配集)的结果中的Union

谈到可读性,LINQ和联接,最好的方法是使用LINQ query 语法(有些人也将其称为 comprehension 语法)。

因此,相关查询可以有效地(并且希望可读)进行如下重写:

LD_RUNPATH_SEARCH_PATHS

答案 1 :(得分:1)

我认为词典方法是最好的选择。

关于可读性,我认为这应该还不错:

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,必须对这些数量求和。

答案 2 :(得分:0)

就优化多个条件而言:

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