Skip to content

Commit

Permalink
feat(book/queue): add recent counter questions and solution
Browse files Browse the repository at this point in the history
  • Loading branch information
amejiarosario committed Aug 29, 2020
1 parent 9a88766 commit 77d4596
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 50 deletions.
57 changes: 28 additions & 29 deletions book/D-interview-questions-solutions.asc
Original file line number Diff line number Diff line change
Expand Up @@ -275,65 +275,64 @@ The stack contains the indexes rather than the temperatures themselves.
:leveloffset: -1


[#queue-q-design-snake-game]
include::content/part02/queue.asc[tag=queue-q-design-snake-game]
[#queue-q-recent-counter]
include::content/part02/queue.asc[tag=queue-q-recent-counter]

This game is perfect to practice working with Queues. There are at least two opportunities to use a Queue. One for the food location and another for the snake body part movements. One very move, we insert a new position into the snake and dequeue the last position to indicate the snake moved. Everytime the snake eats food, it grows one more unit, the food gets dequeue and we place the location of next food (if any).
We are ask to keep track of the request only within a given time window. A queue is a perfect application for this. We can add any new request to Queue. Also, we need to check if the oldest element is outside the window. If so we remove it from the queue.

*Algorithm*:

- Based on the snake head current position, calculate the next position based on the given move `direction`.
- If the new position is outside the boundaries, game over(return -1)
- If the new position has food, remove that eaten food from its queue and go to the next.
- If the new position doesn't have food, remove the tail of the snake since it moved.
- If the snake new position hits itself, game over (return -1). To make this check we have to options:
- Queue: we can visit all the elements on snake queue (body) and check if new position collide. That's `O(n)`
- Set: we can maintain a `set` that has all the snake locations so the check is `O(1)`.
- Move the snake head to new location (enqueue)
- Return the score (snake's length - 1);
- Enqueue new requests
- Take a `peek` at the oldest request on the queue.
- While current timestamp - oldest timestamp, dequeue the oldest.
- Return the length of the queue.

*Implementation*:

[source, javascript]
----
include::interview-questions/design-snake-game.js[tag=description]
include::interview-questions/design-snake-game.js[tag=solution]
include::interview-questions/recent-counter.js[tag=description,solution]
----

As you can see we opted for using a set to trade speed for memory.
IMPLEMENTATION NOTES

*Complexity Analysis*:

- Time: `O(1)`. Insert/Remove from Queue is constant time. Check body collisions is `O(1)` as well when using a set. If you traversed the snake queue, it would be `O(n)`, where `n` is the max length of the snake which is the size of the screen (height x width).
- Space: `O(n + m)`. `m` is the number of food items and `n` is the maximun size of the snake which is (height x width).
- Time: `O(?)`. WHY?
- Space: `O(?)`. WHY?


[#queue-q-FILENAME]
include::content/part02/queue.asc[tag=queue-q-FILENAME]
[#queue-q-design-snake-game]
include::content/part02/queue.asc[tag=queue-q-design-snake-game]

RESTATE REQUIREMENTS AND DESCRIPTIONS
This game is perfect to practice working with Queues. There are at least two opportunities to use a Queue. One for the food location and another for the snake body part movements. One very move, we insert a new position into the snake and dequeue the last position to indicate the snake moved. Everytime the snake eats food, it grows one more unit, the food gets dequeue and we place the location of next food (if any).

*Algorithm*:

- STEP 1
- STEP 2
- STEP 2.1
- STEP 2.2
- Based on the snake head current position, calculate the next position based on the given move `direction`.
- If the new position is outside the boundaries, game over(return -1)
- If the new position has food, remove that eaten food from its queue and go to the next.
- If the new position doesn't have food, remove the tail of the snake since it moved.
- If the snake new position hits itself, game over (return -1). To make this check we have to options:
- Queue: we can visit all the elements on snake queue (body) and check if new position collide. That's `O(n)`
- Set: we can maintain a `set` that has all the snake locations so the check is `O(1)`.
- Move the snake head to new location (enqueue)
- Return the score (snake's length - 1);

*Implementation*:

[source, javascript]
----
include::interview-questions/FILENAME.js[tag=description]
include::interview-questions/FILENAME.js[tag=solution]
include::interview-questions/design-snake-game.js[tag=description,solution]
----

IMPLEMENTATION NOTES
As you can see we opted for using a set to trade speed for memory.

*Complexity Analysis*:

- Time: `O(?)`. WHY?
- Space: `O(?)`. WHY?
- Time: `O(1)`. Insert/Remove from Queue is constant time. Check body collisions is `O(1)` as well when using a set. If you traversed the snake queue, it would be `O(n)`, where `n` is the max length of the snake which is the size of the screen (height x width).
- Space: `O(n + m)`. `m` is the number of food items and `n` is the maximun size of the snake which is (height x width).




Expand Down
41 changes: 20 additions & 21 deletions book/content/part02/queue.asc
Original file line number Diff line number Diff line change
Expand Up @@ -83,39 +83,38 @@ As an experiment, we can see in the following table that if we had implemented t
indexterm:[Runtime, Linear]


==== Interview Questions
==== Practice Questions
(((Interview Questions, Queue)))

// tag::queue-q-design-snake-game[]
===== Design Snake Game

*QU-1*) _Design the `move` function for the snake game. The move function returns an integer representing the current score. If the snake goes out of the given height and width or hit itself return `-1` for game over._
// end::queue-q-design-snake-game[]
// tag::queue-q-recent-counter[]
===== Recent Counter

_Question seen at: Amazon, Bloomberg, Apple_
*QU-1*) _Design a class that counts the most recent requests within a time window._
// end::queue-q-recent-counter[]
_Seen in interviews at: Google, Bloomberg, Yandex_

[source, javascript]
----
include::../../interview-questions/design-snake-game.js[tag=description]
include::../../interview-questions/recent-counter.js[tag=description]
// write you code here
}
----

_Solution: <<queue-q-design-snake-game>>_


_Solution: <<queue-q-recent-counter>>_


// tag::queue-q-design-snake-game[]
===== Design Snake Game

// // tag::queue-q-name-2[]
// ===== NAME2
*QU-2*) _Design the `move` function for the snake game. The move function returns an integer representing the current score. If the snake goes out of the given height and width or hit itself return `-1` for game over._
// end::queue-q-design-snake-game[]

// *QU-2*) _DESCRIPTION_
// // end::queue-q-name-2[]
_Seen in interviews at: Amazon, Bloomberg, Apple_

// [source, javascript]
// ----
// include::../../interview-questions/name-2.js[tag=description]
// // write you code here
// }
// ----
[source, javascript]
----
include::../../interview-questions/design-snake-game.js[tag=description]
----

// _Solution: <<queue-q-name-2>>_
_Solution: <<queue-q-design-snake-game>>_
64 changes: 64 additions & 0 deletions book/interview-questions/recent-counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const { Queue } = require('../../src/index');

// tag::description[]
/**
* Counts the most recent requests within a time window.
* Each request has its timestamp.
* If the time window is 2 seconds,
* any requests that happened more than 2 seconds before the most recent request
* should not count.
*
* @example - The time window is 10 ms.
* const counter = new RecentCounter(10);
* counter.request(1000); // 1 (first request, it counts)
* counter.request(3000); // 1 (last requests was 2000 ms ago, > 10ms, so doesn't count)
* counter.request(3100); // 1 (last requests was 100 ms ago, > 10ms, so doesn't count)
* counter.request(3105); // 2 (last requests was 5 ms ago, <= 10ms, so it counts)
*
* @example - The time window is 3 sec. (3000 ms)
* const counter = new RecentCounter(3000);
* counter.request(100); // 1
* counter.request(1000); // 2
* counter.request(3000); // 3
* counter.request(3100); // 4
* counter.request(3101); // 4 (request at time 100 is out of the 3000 window).
*
*/
class RecentCounter {
// end::description[]
// tag::solution[]
queue = new Queue();
// end::solution[]
// tag::description[]
/**
* @param {number} maxWindow - Max. time window (in ms) for counting requests
* Defaults to 1 second (1000 ms)
*/
constructor(maxWindow = 1000) {
// end::description[]
// tag::solution[]
this.window = maxWindow;
// end::solution[]
// tag::description[]
}

/**
* Add new request and calculate the current count within the window.
* @param {number} timestamp - The current timestamp (increasing order)
* @return {number} - The number of requests within the time window.
*/
request(timestamp) {
// end::description[]
// tag::solution[]
this.queue.enqueue(timestamp);
while (timestamp - this.queue.peek() > this.window)
this.queue.dequeue();

return this.queue.size;
// end::solution[]
// tag::description[]
}
}
// end::description[]

module.exports = { RecentCounter };
21 changes: 21 additions & 0 deletions book/interview-questions/recent-counter.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { RecentCounter } = require('./recent-counter');

describe('Queue: Recent Counter', () => {
it('should count requests within the window', () => {
const counter = new RecentCounter(3000);
expect(counter.request(100)).toEqual(1); // 1
expect(counter.request(1000)).toEqual(2); // 2
expect(counter.request(3000)).toEqual(3); // 3
expect(counter.request(3100)).toEqual(4); // 4
expect(counter.request(3101)).toEqual(4); // 4 (request at time 100 is out of the 3000 window).
});

it('should NOT count requests out of the window', () => {
const counter = new RecentCounter(10);
expect(counter.request(100)).toEqual(1);
expect(counter.request(1000)).toEqual(1);
expect(counter.request(3000)).toEqual(1);
expect(counter.request(3100)).toEqual(1);
expect(counter.request(3101)).toEqual(2);
});
});

0 comments on commit 77d4596

Please sign in to comment.