Определить пересекающиеся временные промежутки

Есть список с различными промежутками времени. Например:

time_list = ['9:00 - 15:00', '13:00 - 17:00', '16:00 - 20:00']

Как мне из этого списка выбрать только '9:00 - 15:00' и '16:00 - 20:00'? То есть, нужно выбрать только те промежутки времени, которые не пересекаются между собой (напр. 9:00 - 15:00 и 13:00 - 17:00 пересекаются, так как 13:00 входит в промежуток 9:00 - 15:00).

Уже три дня не могу решить эту задачу, и кажется что ответ на поверхности, а понять не могу. Пробовал в циклах сравнивать каждый элемент списка с каждым элементом этого же списка, но ничего нормального так и не получилось.

Буду благодарен, если кто-то поможет.


Ответы (4 шт):

Автор решения: MBo

Жадный метод - отсортировать по началу интервалов.

Взять первый интервал, далее найти тот, который первым идёт после его конца, и так далее.

→ Ссылка
Автор решения: Бодя паук

Как и обещал, реализовал функцию, выполняющую бòльшую часть работы (даже дебаг-вывод есть). Почитай код, поймешь. Но, чтобы ты не терял интерес к задаче, эта функция имеет баг - если указать сначала вечерний промежуток времени, а потом утренний, то система будет выводить противоположное значение. Она возвращает только булево значение. Много переменных тут ради удобства чтения, а так это можно и в однострочник всунуть

Функция-парсер, выводит True, если промежутки пересекаются

def parse_time(first_time: str, second_time: str, debug=False):
    """
    Возвращает True, если указанные промежутки времени пересекаются. Вводятся
    они в формате ЧЧ:ММ - ЧЧ:ММ
    """
    first_time = first_time.split()
    second_time = second_time.split()

    first_time.pop(1)
    second_time.pop(1)

    first_time_start = first_time[0].split()[0]
    first_time_stop = first_time[1].split()[0]

    first_time_start_hours = eval(first_time_start.split(':')[0])
    first_time_start_minutes = eval(first_time_start.split(':')[1])

    first_time_stop_hours = eval(first_time_stop.split(':')[0])
    first_time_stop_minutes = eval(first_time_stop.split(':')[1])

    first_time_start_all_minutes = first_time_start_hours * 60 + first_time_start_minutes

    first_time_stop_all_minutes = first_time_stop_hours * 60 + first_time_stop_minutes

    second_time_start = second_time[0].split()[0]
    second_time_stop = second_time[1].split()[0]

    second_time_start_hours = eval(second_time_start.split(':')[0])
    second_time_start_minutes = eval(second_time_start.split(':')[1])

    second_time_stop_hours = eval(second_time_stop.split(':')[0])
    second_time_stop_minutes = eval(second_time_stop.split(':')[1])

    second_time_start_all_minutes = second_time_start_hours * 60 + second_time_start_minutes

    second_time_stop_all_minutes = second_time_stop_hours * 60 + second_time_stop_minutes

    if debug:
        print(first_time)
        print(f"{first_time_start} + {first_time_stop}")
        print(f"{first_time_start_hours}-{first_time_start_minutes} {first_time_stop_hours}-{first_time_stop_minutes}")

        print(second_time)
        print(f"{second_time_start} + {second_time_stop}")
        print(f"{second_time_start_hours}-{second_time_start_minutes} {second_time_stop_hours}-{second_time_stop_minutes}")

        print(first_time_start_all_minutes)
        print(first_time_stop_all_minutes)

        print(second_time_start_all_minutes)
        print(second_time_stop_all_minutes)

    if first_time_stop_all_minutes > second_time_start_all_minutes:
        return True

У этой функции баг - если указать сначала поздний, а затем ранний промежуток времени, то система будет говорить противоположное истине

time = ['9:00 - 15:00', '13:00 - 18:00', '17:00 - 21:00']

# Вот здесь
if parse_time(time[2], time[0]):
    print('Yes')
else:
    print('No')
Yes
→ Ссылка
Автор решения: SergFSM

не очень понятно какие промежутки считать пересекающимися, а какие не пересекающимися, поэтому сделал так:

from datetime import time

time_list = ['7:00 - 11:00', '9:00 - 15:00', '13:00 - 17:00', '16:00 - 20:00', '10:00 - 12:00']
res = {}

time_type = [list(map(lambda x: time.fromisoformat(x.zfill(5)), p.split(' - '))) for p in time_list]
for i in range(len(time_type)):
    interv = time_type.pop(0)
    intervs = [' - '.join(map(lambda x: x.strftime('%H:%M'),j)) for j in time_type if interv[1]<j[0] or j[1]<interv[0]]
    if intervs: res.update({' - '.join(map(lambda x: x.strftime('%H:%M'),interv)):intervs})
    time_type.append(interv)

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

>>> res
'''
{'07:00 - 11:00': ['13:00 - 17:00', '16:00 - 20:00'],
 '09:00 - 15:00': ['16:00 - 20:00'],
 '13:00 - 17:00': ['10:00 - 12:00', '07:00 - 11:00'],
 '16:00 - 20:00': ['10:00 - 12:00', '07:00 - 11:00', '09:00 - 15:00'],
 '10:00 - 12:00': ['13:00 - 17:00', '16:00 - 20:00']}
→ Ссылка
Автор решения: Daniil Loban

Еще один вариант поиска непрекращающихся интервалов

Алгоритм:

  1. создаем отсортированный список в минутах включающий все метки начала и окончания интервалов [t0, t1, t2, t3, ...]
  2. создаем список интервалов в минутах ([[a0, b0], [a1, b1], ... ])
  3. проходим по первому списку беря текущее и следующее значения и ищем такой интервал во втором списке, при совпадении выводим.

Объяснение:

Первый список содержит полную временную шкалу точек времени начала и окончания. Между двумя ближайшими точками не может быть другой с точки зрения логики, но не все из этого является указанными интервалами, так как две точки могут обозначать конец одного интервала и начало другого, или два конца/начала интервалов. По этой причине мы ищем в интервалах есть ли там интервал заданный двумя соседними точками времени.

введите сюда описание изображения

На предоставленном графике видно, что только t[7] и t[8] две соседние точки совпадающие с имеющимися интервалами.

Реализация:

from datetime import time

time_list = ['8:00 - 9:00', '9:00 - 15:00', '13:00 - 17:00', '16:00 - 20:00']

def time_to_minutes(t): 
  t = time.fromisoformat(t.zfill(5))
  return t.hour * 60 * 60 + t.minute * 60

time_intervals = [list(map(lambda x: time_to_minutes(x), item.split(' - '))) for item in time_list ]
time_minutes = [time_to_minutes(item) for sublist in time_list for item in sublist.split(' - ')]
time_minutes.sort()

for n in range(0, len(time_minutes) - 1):
  if [time_minutes[n], time_minutes[n + 1]] in time_intervals:
    print(time_list[n])

Вывод:

8:00 - 9:00 так как это единственный не пересекающийся интервал.

Пояснения к коду:

Строчки в коде

time_intervals = [list(map(lambda x: time_to_minutes(x), item.split(' - '))) for item in time_list ]
time_minutes = [time_to_minutes(item) for sublist in time_list for item in sublist.split(' - ')]

аналогичны коду

time_intervals = []
time_minutes = []
for n in time_list:
  [start, end] = n.split(' - ')
  [start_m, end_m] = [time_to_minutes(start), time_to_minutes(end)] 
  time_intervals.append([start_m, end_m])
  time_minutes += [start_m, end_m]
→ Ссылка