2011-08-03 22 views
2

私は例えば、日付のリストを持っている:Python:日付リストから日付範囲を計算する方法は?

['2011-02-27', '2011-02-28', '2011-03-01', '2011-04-12', '2011-04-13', '2011-06-08'] 

がどのように私はこれらの日付に含まれる連続した日付の範囲を見つけるのですか?上記の例では、範囲は:

[{"start_date": '2011-02-27', "end_date": '2011-03-01'}, 
{"start_date": '2011-04-12', "end_date": '2011-04-13'}, 
{"start_date": '2011-06-08', "end_date": '2011-06-08'} 
] 

ありがとうございます。

+0

あなたの例であなたのソリューションがどのように派生したかわからない。 「2011年2月28日」の日付はどこに行きましたか? – user37078

+0

'2011-02-28'は{start_date ':' 2011-02-27 '、' end_date ':' 2011-03-01 '}の範囲にあります。 – Continuation

+0

2番目のコードブロック、dictsのリストあなたが持っているのは、*答え*ではなく、ちょうど2番目のパラメータですか?もしそうなら、それが返されると期待しているように結果を投稿できますか? – user37078

答えて

7

これは、が動作しますが、私はそれに満足していません、クリーンなソリューションで答えを編集する答え。

import datetime 
import pprint 

def parse(date): 
    return datetime.date(*[int(i) for i in d.split('-')]) 

def get_ranges(dates): 
    while dates: 
     end = 1 
     try: 
      while dates[end] - dates[end - 1] == datetime.timedelta(days=1): 
       end += 1 
     except IndexError: 
      pass 

     yield { 
      'start-date': dates[0], 
      'end-date': dates[end-1] 
     } 
     dates = dates[end:] 

dates = [ 
    '2011-02-27', '2011-02-28', '2011-03-01', 
    '2011-04-12', '2011-04-13', 
    '2011-06-08' 
] 

# Parse each date and convert it to a date object. Also ensure the dates 
# are sorted, you can remove 'sorted' if you don't need it 
dates = sorted([parse(d) for d in dates]) 

pprint.pprint(list(get_ranges(dates))) 

と相対出力:

[{'end-date': datetime.date(2011, 3, 1), 
    'start-date': datetime.date(2011, 2, 27)}, 
{'end-date': datetime.date(2011, 4, 13), 
    'start-date': datetime.date(2011, 4, 12)}, 
{'end-date': datetime.date(2011, 6, 8), 
    'start-date': datetime.date(2011, 6, 8)}] 
0

忍者GaretJaxの編集しようとすると:完了、ここでクリーン、ワーキングソリューションです;)で

def date_to_number(date): 
    return datetime.date(*[int(i) for i in date.split('-')]).toordinal() 

def number_to_date(number): 
    return datetime.date.fromordinal(number).strftime('%Y-%m-%d') 

def day_ranges(dates): 
    day_numbers = set(date_to_number(d) for d in dates) 
    start = None 
    # We loop including one element guaranteed not to be in the set, to force the 
    # closing of any range that's currently open. 
    for n in xrange(min(day_numbers), max(day_numbers) + 2): 
    if start == None: 
     if n in day_numbers: start = n 
    else: 
     if n not in day_numbers: 
     yield { 
      'start_date': number_to_date(start), 
      'end_date': number_to_date(n - 1) 
     } 
     start = None 

list(
    day_ranges([ 
    '2011-02-27', '2011-02-28', '2011-03-01', 
    '2011-04-12', '2011-04-13', '2011-06-08' 
    ]) 
) 
+1

あなたのソリューションが無駄な繰り返しを多くしていることに気づいていますか?この例の103では、同じデータセットで4つのデータセットが生成されます;-) – GaretJax

+0

ああ、BTW、このデータセットのチョーク: '['2011-02-27'、 '2011-02-28'、 '2011- 03-01 '、' 2011-04-12 '、' 2011-04-13 '、' 2011-06-08 '、' 2011-06-10 ']' ... ;-) – GaretJax

+0

ええ、私は本当に間違ったアルゴリズム、特に疎な日付セットの場合:)しかし、新しいデータセットで私のためにうまく動作します。 –

0
from datetime import datetime, timedelta 

dates = ['2011-02-27', '2011-02-28', '2011-03-01', '2011-04-12', '2011-04-13', '2011-06-08'] 
d = [datetime.strptime(date, '%Y-%m-%d') for date in dates] 
test = lambda x: x[1] - x[0] != timedelta(1) 
slices = [0] + [i+1 for i, x in enumerate(zip(d, d[1:])) if test(x)] + [len(dates)] 
ranges = [{"start_date": dates[s], "end_date": dates[e-1]} for s, e in zip(slices, slices[1:])] 

結果以下:

>>> pprint.pprint(ranges) 
[{'end_date': '2011-03-01', 'start_date': '2011-02-27'}, 
{'end_date': '2011-04-13', 'start_date': '2011-04-12'}, 
{'end_date': '2011-06-08', 'start_date': '2011-06-08'}] 

slicesリストの理解は、前の日付が現在の日付の1日前でないすべてのインデックスを取得します。正面に0を追加し、末尾にlen(dates)を追加し、各日付の範囲はdates[slices[i]:slices[i+1]-1]と記述できます。

0

テーマに、私のわずかな変動(私はもともと、開始/終了リストを構築し、タプルを返すために、それらをzip形式が、私は@Karl Knechtelの発電機のアプローチを好ん):ここでは

from datetime import date, timedelta 

ONE_DAY = timedelta(days=1) 

def find_date_windows(dates): 
    # guard against getting empty list 
    if not dates: 
     return 

    # convert strings to sorted list of datetime.dates 
    dates = sorted(date(*map(int,d.split('-'))) for d in dates) 

    # build list of window starts and matching ends 
    lastStart = lastEnd = dates[0] 
    for d in dates[1:]: 
     if d-lastEnd > ONE_DAY: 
      yield {'start_date':lastStart, 'end_date':lastEnd} 
      lastStart = d 
     lastEnd = d 
    yield {'start_date':lastStart, 'end_date':lastEnd} 

は、テストケースのとおりです。

tests = [ 
    ['2011-02-27', '2011-02-28', '2011-03-01', '2011-04-12', '2011-04-13', '2011-06-08'], 
    ['2011-06-08'], 
    [], 
    ['2011-02-27', '2011-02-28', '2011-03-01', '2011-04-12', '2011-04-13', '2011-06-08', '2011-06-10'], 
] 
for dates in tests: 
    print dates 
    for window in find_date_windows(dates): 
     print window 
    print 

プリント:ここ

['2011-02-27', '2011-02-28', '2011-03-01', '2011-04-12', '2011-04-13', '2011-06-08'] 
{'start_date': datetime.date(2011, 2, 27), 'end_date': datetime.date(2011, 3, 1)} 
{'start_date': datetime.date(2011, 4, 12), 'end_date': datetime.date(2011, 4, 13)} 
{'start_date': datetime.date(2011, 6, 8), 'end_date': datetime.date(2011, 6, 8)} 

['2011-06-08'] 
{'start_date': datetime.date(2011, 6, 8), 'end_date': datetime.date(2011, 6, 8)} 

[] 

['2011-02-27', '2011-02-28', '2011-03-01', '2011-04-12', '2011-04-13', '2011-06-08', '2011-06-10'] 
{'start_date': datetime.date(2011, 2, 27), 'end_date': datetime.date(2011, 3, 1)} 
{'start_date': datetime.date(2011, 4, 12), 'end_date': datetime.date(2011, 4, 13)} 
{'start_date': datetime.date(2011, 6, 8), 'end_date': datetime.date(2011, 6, 8)} 
{'start_date': datetime.date(2011, 6, 10), 'end_date': datetime.date(2011, 6, 10)} 
0

は代替ソリューションです:それはRET (開始、終了)のリストタプル、それは私が必要としたものです;)。

これはリストを変更するため、コピーを作成する必要がありました。明らかに、それはメモリ使用量を増加させます。私はlist.pop()が超効率的ではないと思うが、それはおそらくpythonのリストの実装に依存するだろう。

def collapse_dates(date_list): 
    if not date_list: 
     return date_list 
    result = [] 
    # We are going to alter the list, so create a (sorted) copy. 
    date_list = sorted(date_list) 
    while len(date_list): 
     # Grab the first item: this is both the start and end of the range. 
     start = current = date_list.pop(0) 
     # While the first item in the list is the next day, pop that and 
     # set it to the end of the range. 
     while len(date_list) and date_list[0] == current + datetime.timedelta(1): 
      current = date_list.pop(0) 
     # That's a completed range. 
     result.append((start,current)) 

    return result 

追加する行を簡単に変更してdictを追加したり、リストに追加する代わりにyieldしたりすることができます。

ああ、鉱山はすでに日付だと仮定しています。