Implement a simple in-memory cache with fixed capacity and optional per-entry TTL. Each operation includes its timestamp, and timestamps are non-decreasing. Before every operation, first remove all expired entries. A ('set', key, value, ttl, time) operation stores or overwrites a key and marks it as the most recently used entry. ttl is either None or a positive integer; if ttl is not None, the entry expires at time + ttl, and it is unavailable at that exact expiration time. A ('get', key, time) operation returns the value if the key is still live, otherwise None; successful gets also mark the key as most recently used. If a set causes the number of live entries to exceed capacity, evict the least recently used live entry. A ('keys', time) operation should return the current keys from least recently used to most recently used.
Examples
Input: (2, [('set', 'a', 1, None, 0), ('set', 'b', 2, None, 1), ('get', 'a', 2), ('set', 'c', 3, None, 3), ('get', 'b', 4), ('keys', 4)])
Expected Output: [1, None, ['a', 'c']]
Explanation: After 'get a', A becomes most recently used, so inserting C evicts B.
Input: (2, [('set', 'x', 10, 2, 0), ('get', 'x', 1), ('get', 'x', 2), ('keys', 2)])
Expected Output: [10, None, []]
Explanation: The entry for X expires exactly at time 2.
Input: (2, [('set', 'a', 1, 5, 0), ('set', 'b', 2, None, 1), ('set', 'a', 7, None, 2), ('get', 'a', 6), ('keys', 6)])
Expected Output: [7, ['b', 'a']]
Explanation: Overwriting A removes the old TTL logically and keeps A as the most recently used key.
Input: (0, [('set', 'a', 1, None, 0), ('get', 'a', 1), ('keys', 1)])
Expected Output: [None, []]
Explanation: With zero capacity, nothing can be stored.
Solution
def solution(capacity, operations):
from collections import OrderedDict
import heapq
data = {}
order = OrderedDict()
expiry_heap = []
version = 0
answer = []
def cleanup(now):
while expiry_heap and expiry_heap[0][0] <= now:
exp_time, exp_version, key = heapq.heappop(expiry_heap)
current = data.get(key)
if current is None:
continue
value, current_exp, current_version = current
if current_exp == exp_time and current_version == exp_version and current_exp is not None and current_exp <= now:
del data[key]
if key in order:
del order[key]
for op in operations:
if not op:
continue
kind = op[0]
if kind == 'set':
key, value, ttl, now = op[1], op[2], op[3], op[4]
cleanup(now)
if capacity <= 0:
if key in data:
del data[key]
if key in order:
del order[key]
continue
version += 1
exp_time = None if ttl is None else now + ttl
data[key] = (value, exp_time, version)
order[key] = None
order.move_to_end(key)
if exp_time is not None:
heapq.heappush(expiry_heap, (exp_time, version, key))
if len(data) > capacity:
old_key, _ = order.popitem(last=False)
del data[old_key]
elif kind == 'get':
key, now = op[1], op[2]
cleanup(now)
if key not in data:
answer.append(None)
else:
value, exp_time, current_version = data[key]
order.move_to_end(key)
answer.append(value)
elif kind == 'keys':
now = op[1]
cleanup(now)
answer.append(list(order.keys()))
return answerTime complexity: Amortized O(log n) per operation, where n is the number of live keys. Space complexity: O(n + t), where t is the number of pending TTL heap entries.