用于查找基于时间的事件的最佳Javascript算法

时间:2016-04-18 20:11:10

标签: javascript arrays algorithm

我在Javascript中有一个对象数组,其中包含一个以毫秒为单位的事件开始和结束时刻。我们的代码库目前使用一种天真的搜索算法,该算法遍历数组,直到我们找到包含某个时刻的事件:

// time contains the moment of time we're looking to find an event for

profile.tempbasaltreatments.forEach( function eachTreatment (t) {
    if (time <= t.endmills && time >= t.mills) {
      return t;
    }
});

没有使用较大数据集的性能来削减它。什么是一个好的算法/数据模型,可以有效地通过对象数组来查找封装时刻的事件?您可以假设,如果事件重叠,第一次匹配总是足够的。

2 个答案:

答案 0 :(得分:2)

我会建议这些预处理步骤(搜索之前):

  1. 如果出于其他目的需要,可以选择获取原始数组的副本;
  2. 按事件开始时间对数组进行排序。如果可能的话,这应该由数据库完成,数据库可以维护索引。
  3. 从阵列中删除事件,该事件的结束时间早于上一个事件的结束时间。当时间与此删除的事件匹配时,它也可以与之前的事件匹配。由于匹配任何事件都足够好,我们可以删除此事件。
  4. 然后搜索将是二进制的,如下所示:

    1. 将搜索范围设置为整个数组。范围表示为数组中的起始和结束索引(不是两次)
    2. 取该范围的中间元素
    3. 如果此事件与给定时间匹配,请退出
    4. 如果此事件的开始时间大于给定时间,请从步骤2开始,使用所选元素之后的一半范围重复
    5. 否则取另一半范围(在所选元素之前)并从2
    6. 重复
    7. 如果范围没有更多事件并且因故障退出,则停止重复。
    8. 预处理应该只进行一次,如果你仍然需要排序,它的时间复杂度 O(n log n),否则它是 O(n)。完成后,您可以在 O(log n)时间内重复查找事件。

      以下是上面的一些JavaScript代码:

      // Create a copy of the original array and sort it by event start date
      var events = profile.tempbasaltreatments.slice(0).sort(function (a, b) { 
          return a.mills - b.mills;
      });
      
      // Remove events that fall completely within the limits of another event.
      // They are not needed to find an event for a given time.
      for (var i = 0; i < events.length-1;) {
          if (i && events[i].endmills < events[i-1].endmills) {
               events.splice(i, 1);
          } else {
               i++;
          };
      }
      // Now also the remaining events' end dates are sorted
      
      // function for fast search in events:    
      function findEvent(events, time) {
          // Binary search for event
          var first = 0, last = events.length - 1;
          while (first <= last) { 
              var i = first + Math.floor((last - first) / 2);
              var t = events[i];
              if (time >= t.mills && time <= t.endmills) return t;
              if (time < t.mills) {
                  last = i - 1;
              } else { // time > t.endmills
                  first = i + 1;
              }
          }
          // returns undefined
      }
      
      // Example call: find a currently running event:
      var foundEvent = findEvent(events, new Date().getTime());
      

      附录

      以下是在最后一个预处理步骤中进行过滤的方法。首先是在开始时间排序后如何排序事件的时间表:

      a: ---------------
      b:     -------
      c:      ------------------
      d:         --
      e:            --  
      f:                -----
      

      可以消除的事件是b:

      a: ---------------
      c:      ------------------
      d:         --
      e:            --  
      f:                -----
      

      ....然后d:

      a: ---------------
      c:      ------------------
      e:            --  
      f:                -----
      

      ...然后e:

      a: ---------------
      c:      ------------------
      f:                -----
      

      ...和f:

      a: ---------------
      c:      ------------------
      

      很明显,在过滤之前,总覆盖期与原始期相同。

答案 1 :(得分:0)

如果事件按开始时间,“中间时间”或结束时间排序,您可以使用二进制搜索来查找附近的事件,然后执行本地线性搜索以查找包含时间戳的内容(本地搜索)方向取决于排序顺序:如果事件按开始时间排序,则需要查看从最近的开始时间开始减少开始时间的方向。

这种方法的主要问题是当没有最大事件持续时间时它很慢,因为这意味着除了到达列表末尾之外没有本地搜索的停止标准。

更好的方法可能是将事件存储在适合高效访问的数据结构中,例如:一个interval tree