[Leetcode] JS 30 Cache With Time Limit

[Leetcode] JS 30 Cache With Time Limit

Question

Write a class that allows getting and setting key-value pairs, however a time until expiration is associated with each key.

The class has three public methods:

set(key, value, duration): accepts an integer key, an integer value, and a duration in milliseconds. Once the duration has elapsed, the key should be inaccessible. The method should return true if the same un-expired key already exists and false otherwise. Both the value and duration should be overwritten if the key already exists.

get(key): if an un-expired key exists, it should return the associated value. Otherwise it should return -1.

count(): returns the count of un-expired keys.

Example 1:

Input:
actions = ["TimeLimitedCache", "set", "get", "count", "get"]
values = [[], [1, 42, 100], [1], [], [1]]
timeDelays = [0, 0, 50, 50, 150]
Output: [null, false, 42, 1, -1]
Explanation:
At t=0, the cache is constructed.
At t=0, a key-value pair (1: 42) is added with a time limit of 100ms. The value doesn't exist so false is returned.
At t=50, key=1 is requested and the value of 42 is returned.
At t=50, count() is called and there is one active key in the cache.
At t=100, key=1 expires.
At t=150, get(1) is called but -1 is returned because the cache is empty.

Example 2:

Input:
actions = ["TimeLimitedCache", "set", "set", "get", "get", "get", "count"]
values = [[], [1, 42, 50], [1, 50, 100], [1], [1], [1], []]
timeDelays = [0, 0, 40, 50, 120, 200, 250]
Output: [null, false, true, 50, 50, -1, 0]
Explanation:
At t=0, the cache is constructed.
At t=0, a key-value pair (1: 42) is added with a time limit of 50ms. The value doesn't exist so false is returned.
At t=40, a key-value pair (1: 50) is added with a time limit of 100ms. A non-expired value already existed so true is returned and the old value was overwritten.
At t=50, get(1) is called which returned 50.
At t=120, get(1) is called which returned 50.
At t=140, key=1 expires.
At t=200, get(1) is called but the cache is empty so -1 is returned.
At t=250, count() returns 0 because the cache is empty.

Constraints:

  • 0 <= key, value <= 109
  • 0 <= duration <= 1000
  • 1 <= actions.length <= 100
  • actions.length === values.length
  • actions.length === timeDelays.length
  • 0 <= timeDelays[i] <= 1450
  • actions[i] is one of "TimeLimitedCache", "set", "get" and "count"
  • First action is always "TimeLimitedCache" and must be executed immediately, with a 0-millisecond delay

My Solution

var TimeLimitedCache = function() {
        this.cache = new Map();
};

/** 
 * @param {number} key
 * @param {number} value
 * @param {number} duration time until expiration in ms
 * @return {boolean} if un-expired key already existed
 */
TimeLimitedCache.prototype.set = function(key, value, duration) {
    let now = Date.now();
    const exists = this.cache.has(key) && this.cache.get(key).expire > now;
    this.cache.set(key, {value, expire: now + duration});
    return exists;
};

/** 
 * @param {number} key
 * @return {number} value associated with key
 */
TimeLimitedCache.prototype.get = function(key) {
    const now = Date.now()
    if (this.cache.has(key)) {
        const entry = this.cache.get(key);
        if (entry.expire > now) {
            return entry.value;
        } else {
            this.cache.delete(key);
        }
    }
    return -1
};

/** 
 * @return {number} count of non-expired keys
 */
TimeLimitedCache.prototype.count = function() {
    const now = Date.now();
    let count = 0;
    
    // Iterate through entries and count only unexpired ones
    for (const [key, entry] of this.cache) {
      if (entry.expire > now) {
        count++;
      } else {
        this.cache.delete(key); // Cleanup expired keys
      }
    }

    return count;
  }

/**
 * const timeLimitedCache = new TimeLimitedCache()
 * timeLimitedCache.set(1, 42, 1000); // false
 * timeLimitedCache.get(1) // 42
 * timeLimitedCache.count() // 1
 */

Key Takeaways

We use prototype to add methods.

In JavaScript, every function (when used as a constructor) has a prototype object, which is used to share methods among instances. If we want to define instance properties, we use this inside the constructor, so that each instance gets its own copy.

var TimeLimitedCache = function() {
        this.cache = new Map(); // bind cache
};
TimeLimitedCache.prototype.set = function(key, value, duration) {
}; //bind a new method called "set"

We can also use class syntax to define instance properties in the constructor and add methods directly inside the class.

class TimeLimitedCache {
    constructor() {
        this.cache = new Map();
    }

    set(key, value, duration) {
        console.log("This works correctly!");
    }
}

const cache = new TimeLimitedCache();
cache.set(1, 42, 1000); // ✅ "This works correctly!"

Map() in JavaScript

The Map object in JavaScript is a built-in data structure that allows you to store key-value pairs, similar to objects {}. However, Map provides more powerful features:

Keys can be any type (not just strings like in objects)

Maintains insertion order

Has built-in methods for easy manipulation which can be seen from below tables

💡
Be careful! Map does not allow duplicate keys. If a key already exists, its value will be overwritten.

Common Map() Methods

MethodDescriptionExample
.set(key, value)Adds or updates a key-value pairmyMap.set('name', 'Alice')
.get(key)Retrieves the value of a keymyMap.get('name') // 'Alice'
.has(key)Checks if a key existsmyMap.has('name') // true
.delete(key)Removes a key-value pairmyMap.delete('name')
.sizeReturns the number of key-value pairsmyMap.size
.clear()Removes all key-value pairsmyMap.clear()
.keys()Returns an iterator of all keysmyMap.keys()
.values()Returns an iterator of all valuesmyMap.values()
.entries()Returns an iterator of all [key, value] pairsmyMap.entries()
.forEach(callback)Iterates through key-value pairsmyMap.forEach((value, key) => console.log(key, value))