如何将两个日期列表合并为一系列日期区间?

4

我有一列开始日期和一列结束日期。它们已经排序好了...

start_dates = [
    datetime.date(2009, 11, 5), datetime.date(2009, 11, 13), 
    datetime.date(2009, 11, 25), datetime.date(2009, 11, 26), 
    datetime.date(2009, 12, 4), datetime.date(2009, 12, 7), 
    datetime.date(2009, 12, 29), datetime.date(2009, 12, 30)]

end_dates = [
    datetime.date(2009, 10, 1), datetime.date(2009, 10, 2), 
    datetime.date(2009, 10, 9), datetime.date(2009, 10, 12), 
    datetime.date(2009, 11, 4), datetime.date(2009, 12, 14), 
    datetime.date(2009, 12, 15)]

起始日期表示我们收到购买股票建议的日期。结束日期是我们收到卖出建议的日期。建议的来源不同,我们正在回测如果我们使用一个来源的购买建议,但使用另一个来源的卖出建议会发生什么。因此,我们有两个日期序列,我们想将它们解析成一对或多对持有该股票的时间间隔。
因此,我们将从起始日期中选择一个日期来决定何时购买该股票:在11月5日我们购买了一份股票。然后我们循环遍历结束日期,寻找第一个卖出建议:12月14日。然后重复这个过程,当我们从另一个来源得到购买建议时不持有头寸,当我们持有头寸时从另一个来源得到卖出建议。
你可以说我们想在两个列表之间来回切换。
因此,上面的输入生成以下内容:
result = (
  (datetime.date(2009, 11, 5), datetime.date(2009, 12, 14)),
  (datetime.date(2009, 12, 29), None)
)

我正在使用嵌套的for循环,不知道是否有更好的方法。性能十分重要,因为它将在40年的时间内应用于数千种情况;有些列表涉及数千个日期。


1
你想要达到的结果是什么样子?仅从问题中很难说出你希望它变成什么。 - Pablo Jomer
是的,但我要如何映射它们(start_dates [1],end_dates [1])? - Pablo Jomer
@PabloKarlsson 抱歉,您没有遵循。我为您详细阐述了这个问题。 - John Mee
没问题,我想我已经提供了答案,希望它适合你。 - Pablo Jomer
等一下...为什么结果中的结束日期不是datetime.date(2009, 12, 15)而是datetime.date(2009, 12, 14)?我有什么遗漏吗? - Avaris
显示剩余2条评论
4个回答

2

编辑

这应该与 len(start_dates)+len(end_dates) 成比例地扩展:

def date_range(start_dates, end_dates):
    result = []

    start_i = 0
    end_i = 0

    while start_i<len(start_dates):
        while end_i<len(end_dates) and start_dates[start_i]>end_dates[end_i]:
            end_i += 1
        if end_i == len(end_dates):
            result.append((start_dates[start_i], None))
            break
        result.append((start_dates[start_i], end_dates[end_i]))
        while start_i<len(start_dates) and start_dates[start_i]<=end_dates[end_i]:
            start_i += 1
        end_i += 1

    return result

使用方法:

In  : start_dates = [
   ....:     datetime.date(2009, 11, 5), datetime.date(2009, 11, 13),
   ....:     datetime.date(2009, 11, 25), datetime.date(2009, 11, 26),
   ....:     datetime.date(2009, 12, 4), datetime.date(2009, 12, 7),
   ....:     datetime.date(2009, 12, 29), datetime.date(2009, 12, 30)]

In : end_dates = [
   ....:     datetime.date(2009, 10, 1), datetime.date(2009, 10, 2),
   ....:     datetime.date(2009, 10, 9), datetime.date(2009, 10, 12),
   ....:     datetime.date(2009, 11, 4), datetime.date(2009, 12, 14),
   ....:     datetime.date(2009, 12, 15)]

In : date_range(start_dates, end_dates)
Out:
[(datetime.date(2009, 11, 5), datetime.date(2009, 12, 14)),
 (datetime.date(2009, 12, 29), None)]

In : start_dates = [
   ....:     datetime.date(2009, 11, 5), datetime.date(2009, 11, 13),
   ....:     datetime.date(2009, 11, 25), datetime.date(2009, 11, 26),
   ....:     datetime.date(2009, 12, 4), datetime.date(2009, 12, 7),
   ....:     datetime.date(2009, 12, 29), datetime.date(2009, 12, 30)]

In : end_dates = [
   ....:     datetime.date(2009, 10, 1), datetime.date(2009, 10, 2),
   ....:     datetime.date(2009, 10, 9), datetime.date(2009, 10, 12),
   ....:     datetime.date(2009, 11, 7), datetime.date(2009, 12, 14), # changed (2009, 11, 4) -> (2009, 11, 7)
   ....:     datetime.date(2009, 12, 15)]

In : date_range(start_dates, end_dates)
Out:
[(datetime.date(2009, 11, 5), datetime.date(2009, 11, 7)),
 (datetime.date(2009, 11, 13), datetime.date(2009, 12, 14)),
 (datetime.date(2009, 12, 29), None)]

我在想,因为我可能误解了问题...如果您将结束日期从“2009-11-04”更改为“2009-11-07”,预期输出会是什么?这个答案将导致相同的结果。 - sberry
@JohnMee:已更正。这应该比嵌套的“for”循环还要好。 - Avaris
赶我之前就做了。那是我在被分心编辑问题之前正在研究的模式...尽管现在我正沉迷于意识到它只是在搜索时交替列表。嗯。 - John Mee

2

我终于确定了:

    trades = []
    enddate = datetime.date(1900, 1, 1)
    try:
        for startdate in startdates:
            if enddate <= startdate:
                enddate = enddates.next()
                while enddate <= startdate:
                    enddate = enddates.next()
                trades.append((startdate, enddate))
    except StopIteration:
        trades.append((startdate, None))

感谢那些提问和回答的人。出于没有理性的原因,这个小谜题成为了我的固执点,但我认为最终我已经把它做透了,应该继续我的生活了。最后,这真的非常简单-惊人的是,需要这么多的工作才能让它变得如此明显!


1

我认为这应该可以得到日期元组,但是如果不使用for循环,我无法建议您的方法,因为这可能会变得更加复杂。

逻辑非常简单和明显。

result = []
for startd in start_dates:
    if not result or result[-1][1] is not None and startd>result[-1][1]:
    #can use 'len(result)==0' instead of 'not result'
        for endd in end_dates:
            if endd>startd:
                result.append((startd,endd))
                break
        else:
            result.append((start,None))
    if result[-1][1] is None:
        break

result = tuple(result)
print result

0
这个怎么样?
all_dates = start_dates.expand(end_dates)
all_dates.sort()

look_for_start = 1;
date = []
start_date = None
end_date = None
for i in range(len(all_dates)):
   if look_for_start and all_dates[i] in start_dates:
     start_date = all_dates[i]
     look_for_start = 0;

   elsif !look_for_start and all_dates[i] in end_dates:
     end_date = all_dates[1]
     look_for_start = 1;

   if start_date == end_date:
     end_date == None
     look_for_start = 0;

   if start_date != None and end_date != None;
     date.append((start_date,end_date))
     start_date = None
     end_date = None

在这之后,你有一组尽可能远的start_dates和end_dates。只需取剩余的start_dates集合,并将它们的end_date设置为None。

列表上没有 expand 方法,我想你是指 extend。而且这并不能产生期望的输出。 - sberry
这仍然无法产生所需的输出吗? - sberry
我觉得我误解了被请求的内容。我认为这应该相当有效地解决问题。 - Pablo Jomer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接