0

This is more of a design question, but I was just wondering if anyone had any thoughts on this question.

During an interview, I was asked to design 2 methods.

hit();
getNumHits();

hit() is called every time a user makes a request to the server and getNumHits() returns the number of hits in the last minute.

Any ideas?

Update:

I know this a duplicate of

Implementation of a "hits in last [second/minute/hour]" data structure

But I am having some difficulty understanding the C++ code, wondering if we could get a Java version of the answer!

Community
  • 1
  • 1
simonzhu
  • 168
  • 2
  • 8

1 Answers1

2

The core of what you are looking for is to keep a list of all hits within the last minute. Using a LinkedList allows for a fast removeFirst().

class HitCounter {

    // My most recent list - only the last minute is retained.
    LinkedList<Long> hitList = new LinkedList<>();
    private static final long OneMinute = 1000L * 60L;

    public void hit() {
        // Track the hit.
        hitList.add(System.currentTimeMillis());
        // Always clean up.
        cleanup();
    }

    public int hitCount() {
        // Make sure we are clean.
        cleanup();
        return hitList.size();
    }

    private void cleanup() {
        // Eat all stale hits.
        while (hitList.getFirst() < System.currentTimeMillis() - OneMinute) {
            hitList.removeFirst();
        }
    }
}

If, however, your hits come very fast and you doubt it would be efficient to retain all times of all hits you can use a bucket system to quantize. Here I keep counts for each second. Obviously you could be out by as many hits there are in a second.

class HitCounter {

    // One bucket - keeps track of how many hits since the start time.
    private class Bucket {

        // Probably should use an atomic.
        int count;
        // The time this bucket was started.
        long started = System.currentTimeMillis();
    }
    // My most recent list - only the last minute is retained.
    LinkedList<Bucket> hitList = new LinkedList<>();
    private static final long OneSecond = 1000L;
    private static final long OneMinute = OneSecond * 60L;

    public void hit() {
        // Grab the hit time.
        long now = System.currentTimeMillis();
        // Normally goes in the last bucket.
        Bucket bucket = hitList.size() > 0 ? hitList.getLast() : null;
        // Time for new bucket?
        if (bucket == null || now > bucket.started + OneSecond) {
            // Yup!
            hitList.add(bucket = new Bucket());
        }
        // Track the hit.
        bucket.count++;
        // Always clean up.
        cleanup();
    }

    public int hitCount() {
        // Make sure we are clean.
        cleanup();
        // Add up all the bucket counts.
        int total = 0;
        for (Bucket bucket : hitList) {
            total += bucket.count;
        }
        return total;
    }

    private void cleanup() {
        // Eat all stale hits.
        while (hitList.size() > 0 && hitList.getFirst().started < System.currentTimeMillis() - OneMinute - OneSecond) {
            hitList.removeFirst();
        }
    }
}

public void test() throws InterruptedException {
    HitCounter hitList = new HitCounter();
    for (int i = 0; i < 90; i++) {
        hitList.hit();
        System.out.println(i + " - " + hitList.hitCount());
        Thread.sleep(1000);
    }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • this code only returns the correct value for `hitCount`, if `hit` was called by chance just before. E.g.: calling `hit`, waiting for 2 minutes and afterwards calling `hitCount` will return 1. –  Feb 10 '16 at 15:43
  • @simonzhu - Hmmm - that may require some tweaking. I would propably hold a ring of buckets each holding the count recorded in that second. – OldCurmudgeon Feb 10 '16 at 16:50
  • @simonzhu - Second option added - less accurate but not prone to memory hogging. – OldCurmudgeon Feb 10 '16 at 17:01