Алгоритмическая проблема: определение " сеансов пользователя"

у меня есть действительно немного интересная (по крайней мере для меня) проблема для решения (и, нет, это не домашнее задание). Это эквивалентно этому: Вам нужно определить "сеансы" и "время начала и окончания сеансов", на которых пользователь был перед своим компьютером.

вы получаете время, в котором любое взаимодействие с пользователем было сделано и максимальный период бездействия. Если время больше или равно периоду бездействия, прошедшему между двумя пользовательскими входами, то они являются частью разных проведение сессий.

в основном вход, который я получаю, это (входы не сортируются, и я бы предпочел не сортировать их перед определением сеансов):

06:38
07:12
06:17
09:00
06:49
07:37
08:45
09:51
08:29

и, скажем, период бездействия 30 минут.

тогда мне нужно найти три сеанса:

[06:17...07:12]
[07:37...09:00]
[09:51...09:51]

если период бездействия установлен на 12 часов, то я бы просто нашел один большой сеанс:

[06:17...09:51]

как я могу решить это просто?

минимальный действительный период бездействие, которое должно составлять около 15 минут.

Почему мне лучше сортировать заранее, что я получу много данных и только хранить их в памяти будет проблематично. Однако большая часть этих данных должна быть частью одних и тех же сеансов (сеансов должно быть относительно мало по сравнению с объемом данных, возможно, от тысяч до 1 [тысячи пользовательских вводов за сеанс]).

до сих пор я думаю о чтении ввода (скажем, 06: 38) и определение интервала [data-max_inactivity...data+max_inactivity] и для каждого нового ввода используйте дихотомический (log n) поиск, чтобы увидеть, если он попадает в известный интервал или создать новый интервал.

Я бы повторил это для каждого ввода, делая решение N log n AFAICT. Кроме того, хорошо, что он не будет использовать слишком много памяти, поскольку он будет создавать только интервалы (и большинство входов будут падать в известном интервале).

кроме того, каждый раз, когда падает в известном интервале мне пришлось бы изменить нижнюю или верхнюю границу интервала, а затем посмотреть, нужно ли "сливаться" со следующим интервалом. Например (для максимального периода бездействия 30 минут):

[06:00...07:00]  (because I got 06:30)
[06:00...07:00][07:45...08:45]   (because I later got 08:15)
[06:00...08:45] (because I just received 07:20)

Я не знаю, очень ли ясно описание, но это то, что мне нужно сделать.

такие проблемы есть имя? Как бы Вы ее решили?

редактировать

мне очень интересно узнать, какие данные структура, которую я должен использовать, если я планирую решить ее так, как я планирую. Мне нужны оба!--25-->log n возможность поиска и вставки / слияния.

4 ответов


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

относительно выбора структуры данных для текущего набора сеансов можно использовать сбалансированное двоичное дерево поиска. Каждый сеанс представлен парой (start,end) времени начала и времени окончания. Узлы дерева поиска упорядочены по их start времени. Поскольку ваши сеансы разделены по крайней мере max_inactivity, т. е. нет двух сеансов перекрытия, это гарантирует, что end раз заказать. Другими словами, упорядочение по времени начала уже будет упорядочивать сеансы последовательно.

вот какой-то псевдокод для вставки. Для удобства записи мы делаем вид, что sessions является массивом, хотя на самом деле это двоичное дерево поиска.

insert(time,sessions) = do
    i <- find index such that
         sessions[i].start <= time && time < session[i+1].start

    if (sessions[i].start + max_inactivity >= time)
        merge  time  into  session[i]
    else if (time >= sessions[i+1].start - max_inactivity)
        merge  time  into  sessions[i+1]
    else
        insert  (time,time)  into  sessions

    if (session[i] and session[i+1] overlap)
        merge  session[i] and session[i+1]

на merge операция может быть реализована путем удаления и вставки элементов в двоичный поиск дерево.

этот алгоритм займет время O (N log m), где m-максимальное количество сеансов, которое, как вы сказали, довольно мало.

конечно, реализация сбалансированного бинарного дерева поиска - непростая задача, в зависимости от языка программирования. Ключ здесь в том, что вы должны разделить дерево по ключу, и не каждая готовая библиотека поддерживает эту операцию. Для Java я бы использовал TreeSet<E> class; как сказано, тип элемента E это один сеанс дается время начала и конца. Его floor() и ceiling() методы извлечения сессий я обозначается sessions[i] и sessions[i+1] в моем псевдо-код.


Максимальная Задержка
Если записи журнала имеют "максимальную задержку" (например, с максимальной задержкой в 2 часа, событие 8:12 никогда не будет указано после события 10:12), вы можете посмотреть вперед и отсортировать.

Вроде
В качестве альтернативы, я бы сначала попробовал сортировку - по крайней мере, чтобы убедиться, что она не работает. Временная метка может быть разумно сохранена в 8 байтах (4 даже для ваших целей, вы можете поместить 250 миллионов затем в гигабайт). Quicksort не может быть лучший выбор здесь, поскольку он имеет низкую локальность, сортировка вставки почти идеальна для почти отсортированных данных (хотя она также имеет плохую локальность), альтернативно, быстрая сортировка кусков, а затем слияние кусков с сортировкой слияния должно делать, даже если это увеличивает требования к памяти.

сквош и властвуй
Кроме того, вы можете использовать следующую стратегию:

  1. преобразуйте каждое событие в "сеанс длительностью 0"
  2. разделите свой список сеансы в куски (например, значения 1K / chunk)
  3. внутри каждого куска сортировка по началу сеанса
  4. объединить все сеансы, чем могут быть объединены (сортировка до позволяет уменьшить ваш взгляд вперед).
  5. Компактируйте список оставшихся сеансов в большой единый список
  6. повторите шаг 2, пока список не короче.
  7. сортировка и слияние по всем

Если ваши файлы журнала имеют вид " temporal locality "ваш вопрос предполагает, что уже один проход должен уменьшить данные, чтобы разрешить" полную " сортировку.

[редактировать] [этом сайте]1 демонстрирует "оптимизированный quicksort с вставкой сортировки", который довольно хорош для почти отсортированных данных. Как и у этого парня std:: sort


Я не знаю имени вашей проблемы или имени решения, которое вы нашли. Но ваше решение-это (более или менее) решение я предлагаю. Я думаю, это лучшее решение для такого рода проблем.

Если ваши данные, по крайней мере, несколько упорядочены, вы можете найти немного лучшее решение, принимая во внимание этот заказ. Е. Г. ваши данные могут быть упорядочены по дате, но не по времени. Затем отдельные даты.


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

вы не говорите, были ли данные, которые вы предоставили (состоящие исключительно из временных меток без дата) - это фактические данные, которые вы обрабатываете. Если это так, учтите, что в сутках всего 24 * 60 = 1440 минут. Поскольку это относительно небольшое значение, создание бит-вектора (упакованного или нет - - - не имеет значения) кажется, что это обеспечит как эффективный, так и простой решение.

бит-вектор (после заполнения) будет способен:

  • ответ на запрос " был ли пользователь замечен во время T?"в O (1), Если вы решили установить поле вектора в true только тогда, когда соответствующее время появилось на ваших входных данных (мы можем назвать этот метод "консервативным добавлением") или

  • ответ на запрос " был ли сеанс активен во время T?"в O (1) также, но с большей константой, если вы решите установите для поля вектора значение true, если сеанс был активен в то время-под этим я подразумеваю, что при добавлении времени T вы также устанавливаете следующие 29 полей в true.

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