"""Функции для чистки инервалов.""" def get_smaller_interval(interval_A, interval_B) -> list: """Если один из интервалов включает в себя другой, возвращаем меньший. Если интервалы не входят друг в друга, возвращаем пустой интервал.""" if interval_A[0] <= interval_B[0] and interval_A[1] >= interval_B[1]: return interval_B if interval_B[0] <= interval_A[0] and interval_B[1] >= interval_A[1]: return interval_A return [] def absorb_small_included_intervals(intervals: list) -> list: """Возвращает список интервалов без вложенных интервалов. Вложенный интервал - интервал меньшего размера, полностью входящий в больший. Стоит учитывать, что функция работает только если интервалы совпадают по началу или концу, если интервалы входят друг в друга частично (пересекаются) -- здесь мы их не обрабатываем. """ # формируем список из меньших интервалов, входящих в бОльшие small_intervals = [ get_smaller_interval(interval, intervals[k]) for i, interval in enumerate(intervals) for k in range(i + 1, len(intervals)) if get_smaller_interval(interval, intervals[k]) ] # в качестве результа у нас будет новый список, состоящий из # принятого списка интервалов `intervals`, за исключением входящих меньших # интервалов `small_intervals` result = [i for i in intervals if i not in small_intervals] return result def get_union_of_partly_intersecting_intervals(interval_A, interval_B) -> list: """Если интервалы A и B пересекаются, то возвращаем новый интервал, включающий A и B.""" # если один из интервалов пустой, то пересечение будет пустым if not interval_A or not interval_B: return [] # если какой-то из интервалов имеет нулевую длину (начало=конец), # то пересечение пустое if interval_A[0] == interval_A[1] or interval_B[0] == interval_B[1]: return [] # нужно проверить, что интервалы вообще пересекаются # A и B не пересекаются, если # начало A > конец B, или если начало B > конец A if interval_A[0] > interval_B[1] or interval_B[0] > interval_A[1]: return [] # сумма интервалов: берем наименьшее начало из A и B, и наибольший конец # из A и B interval_begin = min(interval_A[0], interval_B[0]) interval_end = max(interval_A[1], interval_B[1]) return [interval_begin, interval_end] def get_extended_intervals(intervals) -> list: """Возвращает расширенные интервалы из списка интервалов. Расширенный интервал -- это интервал, который есть сумма двух пересекающихся интервалов.""" extended_intervals = [ get_union_of_partly_intersecting_intervals(interval, intervals[k]) for i, interval in enumerate(intervals) for k in range(i + 1, len(intervals)) if get_union_of_partly_intersecting_intervals(interval, intervals[k]) ] # список extended_intervals может содержать дублирующиеся # интервалы из списка intervals, поэтому мы возвращаем только # отсутствующие в intervals значения uniq_extended_intervals = [i for i in extended_intervals if i not in intervals] return uniq_extended_intervals def normilize_intervals(intervals) -> list: """Разбивает полученный список на пары, расширяет интервалы, убирает вложенные интервалы.""" # из списка [1,2,3,4] формируем [[1,2], [3,4]] intervals_ = [intervals[i : i + 2] for i in range(0, len(intervals), 2)] # получаем расширенные интервалы extended_intervals = get_extended_intervals(intervals_) # убираем вложенные инетрвалы n_intervals = absorb_small_included_intervals(intervals_ + extended_intervals) return n_intervals