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

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

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 и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
Потяните за рычаг выброса энергососущих проектов
Потяните за рычаг выброса энергососущих проектов
На этой неделе моя команда отменила проект, над которым я работал. Неделя усилий пошла насмарку.
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
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

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