找到重叠日期范围的最低价格 - C#算法

时间:2016-12-22 19:22:51

标签: c# algorithm date-range

某些时间段设置了价格...我在确定特定时间段的最低价格时遇到问题。

我使用对象列表执行此操作,其中对象具有属性DateTime StartDate, DateTime EndDate, decimal Price

例如,两个价格集及其有效日期范围:

A. 09/26/16 - 12/31/17 at $20.00
B. 12/01/16 - 12/31/16 at $18.00

您可以看到B在A时段内且较低。

我需要转换成这个:

A. 09/26/16 - 11/30/16 at $20.00
B. 12/01/16 - 12/31/16 at $18.00
C. 01/01/17 - 12/31/17 at $20.00

它必须适用于任意数量的日期范围和组合。有没有人遇到任何我可以操纵以获得我需要的结果的东西?还是有什么建议吗?

编辑:我的数据结构:

public class PromoResult
{
    public int ItemId { get; set; }
    public decimal PromoPrice { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int PromoType { get; set; } // can ignore this...
}

5 个答案:

答案 0 :(得分:3)

这是使用Linq的一个很好的例子。假设您的价格范围对象被称为PriceRecord ...

您需要创建所有日期的列表,然后过滤到两个连续日期之间的价格记录。实现可能如下所示:

    public static IEnumerable<PriceRecord> ReduceOverlaps(IEnumerable<PriceRecord> source)
    {
        // Get a list of all edges of date ranges
        // edit, added OrderBy (!)
        var edges = source.SelectMany(record => new[] { record.StartDate, record.EndDate }).OrderBy(d => d).ToArray();
        // iterate over pairs of edges (i and i-1)
        for (int i = 1; i < edges.Length; i++)
        {
            // select min price for range i-1, i
            var price = source.Where(r => r.StartDate <= edges[i - 1] && r.EndDate >= edges[i]).Select(r => r.Price).Min();
            // return a new record from i-1, i with price
            yield return new PriceRecord() { StartDate = edges[i - 1], EndDate = edges[i], Price = price };
        }
    }

我没有对此进行测试,您可能需要修改比较运算符,但这可能是一个很好的起点。 我现在测试了代码,此处的示例与问题中的数据一起使用。

随意提出修改以改进此示例。

答案 1 :(得分:1)

没有直接回答你的问题,但这里有一些我用来解决类似问题的SQL(简化了一下,因为我还处理多个地点和不同的价格类型):

SELECT RI.ItemNmbr, RI.UnitPrice, RI.CasePrice
    , RP.ProgramID
    , Row_Number() OVER (PARTITION BY RI.ItemNmbr,
                         ORDER BY CASE WHEN RI.UnitPrice > 0 
                                       THEN RI.UnitPrice
                                       ELSE 1000000 END ASC
                                  , CASE WHEN RI.CasePrice > 0
                                         THEN RI.CasePrice
                                         ELSE 1000000 END ASC
                                  , RP.EndDate DESC
                                  , RP.BeginDate ASC
                                  , RP.ProgramID ASC) AS RowNumBtl
    , Row_Number() OVER (PARTITION BY RI.UnitPrice, 
                         ORDER BY CASE WHEN RI.CasePrice > 0 
                                       THEN RI.CasePrice
                                       ELSE 1000000 END ASC
                                  , CASE WHEN RI.UnitPrice > 0
                                         THEN RI.UnitPrice
                                         ELSE 1000000 END ASC
                                  , RP.EndDate DESC
                                  , RP.BeginDate ASC
                                  , RP.ProgramID ASC) AS RowNumCase
  FROM RetailPriceProgramItem AS RI
    INNER JOIN RetailPriceMaster AS RP
        ON RP.ProgramType = RI.ProgramType AND RP.ProgramID = RI.ProgramID
  WHERE RP.ProgramType='S'
        AND RP.BeginDate <= @date AND RP.EndDate >= @date
                    AND RI.Active=1

我从UnitPrice的RowNumBtl = 1和CasePrice的RowNumCase = 1中选择。如果您随后创建了一个日期表(您可以使用CTE执行此操作),则可以在每个日期交叉应用。这样效率有点低,因为你只需要在日期范围之间的边界条件下进行测试,所以......祝你好运。

答案 2 :(得分:1)

我将使用2个函数DateRangeGroupSequenceWhile

List<PromoResult> promoResult = new List<PromoResult>()
{
    new PromoResult() {  PromoPrice=20, StartDate = new DateTime(2016, 9, 26),EndDate=new DateTime(2017, 12, 31)},
    new PromoResult() {  PromoPrice=18, StartDate = new DateTime(2016, 12, 1),EndDate=new DateTime(2016, 12, 31)}
};

var result = promoResult.SelectMany(x => DateRange(x.StartDate, x.EndDate, TimeSpan.FromDays(1))
                                         .Select(y => new { promo = x, date = y }))
            .GroupBy(x => x.date).Select(x => x.OrderBy(y => y.promo.PromoPrice).First())
            .OrderBy(x=>x.date)
            .ToList();

var final = result.GroupSequenceWhile((x, y) => x.promo.PromoPrice == y.promo.PromoPrice)
            .Select(g => new { start = g.First().date, end = g.Last().date, price = g.First().promo.PromoPrice })
            .ToList();

foreach (var r in final)
{
    Console.WriteLine(r.price + "$ " + r.start.ToString("MM/dd/yy", CultureInfo.InvariantCulture) + " " + r.end.ToString("MM/dd/yy", CultureInfo.InvariantCulture));
}

<强>输出:

20$ 09/26/16 11/30/16
18$ 12/01/16 12/31/16
20$ 01/01/17 12/31/17

<强>算法:

1-为<day,price>列表

中的每个项目创建一个promoResult元组

按天将这个元组分组并选择最低价格

按日期排序此元组

4-选择连续几天价格发生变化的开始和结束日

IEnumerable<DateTime> DateRange(DateTime start, DateTime end, TimeSpan period)
{
    for (var dt = start; dt <= end; dt = dt.Add(period))
    {
        yield return dt;
    }
}
public static IEnumerable<IEnumerable<T>> GroupSequenceWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition) 
{
    List<T> list = new List<T>();
    using (var en = seq.GetEnumerator())
    {
        if (en.MoveNext())
        {
            var prev = en.Current;
            list.Add(en.Current);

            while (en.MoveNext())
            {
                if (condition(prev, en.Current))
                {
                    list.Add(en.Current);
                }
                else
                {
                    yield return list;
                    list = new List<T>();
                    list.Add(en.Current);
                }
                prev = en.Current;
            }

            if (list.Any())
                yield return list;
        }
    }
}

答案 3 :(得分:0)

我将从基于开始日期的日期顺序开始,将第一个条目全部添加为范围,以便:

09/26/16 - 12/31/17 at $20.00
TBD:
12/01/16 - 12/31/16 at $18.00

接下来抓住你拥有的下一个范围,如果它与前一个范围重叠,则分割重叠(有几种重叠,确保全部处理它们),取重叠区域的最小值:

09/26/16 - 11/30/16 at $20.00
12/01/16 - 12/31/16 at $18.00
TBD:
01/01/17 - 12/31/17 at $20.00

请注意,您还没有最后一个,因为您会接受之后发生的任何拆分,并将它们放回到排序列表中,然后进行比较&#34;项目

答案 4 :(得分:-3)

试试这个

我们说:

public class DatePrice
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public decimal Price { get; set; }
}

IList<DatePrice> list = new List<DatePrice>(); // populate your data from the source..
var lowestPriceItem = list.OrderBy(item => item.Price).First();

应该给你最低价格项目。