Поиск перекрывающихся интервалов в наборе интервалов

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

P1: [1,7]
P2: [2,5]
P3: [3,4]
P4: [6,8]

Думайте об этом как об интервалах от человека 1 до 4. Я хочу, чтобы результат алгоритма был примерно таким:

P1, P2 : [2, 3]
P1, P2, P3 : [3, 4]
P1, P2 : [4, 5]
P1, P4 : [6,7]

Я попытался решить проблему с двумя циклами for, чтобы мы получили список людей, чьи интервалы перекрываются, но проблема заключается в том, чтобы иметь дело с интервалами для более чем одного человека. например, в приведенном выше примере [3,4] не обязательно должно находиться в [4, 5] в строке три, потому что он рассчитывается как интервал для трех человек.

Не могли бы вы опубликовать свой код? Это поможет лучше понять ваш фактический вклад...

Dani Mesejo 10.01.2023 16:04

Этот подход должен работать с минимальной дополнительной бухгалтерией: stackoverflow.com/questions/15013800/… (дополнительная бухгалтерия заключается в том, что вместо того, чтобы отслеживать количество, отслеживать отдельных лиц)

Dave 10.01.2023 17:02
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
72
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Закодируйте все границы интервала в виде 1:+P1, 7:-P1. Затем отсортируйте все границы в хронологическом порядке и просканируйте по возрастанию времени. Также обновляйте список лиц, присутствующих при «выполнении» операций вставки или удаления.

{} 1:+P1 {P1} 2:+P2 {P1, P2} 3:+P3 {P1, P2, P3} 4:-P3 {P1, P2} 5:-P2 {P1} ...

Каждая конфигурация возникает между одной временной привязкой и следующей.

Некоторые конфигурации могут со временем повторяться, но вы не указали, как с ними работать. При указанном выше методе они будут перечислены отдельно.

Один подход, предполагающий, что ввод является словарем человека -> интервал:

from collections import defaultdict
from itertools import pairwise

data = {"P1": (1, 7),
        "P2": (2, 5),
        "P3": (3, 4),
        "P4": (6, 8)}

table = defaultdict(list)
for key, (start, end) in data.items():
    for i, j in pairwise(range(start, end + 1)):
        table[(i, j)].append(key)

result = {k: v for k, v in table.items() if len(v) > 1}

for key, value in result.items():
    print(value, key)

Выход

['P1', 'P2'] (2, 3)
['P1', 'P2', 'P3'] (3, 4)
['P1', 'P2'] (4, 5)
['P1', 'P4'] (6, 7)

Он такой маленький, но я использую временные метки, и этот код требует много времени для моего кода. Однако я благодарю вас за ваш ответ. Я не знал об itertools в python. Это так полезно.

Mohammad.J 12.01.2023 16:57
Ответ принят как подходящий

В значительной степени вдохновлен Минимальным количеством платформ, необходимым для железнодорожной станции, но с сохранением набора присутствующих людей вместо количества присутствующих поездов:

Также реализация алгоритма намекает в ответе Ива Дауста.

Идея состоит в том, чтобы сначала построить хронологический список всех событий, где событием является либо приход, либо уход человека.

Затем мы повторяем список событий, сохраняя набор присутствующих людей, добавляя прибывающих и удаляя уходящих.

Ниже хронологический список событий реализован как время сопоставления Python dict с событиями Python list, так что события, происходящие в одно и то же время, если таковые имеются, естественным образом группируются вместе.

from operator import itemgetter

data = {
    'P1': [1,7],
    'P2': [2,5],
    'P3': [3,4],
    'P4': [6,8]
}

def get_overlaps(data):
    events = {}
    for person, (start, end) in data.items():
        events.setdefault(start, []).append((person, set.add))
        events.setdefault(end, []).append((person, set.remove))
    # events = {1: [('P1', add)], 2: [('P2', add)], 3: [('P3', add)], 4: [('P3', remove)], 5: [('P2', remove)], 6: [('P4', add)], 7: [('P1', remove)], 8: [('P4', remove)]}
    present_persons = set()
    start_time = 0
    for new_time, grouped_events in sorted(events.items(), key=itemgetter(0)):
        if len(present_persons) >= 2:
            yield (frozenset(present_persons), (start_time, new_time))
        start_time = new_time
        for new_person, add_or_remove in grouped_events:
            add_or_remove(present_persons, new_person)

data = {
    'P1': [1,7],
    'P2': [2,5],
    'P3': [3,4],
    'P4': [6,8]
}
overlaps = list(get_overlaps(data))

print(overlaps)
# [(frozenset({'P2', 'P1'}),       (2, 3)),
#  (frozenset({'P2', 'P1', 'P3'}), (3, 4)),
#  (frozenset({'P2', 'P1'}),       (4, 5)),
#  (frozenset({'P4', 'P1'}),       (6, 7))]

data2 = { # three events at time 5: (P1,remove), (P2,remove), (P4,add) 
    'P1': [1,5],
    'P2': [2,5],
    'P3': [3,4],
    'P4': [5,8]
}

overlaps = list(get_overlaps(data2))

print(overlaps)
# [(frozenset({'P2', 'P1'}), (2, 3)),
#  (frozenset({'P2', 'P1', 'P3'}), (3, 4)),
#  (frozenset({'P2', 'P1'}), (4, 5))]

Этот алгоритм имеет временную сложность O(N), где N — количество человек. Для сравнения, алгоритм в ответе Дани Месехо имеет временную сложность O (TN), где N — количество людей, а T — максимальное время присутствия любого человека, измеряемое количеством мгновений.

Спасибо за отличный код и решение. Я узнал хорошие вещи из этого. У меня есть несколько вопросов, почему вы использовали замороженный набор? а почему вы написали это как генератор? Влияют ли они на время выполнения? Я использую библиотеку datetime для времени, и ваша реализация очень быстрая.

Mohammad.J 12.01.2023 16:53

Если хотите, вы можете использовать set вместо FrozenSet, но важно убедиться, что никакие два множества не являются одним и тем же объектом. Попробуйте следующий код: def f(): a = set(); for x in range(4): a.add(x); yield a, а затем print(list(f())). Вместо [{0}, {0,1}, {0,1,2}, {0,1,2,3}], как вы могли ожидать, будет напечатано [{0,1,2,3}, {0,1,2,3}, {0,1,2,3}, {0,1,2,3}]. А если вы сделаете def g(): a = set(): for x in range(4): a.add(x); yield frozenset(a), то получите то, что ожидаете.

Stef 12.01.2023 17:03

Я написал его как генератор, потому что обычно проще писать функции таким образом на python, но вы можете легко написать его как функцию, которая вместо этого возвращает список, добавив result = [] перед циклом и заменив yield ... на result.append(...) в цикле, и добавление return result после цикла.

Stef 12.01.2023 17:06

Другие вопросы по теме