Python разбивает список даты и времени на основе пропущенных дней

Как я могу разделить список даты и времени с отсутствующими датами на список списков на основе отсутствующих дат?

Используя следующий пример:

date_list = [
        datetime.datetime(2012,1,1,0,0,0), 
        datetime.datetime(2012,1,2,0,0,0), 
        datetime.datetime(2012,1,4,0,0,0), 
        datetime.datetime(2012,1,7,0,0,0),
        datetime.datetime(2012,1,8,0,0,0),
        ]

Результат, который я ищу здесь,

[[datetime.datetime(2012,1,1,0,0,0), datetime.datetime(2012,1,2,0,0,0)]
[datetime.datetime(2012,1,4,0,0,0)], 
[datetime.datetime(2012,1,7,0,0,0), datetime.datetime(2012,1,8,0,0,0)]]

Я пытался использовать groupby, но не могу понять, что использовать для ключа.

[list(g) for k, g in itertools.groupby(date_list, key=lambda d: d.day)]


person pyCthon    schedule 05.12.2014    source источник
comment
вы, вероятно, найдете второй пример в (старой версии) документации itertools полезно. С другой стороны, если вы не заботитесь о том, чтобы быть супер-причудливым, 21142465">написать собственный генератор довольно просто.   -  person roippi    schedule 05.12.2014


Ответы (3)


Это работает для данного примера...

>>> import datetime
>>> date_list = [
...         datetime.datetime(2012,1,1,0,0,0),
...         datetime.datetime(2012,1,2,0,0,0),
...         datetime.datetime(2012,1,4,0,0,0),
...         datetime.datetime(2012,1,7,0,0,0),
...         datetime.datetime(2012,1,8,0,0,0),
...         ]
>>> import itertools
>>> [list(g) for k, g in itertools.groupby(enumerate(date_list), key=lambda (i, x): i-x.day)]
[[(0, datetime.datetime(2012, 1, 1, 0, 0)), (1, datetime.datetime(2012, 1, 2, 0, 0))], [(2, datetime.datetime(2012, 1, 4, 0, 0))], [(3, datetime.datetime(2012, 1, 7, 0, 0)), (4, datetime.datetime(2012, 1, 8, 0, 0))]]

Это может быть лучше, если вам не нужен индекс...

>>> [[v for i, v in g] for k, g in itertools.groupby(enumerate(date_list), key=lambda (i, x): i-x.day)]
[[datetime.datetime(2012, 1, 1, 0, 0), datetime.datetime(2012, 1, 2, 0, 0)], [datetime.datetime(2012, 1, 4, 0, 0)], [datetime.datetime(2012, 1, 7, 0, 0), datetime.datetime(2012, 1, 8, 0, 0)]]
person Kevin Cherepski    schedule 05.12.2014

Вот скучная вспомогательная функция цикла for для этого.

def date_segments(dates):
    output = []
    cur_list = [dates[0]]
    for dt_pair in zip(dates[1:], dates):
        if (dt_pair[0] - dt_pair[1]).days > 1:
            output.append(cur_list)
            cur_list = [dt_pair[0]]
        else:
            cur_list.append(dt_pair[0])
    output.append(cur_list)
    return output

который дает:

In [28]: date_segments(date_list)
Out[28]: 
[[datetime.datetime(2012, 1, 1, 0, 0), datetime.datetime(2012, 1, 2, 0, 0)],
 [datetime.datetime(2012, 1, 4, 0, 0)],
 [datetime.datetime(2012, 1, 7, 0, 0), datetime.datetime(2012, 1, 8, 0, 0)]]

Если я определяю подход itertools.groupby как вспомогательную функцию с именем other_way, как показано ниже:

from itertools import groupby
def other_way(date_list):
    return [[v for i, v in g] for k, g in groupby(enumerate(date_list), 
                                                  key=lambda (i, x): i-x.day)]

тогда для этого, по общему признанию, небольшого примера timeit показывает, что этот подход с циклом for немного быстрее:

In [31]: %timeit date_segments(date_list) 
100000 loops, best of 3: 3.2 µs per loop

In [32]: %timeit other_way(date_list)
100000 loops, best of 3: 3.72 µs per loop

и я, например, нахожу подход for-loop гораздо более питоническим и читабельным.

person ely    schedule 05.12.2014

Вы можете создать ключ, который «переключается», когда нет последовательных дат:

class Switcher():
    def __call__(self, d):
        if not hasattr(self, 'prev'):    # first element: init switch
            self.switch = 1
        elif (d - self.prev).days > 1:   # not consecutive: invert switch
            self.switch *= -1
        self.prev = d                    # save current value
        return self.switch

Затем вы можете использовать его как:

>>> [list(g) for k, g in groupby(date_list, key = Switcher())]
[[datetime.datetime(2012, 1, 1, 0, 0), datetime.datetime(2012, 1, 2, 0, 0)],
 [datetime.datetime(2012, 1, 4, 0, 0)],
 [datetime.datetime(2012, 1, 7, 0, 0), datetime.datetime(2012, 1, 8, 0, 0)]]
person elyase    schedule 05.12.2014
comment
Если вы используете только __call__ возможности этого класса, почему бы вам просто не сделать его функцией? Просто удалите строку class Switcher, переместите отступ и измените имя __call__ на любое другое, и просто вычислите случаи переключения на zip(date_list[1:], date_list)... Похоже, что это может привести только к меньшему количеству кода и менее запутанному коду. - person ely; 05.12.2014
comment
@prpl.mnky.dshwshr, причина в том, что ключевой объект должен иметь память (self.prev, self.switch), чтобы помнить предыдущее состояние элемента/переключателя. Функция будет без состояния. - person elyase; 05.12.2014
comment
Вот почему я сказал вычислять случаи переключения вне zip, а не притворяться, что они являются состоянием. Я не спорю, можете представить это таким образом, просто это не очень хорошее использование класса. Кроме того, вы можете иметь состояние в функции, либо сделав ее генератором, либо используя замыкания. - person ely; 05.12.2014
comment
@prpl.mnky.dshwshr, О, теперь я понимаю, что вы имеете в виду. Тем не менее моя цель с этим решением состоит в том, чтобы ОП мог использовать свою строку groupby без изменений. Мне было бы интересно посмотреть, как это можно реализовать с помощью замыкания. Кстати +1, ваше решение очень быстрое, - person elyase; 05.12.2014