class AuthenticationManager:
def __init__(self, timeToLive: int):
self.timeToLive = timeToLive
self.tokens = {}
def generate(self, tokenId: str, currentTime: int) -> None:
self.tokens[tokenId] = currentTime + self.timeToLive
def renew(self, tokenId: str, currentTime: int) -> None:
if tokenId in self.tokens and self.tokens[tokenId] > currentTime:
self.tokens[tokenId] = currentTime + self.timeToLive
def countUnexpiredTokens(self, currentTime: int) -> int:
return sum(expiry > currentTime for expiry in self.tokens.values())
class AuthenticationManager {
int timeToLive;
unordered_map<string, int> tokens;
public:
AuthenticationManager(int timeToLive) {
this->timeToLive = timeToLive;
}
void generate(string tokenId, int currentTime) {
tokens[tokenId] = currentTime + timeToLive;
}
void renew(string tokenId, int currentTime) {
if (tokens.count(tokenId) && tokens[tokenId] > currentTime) {
tokens[tokenId] = currentTime + timeToLive;
}
}
int countUnexpiredTokens(int currentTime) {
int count = 0;
for (auto& [_, expiry] : tokens) {
if (expiry > currentTime) count++;
}
return count;
}
};
class AuthenticationManager {
private int timeToLive;
private Map<String, Integer> tokens;
public AuthenticationManager(int timeToLive) {
this.timeToLive = timeToLive;
this.tokens = new HashMap<>();
}
public void generate(String tokenId, int currentTime) {
tokens.put(tokenId, currentTime + timeToLive);
}
public void renew(String tokenId, int currentTime) {
if (tokens.containsKey(tokenId) && tokens.get(tokenId) > currentTime) {
tokens.put(tokenId, currentTime + timeToLive);
}
}
public int countUnexpiredTokens(int currentTime) {
int count = 0;
for (int expiry : tokens.values()) {
if (expiry > currentTime) count++;
}
return count;
}
}
class AuthenticationManager {
constructor(timeToLive) {
this.timeToLive = timeToLive;
this.tokens = new Map();
}
generate(tokenId, currentTime) {
this.tokens.set(tokenId, currentTime + this.timeToLive);
}
renew(tokenId, currentTime) {
if (this.tokens.has(tokenId) && this.tokens.get(tokenId) > currentTime) {
this.tokens.set(tokenId, currentTime + this.timeToLive);
}
}
countUnexpiredTokens(currentTime) {
let count = 0;
for (let expiry of this.tokens.values()) {
if (expiry > currentTime) count++;
}
return count;
}
}
The "Design Authentication Manager" problem requires you to implement a system that manages authentication tokens with an expiration policy. Each token is identified by a unique tokenId
and has a limited lifespan defined by timeToLive
seconds. The class must support three operations:
generate(tokenId, currentTime)
: Registers a new token with the given tokenId
at currentTime
. The token will expire after timeToLive
seconds from currentTime
.renew(tokenId, currentTime)
: If the token exists and has not expired, resets its expiry time to currentTime + timeToLive
. If the token has expired or does not exist, do nothing.countUnexpiredTokens(currentTime)
: Returns the number of tokens that are still valid (i.e., not expired) at currentTime
.Key constraints include:
tokenId
is unique.The core challenge is to efficiently track tokens and their expiration times, allowing for fast generation, renewal, and counting of unexpired tokens. The naive approach would be to store all tokens and their expiration times, and whenever we need to count unexpired tokens, iterate through the entire collection and check each expiry. However, this could be slow if there are many tokens.
To optimize, we need a data structure that allows:
tokenId
to its expiry time fits these needs, as lookups, insertions, and updates are all O(1) on average. When counting unexpired tokens, we can iterate through the expiry times and count those that are still valid.
The solution uses a hash map to store each tokenId
and its corresponding expiry time. Here are the steps for each operation:
generate(tokenId, currentTime)
, insert or update the tokenId
in the map with value currentTime + timeToLive
.
renew(tokenId, currentTime)
, check if the token exists and its expiry time is greater than currentTime
. If so, update its expiry to currentTime + timeToLive
. If not, do nothing.
countUnexpiredTokens(currentTime)
, iterate through the map and count tokens whose expiry time is greater than currentTime
.
We use a hash map because it provides O(1) average time complexity for insert, lookup, and update operations, making all three methods efficient.
For even more optimization (not required for this problem), we could periodically clean up expired tokens to save memory. However, for correctness and efficiency in the context of this problem, the hash map approach is sufficient.
Let's walk through an example with timeToLive = 5
:
generate("abc", 1)
: Adds "abc" with expiry at 6 (1 + 5
).generate("def", 2)
: Adds "def" with expiry at 7 (2 + 5
).renew("abc", 4)
: "abc" has expiry 6, which is > 4, so update expiry to 9 (4 + 5
).countUnexpiredTokens(5)
: "abc" (expiry 9) and "def" (expiry 7) are both > 5, so return 2.countUnexpiredTokens(7)
: "abc" (expiry 9) is > 7, "def" (expiry 7) is not (> 7 is false), so return 1.renew("def", 8)
: "def" has expiry 7, which is not > 8, so do nothing.countUnexpiredTokens(10)
: "abc" (expiry 9) is not > 10, so return 0.At each step, we see how the expiry times are updated and how expired tokens are ignored for renewals and counting.
Brute-force approach: If we used a list to store all tokens, counting unexpired tokens would be O(n) where n is the number of tokens. Renew and generate would also be O(n) if searching through a list.
Optimized approach (hash map):
The Authentication Manager problem is elegantly solved using a hash map to track token expirations. This approach ensures fast generation and renewal of tokens, and although counting unexpired tokens is O(n), it is simple and effective for the problem's constraints. The key insight is to use efficient data structures for quick lookups and updates, while handling expiry logic carefully to avoid renewing or counting expired tokens.