使用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
现在我希望得到一个日期范围,当qty为零时,像这样
FromDate | ToDate
2014-02-04 | 2014-02-05
2014-02-07 | 2014-02-09
有人可以帮助我,请问如何实现这一目标?
更新
我知道我可以通过乘以查询来做到这一点,但如果可能的话,我想在一个LINQ查询中这样做。
答案 0 :(得分:2)
对于仅使用内置函数的替代方法,
此处的策略是选择数量小于零的所有日期,并且对于每个日期,执行子查询,该子查询构建当前日期之后所有日期的列表,其数量也小于零。使用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();
}
答案 1 :(得分:0)
您可以编写自己的自定义函数以更改为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; }
}
}
答案 2 :(得分:0)
这是我将如何做到的。
从您的数据开始:
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;
});
这是我得到的结果:
答案 3 :(得分:0)
我想出了以下内容:
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)));