算法问题:确定“用户会话”

时间:2010-08-02 11:30:54

标签: algorithm language-agnostic

我有一个非常有趣(至少对我来说)要解决的问题(并且,不,它不是家庭作业)。它等同于:您需要确定用户在他的计算机前面的“会话”和“会话开始和结束时间”。

您可以获得进行任何用户互动的时间以及最长的不活动时间。如果时间大于或等于两个用户输入之间的不活动时间,则它们是不同会话的一部分。

基本上我得到的输入是这样的(输入没有排序,我不想在确定会话之前对它们进行排序):

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 < / em>)搜索它是否属于已知间隔或创建新间隔。

我会为每个输入重复此操作,使解决方案 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)

我不知道描述是否非常明确,但这就是我需要做的事情。

这样的问题有名字吗?你会如何解决它?

修改

如果我计划以我计划的方式解决这个问题,我很想知道应该使用哪种数据结构。我需要 log n 搜索和插入/合并功能。

4 个答案:

答案 0 :(得分:3)

最长延迟
如果日志条目具有“最大延迟”(例如,最大延迟为2小时,则在10:12事件之后将永远不会列出8:12事件),您可以向前看并排序。

排序
或者,我首先尝试排序 - 至少要确保它不起作用。时间戳可以合理地存储在8个字节中(即使你的目的也是4个,你可以将250万个然后放入一个千兆字节)。 Quicksort可能不是这里的最佳选择,因为它具有较低的局部性,插入排序对于几乎排序的数据几乎是完美的(尽管它也具有不良的局部性),或者,快速排序块,然后合并块与合并排序应该做,即使它增加了内存需求。

壁球和征服
或者,您可以使用以下策略:

  1. 将每个事件转换为“持续时间为0的会话”
  2. 将会话列表拆分为块(例如1K值/块)
  3. 在每个块中,按会话开始排序
  4. 合并所有可以合并的会话(之前排序可以让您减少前瞻性)。
  5. 将剩余会话列表压缩为大型单个列表
  6. 重复步骤2,直到列表没有缩短。
  7. 对所有
  8. 进行排序和合并

    如果你的日志文件具有你的问题所暗示的“时间局部性”,那么单次传递应该减少数据以允许“完整”排序。

    [编辑] [此网站] 1演示了“已插入排序完成的优化快速排序”,这对于几乎排序的数据非常有用。就像这个家伙一样std :: sort

答案 1 :(得分:2)

您要求在线算法,即可以为每个新输入时间递增计算一组新会话的算法。

关于为当前会话集选择数据结构,您可以使用平衡二进制搜索树。每个会话由开始时间和结束时间的(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>类;如上所述,元素类型E是由开始和结束时间给出的单个会话。其floor()ceiling()方法将检索我在伪代码中使用sessions[i]sessions[i+1]表示的会话。

答案 2 :(得分:1)

我不知道您的问题的名称或您找到的解决方案的名称。但是你的解决方案(或多或少)是我建议的解决方案。我认为这是解决这类问题的最佳方案。

如果您的数据至少在某种程度上有序,那么您可以通过考虑此顺序找到稍微好一点的解决方案。例如。您的数据可以按日期排序,但不能按时间排序。然后,您将分开各个日期。

答案 3 :(得分:1)

使用间隔搜索树的解决方案听起来就足够了。

您没有说明您提供的数据(仅包含没有 date 的时间戳)是您正在处理的实际数据。如果是这样,请考虑一天只有24 * 60 = 1440分钟。由于这是一个相对较小的值,创建一个位向量(打包或不打包 - 并不重要)感觉它会提供一个高效而简单的解决方案。

位向量(一旦填充)将能够:

  • 回答查询“用户是否在时间T看到过?”在O(1)中,如果您决定仅在输入数据上显示相应的时间时将矢量的字段设置为true(我们可以将此方法称为“保守添加”)或

  • 回答查询“会话在时间T是否有效?”在O(1)中也是如此,但是如果你决定将一个向量的字段设置为true,如果会话在那个时候处于活动状态,那么我的意思是,当你添加时间T时,你也可以将以下29个字段设置为true。

我想要注意的是,通过使用保守的添加,您不会限制自己30分钟的会话间隔:实际上,您可以随时在线更改此值,因为结构不会推断任何信息,只是一种存储/查看状态记录的实用方法。