6 changed files with 279 additions and 141 deletions
@ -1,105 +0,0 @@ |
|||||||
"""Модуль, содержащий функции для чистки интервалов. |
|
||||||
Под 'чисткой' подразумевается: |
|
||||||
- удаление меньших интервалов, полностью входящих в большие (поглощение) |
|
||||||
- расширение пересекающихся интервалов (сумма интервалов) |
|
||||||
""" |
|
||||||
|
|
||||||
|
|
||||||
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 |
|
@ -0,0 +1,67 @@ |
|||||||
|
"""Модуль, содержащий функции для чистки интервалов. |
||||||
|
Под 'чисткой' подразумевается: |
||||||
|
- удаление меньших интервалов, полностью входящих в большие (поглощение) |
||||||
|
- расширение пересекающихся интервалов (сумма интервалов) |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
def absorb_small_included_intervals(intervals: list) -> list: |
||||||
|
"""Возвращает список интервалов без вложенных интервалов. |
||||||
|
|
||||||
|
Вложенный интервал - интервал меньшего размера, полностью входящий |
||||||
|
в больший. |
||||||
|
|
||||||
|
Стоит учитывать, что функция работает только если интервалы |
||||||
|
совпадают по началу или концу, если интервалы входят друг в друга |
||||||
|
частично (пересекаются) -- здесь мы их не обрабатываем. |
||||||
|
|
||||||
|
""" |
||||||
|
# запоминаем самый перевый интервал, раньше него интервалов не |
||||||
|
# будет. Если текущий интервал входит в последний добавленный |
||||||
|
# нами, т.е. он заканчивается раньше, чем тот который мы добавили, |
||||||
|
# то мы его не добавляем в результат, так как он вложенный |
||||||
|
big_intervals = [intervals[0]] |
||||||
|
for i in range(1, len(intervals)): |
||||||
|
if big_intervals[-1][1] >= intervals[i][1]: |
||||||
|
continue |
||||||
|
else: |
||||||
|
big_intervals.append(intervals[i]) |
||||||
|
|
||||||
|
return big_intervals |
||||||
|
|
||||||
|
|
||||||
|
def merge_intersected_intervals(intervals) -> list: |
||||||
|
"""Возвращает расширенные интервалы. |
||||||
|
|
||||||
|
Расширенные интервалы -- это интервалы, состоящие из суммы |
||||||
|
частично пересекающихся интервалов.""" |
||||||
|
|
||||||
|
# запоминаем самый первый интервал. Проходимся по списку, и если |
||||||
|
# начало текущего интервала меньше, чем конец последнего |
||||||
|
# добавленного, то мы расширяем записанный интервал до конца |
||||||
|
# текущего. В противном случае просто добавляем текущий интервал в |
||||||
|
# итоговый список интервалов. |
||||||
|
res = [intervals[0]] |
||||||
|
for i in range(1, len(intervals)): |
||||||
|
if res[-1][1] >= intervals[i][0]: |
||||||
|
res[-1][1] = max(intervals[i][1], res[-1][1]) |
||||||
|
else: |
||||||
|
res.append(intervals[i]) |
||||||
|
|
||||||
|
return res |
||||||
|
|
||||||
|
|
||||||
|
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)] |
||||||
|
|
||||||
|
# убираем вложенные интервалы |
||||||
|
n_intervals = absorb_small_included_intervals(intervals_) |
||||||
|
|
||||||
|
# расширяем пересекающиеся интервалы |
||||||
|
result = merge_intersected_intervals(n_intervals) |
||||||
|
|
||||||
|
return result |
@ -0,0 +1,48 @@ |
|||||||
|
"""Модуль поиска пересечений интервалов.""" |
||||||
|
|
||||||
|
|
||||||
|
def has_intersection(interval_A, interval_B) -> bool: |
||||||
|
"""Пересекаются ли интервалы.""" |
||||||
|
if interval_A[1] <= interval_B[0]: |
||||||
|
return False |
||||||
|
|
||||||
|
if interval_B[1] <= interval_A[0]: |
||||||
|
return False |
||||||
|
|
||||||
|
return True |
||||||
|
|
||||||
|
|
||||||
|
def get_intersection(interval_A, interval_B) -> list: |
||||||
|
"""Возвращает пересечение двух интервалов, если они не |
||||||
|
пересекаются, возвращает пустой интервал.""" |
||||||
|
if not has_intersection(interval_A, interval_B): |
||||||
|
return [] |
||||||
|
|
||||||
|
begin = max(interval_A[0], interval_B[0]) |
||||||
|
end = min(interval_A[1], interval_B[1]) |
||||||
|
|
||||||
|
return [begin, end] |
||||||
|
|
||||||
|
|
||||||
|
def get_all_intersections(intervals_A, intervals_B) -> list: |
||||||
|
"""Принимает два списка интервалов, для которых нужно найти |
||||||
|
пересечения. Возвращает список найденных пересечений.""" |
||||||
|
|
||||||
|
# задаем индексы для каждого из интервалов |
||||||
|
p_A, p_B = 0, 0 |
||||||
|
res = [] |
||||||
|
|
||||||
|
# Если текущий интервал в списке intervals_A заканчивается позже, |
||||||
|
# чем текущий интервал в списке intervals_B, нам нужно перейти на |
||||||
|
# следующий интервал в списке intervals_B. Таким образом по |
||||||
|
# спискам мы пройдем всего лишь раз. |
||||||
|
while p_A < len(intervals_A) and p_B < len(intervals_B): |
||||||
|
A, B = intervals_A[p_A], intervals_B[p_B] |
||||||
|
if has_intersection(A, B): |
||||||
|
res.append(get_intersection(A, B)) |
||||||
|
|
||||||
|
if A[1] <= B[1]: |
||||||
|
p_A += 1 |
||||||
|
else: |
||||||
|
p_B += 1 |
||||||
|
return res |
@ -0,0 +1,158 @@ |
|||||||
|
from task_3 import has_intersection, get_intersection |
||||||
|
|
||||||
|
|
||||||
|
def test_case_1(): |
||||||
|
test = [ |
||||||
|
[1594705106, 1594706480], |
||||||
|
[1594705158, 1594705773], |
||||||
|
[1594705849, 1594706480], |
||||||
|
] |
||||||
|
result = [ |
||||||
|
[1594705106, 1594706480], |
||||||
|
[1594705158, 1594705773], |
||||||
|
] |
||||||
|
# print(f"{test=}") |
||||||
|
|
||||||
|
answer = enlarge_elapsed_intervals(test) |
||||||
|
assert answer == result, f"Error in test_case_1:\n{answer} \n {result}" |
||||||
|
|
||||||
|
|
||||||
|
def test_case_2(): |
||||||
|
test = [ |
||||||
|
[1594705106, 1594706480], |
||||||
|
[1594705106, 1594706490], |
||||||
|
[1594705849, 1594706495], |
||||||
|
] |
||||||
|
result = [ |
||||||
|
[1594705106, 1594706490], |
||||||
|
[1594705849, 1594706495], |
||||||
|
] |
||||||
|
# print(f"{test=}") |
||||||
|
|
||||||
|
answer = enlarge_elapsed_intervals(test) |
||||||
|
assert answer == result, f"Error in test_case_1:\n{answer}\n {result}" |
||||||
|
|
||||||
|
|
||||||
|
def test_case_3(): |
||||||
|
test = [ |
||||||
|
[21, 30], |
||||||
|
[25, 32], |
||||||
|
[33, 40], |
||||||
|
[33, 35], |
||||||
|
[41, 45], |
||||||
|
[42, 43], |
||||||
|
[44, 45], |
||||||
|
[46, 50], |
||||||
|
[47, 51], |
||||||
|
] |
||||||
|
result = [ |
||||||
|
[21, 32], |
||||||
|
[33, 40], |
||||||
|
[41, 45], |
||||||
|
[46, 51], |
||||||
|
] |
||||||
|
|
||||||
|
answer = [] |
||||||
|
for i in range(len(test)): |
||||||
|
for k in range(i, len(test)): |
||||||
|
a = find_inner_sum_of_intersections(test[i], test[k]) |
||||||
|
answer.append(a) |
||||||
|
print(f"{answer=}") |
||||||
|
answer = [a for a in answer if a] |
||||||
|
test.extend(answer) |
||||||
|
print("!___") |
||||||
|
print(test) |
||||||
|
answer = enlarge_elapsed_intervals(test) |
||||||
|
|
||||||
|
print(f"{answer=}") |
||||||
|
assert answer == result, f"Error in test_case_1:\n{answer}\n {result}" |
||||||
|
|
||||||
|
|
||||||
|
def test_case_4(): |
||||||
|
test = [ |
||||||
|
[21, 30], |
||||||
|
[33, 40], |
||||||
|
[33, 35], # |
||||||
|
[41, 45], |
||||||
|
[42, 43], # |
||||||
|
[44, 45], # |
||||||
|
] |
||||||
|
result = [ |
||||||
|
[21, 30], |
||||||
|
[33, 40], |
||||||
|
[41, 45], |
||||||
|
] |
||||||
|
|
||||||
|
answer = absorb_small_included_intervals(test) |
||||||
|
assert answer == result, f"Error in test_case_4:\n{answer}\n {result}" |
||||||
|
|
||||||
|
|
||||||
|
def test_case_5(): |
||||||
|
test = [ |
||||||
|
[21, 30], # |
||||||
|
[28, 40], # |
||||||
|
[33, 35], |
||||||
|
[41, 45], |
||||||
|
[42, 43], |
||||||
|
[44, 45], |
||||||
|
[53, 60], |
||||||
|
[51, 55], |
||||||
|
] |
||||||
|
result = [ |
||||||
|
[21, 40], |
||||||
|
[41, 45], |
||||||
|
[51, 60], |
||||||
|
] |
||||||
|
|
||||||
|
answer = get_extended_intervals(test) |
||||||
|
assert answer == result, f"Error in extend_intervals:\n{answer}\n {result}" |
||||||
|
|
||||||
|
|
||||||
|
def absorb(): |
||||||
|
test = [ |
||||||
|
[21, 30], |
||||||
|
[33, 40], |
||||||
|
[33, 35], # |
||||||
|
[41, 45], |
||||||
|
[42, 43], # |
||||||
|
[44, 45], # |
||||||
|
] |
||||||
|
result = [ |
||||||
|
[21, 30], |
||||||
|
[33, 40], |
||||||
|
[41, 45], |
||||||
|
] |
||||||
|
|
||||||
|
test_b = {i[0]: i[1] for i in test} |
||||||
|
beg = [test[k] for k, v in enumerate(test_b) if test[k][1] < test_b[v]] |
||||||
|
|
||||||
|
test_e = {i[1]: i[0] for i in test} |
||||||
|
end = [test[k] for k, v in enumerate(test_e) if test[k][0] > test_e[v]] |
||||||
|
|
||||||
|
print(beg, end) |
||||||
|
|
||||||
|
|
||||||
|
def inters(): |
||||||
|
print(has_intersection([21, 25], [22, 30])) |
||||||
|
print(has_intersection([22, 30], [21, 25])) |
||||||
|
print(has_intersection([21, 25], [26, 30])) |
||||||
|
print(has_intersection([26, 30], [21, 25])) |
||||||
|
|
||||||
|
|
||||||
|
def inters2(): |
||||||
|
print(get_intersection([21, 25], [22, 30])) |
||||||
|
print(get_intersection([22, 30], [21, 25])) |
||||||
|
print(get_intersection([22, 30], [31, 33])) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
|
||||||
|
# test_case_1() |
||||||
|
# test_case_2() |
||||||
|
# test_case_3() |
||||||
|
# test_case_4() |
||||||
|
# test_case_5()\ |
||||||
|
# absorb() |
||||||
|
|
||||||
|
# inters() |
||||||
|
inters2() |
Loading…
Reference in new issue