Определить пересекающиеся временные промежутки
Есть список с различными промежутками времени. Например:
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 шт):
Жадный метод - отсортировать по началу интервалов.
Взять первый интервал, далее найти тот, который первым идёт после его конца, и так далее.
Как и обещал, реализовал функцию, выполняющую бòльшую часть работы (даже дебаг-вывод есть). Почитай код, поймешь. Но, чтобы ты не терял интерес к задаче, эта функция имеет баг - если указать сначала вечерний промежуток времени, а потом утренний, то система будет выводить противоположное значение. Она возвращает только булево значение. Много переменных тут ради удобства чтения, а так это можно и в однострочник всунуть
Функция-парсер, выводит 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
не очень понятно какие промежутки считать пересекающимися, а какие не пересекающимися, поэтому сделал так:
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']}
Еще один вариант поиска непрекращающихся интервалов
Алгоритм:
- создаем отсортированный список в минутах включающий все метки начала и окончания интервалов [t0, t1, t2, t3, ...]
- создаем список интервалов в минутах ([[a0, b0], [a1, b1], ... ])
- проходим по первому списку беря текущее и следующее значения и ищем такой интервал во втором списке, при совпадении выводим.
Объяснение:
Первый список содержит полную временную шкалу точек времени начала и окончания. Между двумя ближайшими точками не может быть другой с точки зрения логики, но не все из этого является указанными интервалами, так как две точки могут обозначать конец одного интервала и начало другого, или два конца/начала интервалов. По этой причине мы ищем в интервалах есть ли там интервал заданный двумя соседними точками времени.
На предоставленном графике видно, что только 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]
