我有一个非常有趣(至少对我来说)要解决的问题(并且,不,它不是家庭作业)。它等同于:您需要确定用户在他的计算机前面的“会话”和“会话开始和结束时间”。
您可以获得进行任何用户互动的时间以及最长的不活动时间。如果时间大于或等于两个用户输入之间的不活动时间,则它们是不同会话的一部分。
基本上我得到的输入是这样的(输入没有排序,我不想在确定会话之前对它们进行排序):
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 搜索和插入/合并功能。
答案 0 :(得分:3)
最长延迟
如果日志条目具有“最大延迟”(例如,最大延迟为2小时,则在10:12事件之后将永远不会列出8:12事件),您可以向前看并排序。
排序
或者,我首先尝试排序 - 至少要确保它不起作用。时间戳可以合理地存储在8个字节中(即使你的目的也是4个,你可以将250万个然后放入一个千兆字节)。 Quicksort可能不是这里的最佳选择,因为它具有较低的局部性,插入排序对于几乎排序的数据几乎是完美的(尽管它也具有不良的局部性),或者,快速排序块,然后合并块与合并排序应该做,即使它增加了内存需求。
壁球和征服
或者,您可以使用以下策略:
如果你的日志文件具有你的问题所暗示的“时间局部性”,那么单次传递应该减少数据以允许“完整”排序。
[编辑] [此网站] 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分钟的会话间隔:实际上,您可以随时在线更改此值,因为结构不会推断任何信息,只是一种存储/查看状态记录的实用方法。