【问题标题】:Get dates when quantity were out of stock获取数量缺货的日期
【发布时间】:2014-12-08 21:31:14
【问题描述】:

尝试使用 LINQ C# 找出当数量缺货时如何获得日期范围结果

假设我有一个看起来像这样的表格结果

EventDate  | Qty
2014-02-03 | 6
2014-02-04 | -1
2014-02-05 | -2
2014-02-06 | 2
2014-02-07 | -1
2014-02-08 | -2
2014-02-09 | -3
2014-02-10 | 5

现在我想像这样获得库存数量为负0时的日期范围

FromDate   | ToDate
2014-02-04 | 2014-02-05
2014-02-07 | 2014-02-09

有人可以帮助我如何实现吗?

更新

我知道我可以通过乘以查询来做到这一点,但如果可能的话,我想只在一个 LINQ 查询中做到这一点。

【问题讨论】:

  • 如果你把问题分解成小部分,应该不会太难。首先获取 qty 为负数的日期(Linq 的 .Where() 函数),然后将结果传递给返回类型为 List<OutOfStockRange> 并有一个类 class OutOfStockRange { public Datetime FromDate {get; set;} public DateTime ToDate {get; set;}} 的自定义函数您的自定义函数只需要找到连续区域和创建一个新的OutOfStockRange 并将其添加到要返回的列表中。
  • @mason 我正在尝试在一个 linq 查询中做到这一点,这可能吗?
  • 您可以将假设的自定义函数链接到您的 Linq 查询中。你也可以避免使用自定义函数,但我只擅长基本的 Linq 东西。
  • 与论坛网站不同,我们不使用“谢谢”、“任何帮助表示赞赏”或Stack Overflow 上的签名。请参阅“Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?。此外,“提前感谢”是没有意义的。

标签: c# linq


【解决方案1】:

对于仅使用内置函数的替代方法,

这里的策略是选择数量小于零的所有日期,并为每个日期执行一个子查询,该子查询构建一个包含当前日期之后数量也小于零的所有日期的列表。使用TakeWhile,这将在下一个日期之前以非负数停止。然后取其中的最大值,这对于范围的结束日期是正确的。最后一步是 GroupBy 删除映射到同一结束日期的“缺货”范围开始后的所有天数,从而为您提供不同的缺货日期范围。

如下所示,它依赖于输入时按时间顺序排序的库存水平。

public class StockLevel
{
    public DateTime Date { get; set; }
    public int Quantity { get; set; }                        
}

static void Main(string[] args)
{
    List<StockLevel> stockLevels = new List<StockLevel>()
    { 
        new StockLevel() { Date = DateTime.Parse("03-Feb-2014"), Quantity = 6 },
        new StockLevel() { Date = DateTime.Parse("04-Feb-2014"), Quantity = -1 },
        new StockLevel() { Date = DateTime.Parse("05-Feb-2014"), Quantity = -2 },
        new StockLevel() { Date = DateTime.Parse("06-Feb-2014"), Quantity = 2 },
        new StockLevel() { Date = DateTime.Parse("07-Feb-2014"), Quantity = -1 },
        new StockLevel() { Date = DateTime.Parse("08-Feb-2014"), Quantity = -2 },
        new StockLevel() { Date = DateTime.Parse("09-Feb-2014"), Quantity = -3 },
        new StockLevel() { Date = DateTime.Parse("10-Feb-2014"), Quantity = 5 },
    };

    var outOfStockDates = stockLevels
        .Where(a => a.Quantity < 0)
        .Select(a => new 
        { 
                S1 = a.Date, 
                S2 = stockLevels
                        .Where(c => c.Date >= a.Date)
                        .TakeWhile(b => b.Quantity < 0)
                        .Select(b => b.Date).Max() 
        })
        .GroupBy(a => a.S2, a => a.S1, (S2, S1S) => new { FromDate = S1S.Min(), ToDate = S2 });

    Console.ReadKey();
}

【讨论】:

  • 这似乎在起作用,是的,这算作一个查询:),很棒的工作。现在为我的实际代码测试它。
  • @believeme 有没有办法让你有这样的事情? 2014-02-03 | 6, 2014-02-03 | 3, 2014-02-03 | -1, 2014-02-03 | -2, 2014-02-03 | 1, 2014-02-03 | -10, 2014-02-03 | -9, 2014-02-03 | 10?我的意思是一天你可以有不同的数量?
  • @alexo 是的,你明白了。
  • @steve16351 该代码是我见过的最干净、最好的 LINQ 代码 :) 谢谢
  • @believeme linq 做同样的事情,它会迭代,而且速度较慢,但​​更紧凑。例如,当您执行 where 时,linq 将使用表达式树遍历整个集合,并应用您提供的 predicate。您可以使用foreach 执行相同的操作,它使用GetEnumerator 中的Iterator,这会稍微快一些,或者您可以直接使用香草forwhile,这是基于索引的访问,最快.无论如何,最减慢处理速度的是 CPU 的内存可用性。
【解决方案2】:

您可以编写自己的自定义函数来更改为 Linq 表达式。

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication
    {
    public class Class1
        {
        public static void Main(string[] args)
            {
            IEnumerable<QuantityDate> quantityDates = GetQuantityDates();//I'm sure you already have some way of retrieving these, via EF or Linq to SQL etc.
            var results = quantityDates.Where(qd => qd.Qty < 0).CombineResults(); //This is the main Linq expression
            foreach (var result in results)
                {
                Console.WriteLine("From Date: {0} To Date: {1}", result.FromDate, result.ToDate);
                }
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
            }

        //Just to see the data for the moment. You'll probably get this data via EF or Linq to SQL
        public static List<QuantityDate> GetQuantityDates()
            {
            List<QuantityDate> seed = new List<QuantityDate>()
            {
            new QuantityDate() { EventDate = new DateTime(2014, 2, 3), Qty = 6 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 4), Qty = -1 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 5), Qty = -2 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 6), Qty = 2 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 7), Qty = -1 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 8), Qty = -2 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 9), Qty = -3 },
            new QuantityDate() { EventDate = new DateTime(2014, 2, 10), Qty = 5 }
            };
            return seed;
            }
        }
    public static class Extensions
        {

        //This is where the magic happens, and we combine the results
        public static List<OutOfStockRange> CombineResults(this IEnumerable<QuantityDate> input)
            {
            List<OutOfStockRange> output=new List<OutOfStockRange>();
            OutOfStockRange lastEntered = null;
            foreach(var qd in input.OrderBy(qd => qd.EventDate))
             {
                 if(lastEntered != null && lastEntered.ToDate.AddDays(1) == qd.EventDate)
                 {
                     lastEntered.ToDate = qd.EventDate;
                 }
                 else
                 {
                     lastEntered =new OutOfStockRange(){FromDate = qd.EventDate, ToDate = qd.EventDate};
                     output.Add(lastEntered);
                 }
            }
            return output;
        }
        }

    //This class represents the input data
    public class QuantityDate
        {
        public DateTime EventDate { get; set; }
        public int Qty { get; set; }
        }

    //This class represents the output data
    public class OutOfStockRange
        {
        public DateTime FromDate { get; set; }
        public DateTime ToDate { get; set; }
        }
    }

【讨论】:

    【解决方案3】:

    我会这样做。

    从您的数据开始:

    var stockQuantities = new []
    {
        new { Date = new DateTime(2014, 2, 3), Qty = 6 },
        new { Date = new DateTime(2014, 2, 4), Qty = -1 },
        new { Date = new DateTime(2014, 2, 5), Qty = -2 },
        new { Date = new DateTime(2014, 2, 6), Qty = 2 },
        new { Date = new DateTime(2014, 2, 7), Qty = -1 },
        new { Date = new DateTime(2014, 2, 8), Qty = -2 },
        new { Date = new DateTime(2014, 2, 9), Qty = -3 },
        new { Date = new DateTime(2014, 2, 10), Qty = 5 },
    };
    

    然后查询缺货记录:

    var outOfStock = stockQuantities.Where(x => x.Qty < 0);
    

    现在使用Aggregate 构建结果:

    var outOfStockRanges =
        outOfStock
            .Skip(1)
            .Aggregate(
                outOfStock
                    .Take(1)
                    .Select(x => new { From = x.Date, To = x.Date })
                    .ToList(),
                (a, x) =>
                {
                    if (a.Last().To.AddDays(1.0) == x.Date)
                        a[a.Count - 1] = new { From = a.Last().From, To = x.Date };
                    else
                        a.Add(new { From = x.Date, To = x.Date });
                    return a;
                });
    

    这是我得到的结果:

    【讨论】:

    • 那太乱了,不适合我的实际代码
    • @believeme - 这正是您在问题中所要求的。它使用 LINQ 并提供您所需的确切输出。什么是太乱了,什么是行不通的?
    • 问题在于你跳过了第一行,如果我有超过 1 行要跳过怎么办,其余的呢?
    • @believeme - 我实际上并没有跳过一行。在.Aggregate 内部,我正在执行.Take(1) 将第一条记录加载到累加器中。 .Skip(1) 只是跳过了第一个记录,因此它不会被处理两次。我不知道你说的需要跳过多个是什么意思。 .Where(x =&gt; x.Qty &lt; 0) 表示我不需要跳过任何内容。
    【解决方案4】:

    我想出了以下几点:

    var src_objects = new []
    {
        new {date = DateTime.Parse("2014-02-03"), qty = 6},
        new {date = DateTime.Parse("2014-02-04"), qty = -1},
        new {date = DateTime.Parse("2014-02-05"), qty = -2},
        new {date = DateTime.Parse("2014-02-06"), qty = 2},
        new {date = DateTime.Parse("2014-02-07"), qty = -1},
        new {date = DateTime.Parse("2014-02-08"), qty = -2},
        new {date = DateTime.Parse("2014-02-09"), qty = -3},
        new {date = DateTime.Parse("2014-02-10"), qty = 5}
    };
    
    
    int i = 0;
    
    
    var ranges = src_objects
        .OrderBy(key => key.date)
        .Select(obj =>
        {
            if (obj.qty > 0)
            {
                ++i;
                return new { date = obj.date, group_key = 0 };
            }
            else
                return new { date = obj.date, group_key = i };
        })
        .Where(obj => obj.group_key != 0)
        .GroupBy(obj => obj.group_key)
        .Select(g => new { fromdate = g.First().date, todate = g.Last().date });
    
    ranges
    .ToList()
    .ForEach(range => Console.WriteLine(string.Format("{0} - {1}", range.fromdate, range.todate.Date)));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多