Разделить список дат на подмножества последовательных дат - PullRequest
3 голосов
/ 16 января 2020

У меня есть массив дат, который может содержать несколько диапазонов дат.

dates = [
 '2020-01-01',
 '2020-01-02',
 '2020-01-03',
 '2020-01-06',
 '2020-01-07',
 '2020-01-08'
]

В этом примере список содержит 2 отдельных последовательных диапазона дат (с 2020-01-01 по 2020- 01-03 и с 2020-01-06 по 2020-01-08)

Я пытаюсь выяснить, как я проведу oop по этому списку и найду все последовательные диапазоны дат.

Одна из статей, на которые я смотрю ( Как определить, являются ли даты последовательными в Python? ), кажется, имеет хороший подход, однако я изо всех сил пытаюсь реализовать эту логику c в моем случае использования.

Ответы [ 5 ]

2 голосов
/ 16 января 2020

Больше itertools имеет функцию под названием consecutive_groups, которая делает это за вас:

Или вы можете просмотреть исходный код и скопировать его подход:

from datetime import datetime
from itertools import groupby
from operator import itemgetter

def consecutive_groups(iterable, ordering=lambda x: x):
    for k, g in groupby(enumerate(iterable), key=lambda x: x[0] - ordering(x[1])):
        yield map(itemgetter(1), g)

for g in consecutive_groups(dates, lambda x: datetime.strptime(x, '%Y-%m-%d').toordinal()):
    print(list(g))

['2020-01-01', '2020-01-02', '2020-01-03']
['2020-01-06', '2020-01-07', '2020-01-08']
1 голос
/ 16 января 2020

Предполагается, что «диапазоны» для одной даты все еще представлены двумя датами:

def makedate(s):
    return datetime.strptime( s, "%Y-%m-%d" )
def splitIntoRanges( dates ):
    ranges = []
    start_s = last_s = dates[0]
    last = makedate(start_s)
    for curr_s in dates[1:]:
        curr = makedate(curr_s)
        if (curr - last).days > 1:
            ranges.append((start_s,last_s))
            start_s = curr_s
        last_s = curr_s
        last = curr
    return ranges + [(start_s,last_s)]
0 голосов
/ 16 января 2020

Вы можете найти все последовательные диапазоны дат, добавить их в список и получить доступ к диапазонам на основе индекса, но я предпочитаю использовать ключи в словаре для удобства чтения.

Вот как: (примечание: пожалуйста, прочитайте комментарии)

dates = [datetime.strptime(d, "%Y-%m-%d") for d in dates] # new datetime parsed from a string
date_ints = [d.toordinal() for d in dates]  # toordinal() returns the day count from the date 01/01/01 in integers
ranges = {}; arange = []; prev=0; index=0; j=1
for i in date_ints: # iterate through date integers
    if i+1 == date_ints[index] + 1 and i - 1 == prev: # check and compare if integers are in sequence
        arange.append(dates[index].strftime("%Y-%m-%d"))
    elif prev == 0: # append first date to 'arange' list since 'prev' has not been updated
        arange.append(dates[index].strftime("%Y-%m-%d"))
    else:
        ranges.update({f'Range{j}': tuple(arange)}) # integer are no longer in sequence, update dictionary with new range  
        arange = []; j += 1                                   # clear 'arange' and start appending to new range  
        arange.append(dates[index].strftime("%Y-%m-%d"))
    index += 1; prev = i
ranges.update({f'Range{j}': tuple(arange)})
print(ranges)  
print(ranges['Range1'])  # access a range based on the associated key
print(ranges['Range2']) 

вывод:

{'Range1': ('2020-01-01', '2020-01-02', '2020-01-03'), 'Range2': ('2020-01-06', '2020-01-07', '2020-01-08')}
('2020-01-01', '2020-01-02', '2020-01-03')
('2020-01-06', '2020-01-07', '2020-01-08')
0 голосов
/ 16 января 2020

Я нашел ключ к своему решению во втором посте и сложил его вместе.

В моем выпуске есть две части:

  1. Как мне представить список дат эффективным образом

Ответ: { ссылка }

pto = [
    '2020-01-03',
    '2020-01-08',
    '2020-01-02',
    '2020-01-07',
    '2020-01-01',
    '2020-01-06'
]

ordinal_dates = [datetime.datetime.strptime(i, '%Y-%m-%d').toordinal() for i in pto]
Получив список дат в целочисленном представлении, вы можете просто искать последовательные целые числа и получать верхнюю и нижнюю границы каждого диапазона, а затем преобразовывать обратно в формат гггг-мм-дд.

Ответ: { ссылка }

def ranges(nums):
    nums = sorted(set(nums))
    gaps = [[s, e] for s, e in zip(nums, nums[1:]) if s+1 < e]
    edges = iter(nums[:1] + sum(gaps, []) + nums[-1:])
    return list(zip(edges, edges))

Моя полная функция:

def get_date_ranges(pto_list: list) -> list:
    pto_dates = [datetime.datetime.strptime(i, '%Y-%m-%d').toordinal() for i in pto_list]
    nums = sorted(set(pto_dates))
    gaps = [[s, e] for s, e in zip(nums, nums[1:]) if s + 1 < e]
    edges = iter(nums[:1] + sum(gaps, []) + nums[-1:])
    ordinal_ranges = list(zip(edges, edges))
    date_bounds = []
    for start, end in ordinal_ranges:
        date_bounds.append((
            datetime.datetime.fromordinal(start).strftime('%Y-%m-%d'),
            datetime.datetime.fromordinal(end).strftime('%Y-%m-%d')
        ))
    return date_bounds
0 голосов
/ 16 января 2020

Я выбрал такой же, хотя и не совсем элегантный подход, как @Scott:

ranges = []

dates = [datetime.strptime(date, '%Y-%m-%d') for date in dates]
start = dates[0]

for i in range(1, len(dates)):
    if (dates[i] - dates[i-1]).days == 1 and i==len(dates)-1:
        end = dates[i]
        ranges.append(f'{start} to {end}')
        start = dates[i]
    elif (dates[i] - dates[i - 1]).days > 1:
        end = dates[i - 1]
        ranges.append(f'{start} to {end}')
        start = dates[i]
    else:
        continue
...