实施匹配计数器以记录最近5分钟的匹配问题

时间:2018-11-05 02:33:48

标签: javascript algorithm datetime data-structures

我遇到了这个问题,仍在尝试解决此问题:

  

您要记录网站的点击次数。

     

实现两个功能,   log_hit()在记录匹配时被调用,并且   get_hits_in_last_five_minutes()返回命中总数   在最后五分钟内   假定所有时间戳按递增顺序排列。

我的想法(尝试解决问题):

数据结构:数组。

我的逻辑:

调用log_hit()时,我们基本上将时间存储在数组中的ms中(Date()。getTime()以ms为单位),并将命中的特定时间存储在哈希图中。

function Counter() {
  this.map = {};
  this.store = [];
}

Counter.prototype.log_hit = function() {
  const d = new Date().getTime()/1000;
  this.store.push(d);
  this.map[d] = this.map[d] ? this.map[d]++ : 1;
}

Counter.prototype.get_hits_in_last_five_minutes = function() {
 const fiveMin = 60 * 60; //seconds.
 const initalPointer = new Date().getTime() / 1000 - fiveMin;
 // Somehow retrieve the value and return it back
}

但是,如果我想将解决方案扩展一个小时或其他粒度,我认为这不是解决问题的最佳方法。

我将如何解决此类问题?

2 个答案:

答案 0 :(得分:0)

使用队列是解决此问题的正确方法。

   var HitCounter = function() {
            this.queue = [];
   };

/**
 * Record a hit.
        @param timestamp - The current timestamp (in seconds granularity). 
 * @param {number} timestamp
 * @return {void}
 */
HitCounter.prototype.hit = function(timestamp) {
    this.queue.push(timestamp);
};

/**
 * Return the number of hits in the past 5 minutes.
        @param timestamp - The current timestamp (in seconds granularity). 
 * @param {number} timestamp
 * @return {number}
 */
HitCounter.prototype.getHits = function(timestamp) {
    while(this.queue.length && this.queue[0] <= timestamp-300) {
        this.queue.shift();
    }
    return this.queue.length;
};

const counter = new HitCounter();
counter.hit(1);
counter.hit(2);
counter.hit(3);
counter.getHits(4);
counter.hit(300);
counter.getHits(300);
console.log(counter.getHits(301)); // should output 3.

答案 1 :(得分:0)

我们需要利用这一事实-

  

假设所有时间戳按递增顺序排列。

算法:

  • 我们记录数组中的每个匹配,并将其大小逐渐增加1。
  • 获得编号在最后 5分钟内的匹配(不包括当前匹配)中,由于所有时间戳都是按递增顺序排列的,因此我们进行了binary search来获得答案默认排序。
  • 首先,我们对数组进行二进制搜索,以获取关于提供给t的时间get_hits_in_last_five_minutes()的最后一个有效上限索引。
  • Upper bound所用的限制是我们可以使用点击数判断否的极限。最近5分钟的通话次数。这是必需的,因为问题说明中会显示 get_hits_in_last_five_minutes(),该函数会返回最近五分钟的总点击数。因此,从技术上讲,这意味着它将用作API来检查在参数传递时间t前5分钟之前进行了多少次呼叫。 不能保证,为此方法传递的时间t始终是计数器中最后插入的时间戳。因此,我们需要搜索数组的上限,即直到存储在数组中的命中值可以算作对我们的答案有效的索引为止。

  • 第二,我们从0upper_bound进行二进制搜索,以获取t前最后5分钟内的所有有效匹配。

    < / li>
  • 空间复杂度: O(n),其中n是否。已记录的点击数。

  • 时间复杂度:
    • O(log(n))搜索上限。
    • O(log(n))获取最近5分钟内记录的实际匹配。
    • 总复杂度= O(log(n)) + O(log(n)) = 2 * O(log(n)) = O(log(n))

注意:在存储和搜索时,我将时间t转换为秒。

代码:

function Counter() {
  this.hits = [];
  this.hits_size = 0;
}

Counter.prototype.log_hit = function(t) {
  this.hits[this.hits_size++] = t * 60;
}

Counter.prototype.get_hits_in_last_five_minutes = function(t) {
  if (this.hits_size < 2) return 0;
  t *= 60;
  var upper_bound = this.getUpperBound(t);
  this.last_call_type = 2;
  var low = 0,
    high = upper_bound;
  while (low <= high) {
    var mid = low + parseInt((high - low) / 2);
    if (this.hits[mid] > t - 300) high = mid - 1;
    else if (this.hits[mid] < t - 300) low = mid + 1;
    else return upper_bound - mid + 1;
  }

  return upper_bound - low + 1;
}

Counter.prototype.getUpperBound = function(t) {
  var low = 0,
    high = t > this.hits[this.hits_size - 1] ? this.hits_size - 1 : this.hits_size - 2;
  var ans = 0;
  while (low <= high) {
    var mid = low + parseInt((high - low) / 2);
    if (this.hits[mid] >= t) {
      high = mid - 1;
    } else {
      ans = mid;
      low = mid + 1;
    }
  }

  if (high < 0) return -1;
  return ans;
}


console.log("*****Counter 1******");
var c1 = new Counter();
c1.log_hit(1);
console.log("Registered, 1 = " + c1.get_hits_in_last_five_minutes(1));
c1.log_hit(2);
console.log("Registered, 2 = " + c1.get_hits_in_last_five_minutes(2));
c1.log_hit(3);
console.log("Registered, 3 = " + c1.get_hits_in_last_five_minutes(3));
c1.log_hit(4);
console.log("Registered, 4 = " + c1.get_hits_in_last_five_minutes(4));
c1.log_hit(5);
console.log("Registered, 5 = " + c1.get_hits_in_last_five_minutes(5));
c1.log_hit(6);
console.log("Registered, 6 = " + c1.get_hits_in_last_five_minutes(6));
c1.log_hit(7);
console.log("Registered, 7 = " + c1.get_hits_in_last_five_minutes(7));
c1.log_hit(8);
console.log("Registered, 8 = " + c1.get_hits_in_last_five_minutes(8));
c1.log_hit(9);
console.log("Registered, 9 = " + c1.get_hits_in_last_five_minutes(9));
c1.log_hit(10);
console.log("Registered, 10 = " + c1.get_hits_in_last_five_minutes(10));

console.log("*****Counter 2******");
var c2 = new Counter();
c2.log_hit(2);
console.log("Registered, 2 = " + c2.get_hits_in_last_five_minutes(2));
c2.log_hit(7);
console.log("Registered, 7 = " + c2.get_hits_in_last_five_minutes(7));
c2.log_hit(8);
console.log("Registered, 8 = " + c2.get_hits_in_last_five_minutes(8));
c2.log_hit(9);
console.log("Registered, 9 = " + c2.get_hits_in_last_five_minutes(9));
c2.log_hit(10);
console.log("Registered, 10 = " + c2.get_hits_in_last_five_minutes(10));
c2.log_hit(11);
console.log("Registered, 11 = " + c2.get_hits_in_last_five_minutes(11));
c2.log_hit(12);
console.log("Registered, 12 = " + c2.get_hits_in_last_five_minutes(12));
c2.log_hit(17);
console.log("Registered, 17 = " + c2.get_hits_in_last_five_minutes(17));
console.log("Unregistered, 18 = " + c2.get_hits_in_last_five_minutes(18));
c2.log_hit(19);
console.log("Registered, 19 = " + c2.get_hits_in_last_five_minutes(19));
console.log("Unregistered, 20 = " + c2.get_hits_in_last_five_minutes(20));
c2.log_hit(21);
console.log("Registered, 21 = " + c2.get_hits_in_last_five_minutes(21));
console.log("Unregistered, 6 = " + c2.get_hits_in_last_five_minutes(6));
console.log("Unregistered, 500 = " + c2.get_hits_in_last_five_minutes(500));
console.log("Unregistered, 15 = " + c2.get_hits_in_last_five_minutes(15));
console.log("Registered, 17 = " + c2.get_hits_in_last_five_minutes(17));

为什么要执行二进制搜索?

  • 您可能想知道为什么不向后循环并获得t - 300下的所有这些值的计数。这样,每次通话可能是O(1)
  • 请注意,我们的搜索空间位于t - 300下。如果增加,例如t - 9000,那么我们的向后迭代也会增加直到9000,并且如果get_hits_in_last_five_minutes()的调用次数恰好是10000或更多,则循环的复杂性增加了整体复杂性。因此,可能是
  

10000次致电get_hits_in_last_five_minutes() * 9000

如果我们使用上述算法,它将是

  

10000次调用get_hits_in_last_five_minutes() * log(n)

如果通话永无止境(无限)怎么办?

  • 这取决于我们选择使用get_hits_in_last_five_minutes()方法的方式。

    • 如果传递给t的呼叫所花费的时间get_hits_in_last_five_minutes()始终处于非递减状态,那么我们可以从存储中截断/删除匹配。

      • 要执行此操作,我们可以再次进行二进制搜索,以从不属于t - 300的存储中获取最大值的索引。之后,我们只需执行array slice并将this.hits_size重置为新长度即可。
        • 我们需要用log_hit()方法执行此操作,而不是get_hits_in_last_five_minutes(),因为时间t传递给它不一定是我们注册匹配的一部分
        • 使用array slice可能会增加一点复杂度,因为它返回原始数组的浅表副本,并且复杂度为O(N),其中Nend - start。参见array slice complexity。为了避免这种情况,我们可以创建一个链表,并在其中存储数据,并使用地图存储节点。这样,我们可以重置列表的头部并进行修剪/截断O(1)。我们也需要维护一个被截断的节点数,因为我们无法使用新值重置地图。
    • 如果传递给您网站t的呼叫的时间get_hits_in_last_five_minutes()是任意随机的,那么我们将无法截断任何内容,因为我们需要所有数据来给出答案。在这种情况下,可能会将数据存储在数据库中。好的部分是,您可以从数据库查询您的答案,而无需在javascript中进行计算。