Implement a time-versioned key-value store. You will receive a sequence of operations of two forms: ('set', key, value, t) and ('get', key, t). A get must return the value associated with the greatest timestamp less than or equal to t for that key, or None if no such version exists. Timestamps may arrive out of order. If the same key is set multiple times at the same timestamp, the latest set should deterministically replace the previous value. Return the results of all get operations in order. Aim for O(log v) lookup/update per key, where v is the number of versions for that key.
Examples
Input: ([('set', 'a', 'x', 5), ('set', 'a', 'y', 10), ('get', 'a', 4), ('get', 'a', 5), ('get', 'a', 7), ('get', 'a', 10), ('get', 'a', 100)],)
Expected Output: [None, 'x', 'x', 'y', 'y']
Explanation: Basic predecessor queries on one key.
Input: ([('set', 'k', 'late', 10), ('set', 'k', 'early', 3), ('set', 'k', 'replace', 10), ('get', 'k', 2), ('get', 'k', 3), ('get', 'k', 9), ('get', 'k', 10)],)
Expected Output: [None, 'early', 'early', 'replace']
Explanation: Out-of-order inserts are allowed, and the second write at timestamp 10 replaces the earlier value.
Input: ([('set', 'a', '1', 1), ('set', 'b', '2', 2), ('set', 'a', '3', 3), ('get', 'b', 2), ('get', 'a', 2), ('get', 'c', 5)],)
Expected Output: ['2', '1', None]
Explanation: Multiple keys are independent, and missing keys should return None.
Input: ([],)
Expected Output: []
Explanation: Edge case: no operations.
Solution
def solution(operations):
MASK = (1 << 64) - 1
def fnv1a(text):
h = 1469598103934665603
for ch in text:
h ^= ord(ch)
h = (h * 1099511628211) & MASK
return h
def mix64(x):
x = (x + 0x9E3779B97F4A7C15) & MASK
x = ((x ^ (x >> 30)) * 0xBF58476D1CE4E5B9) & MASK
x = ((x ^ (x >> 27)) * 0x94D049BB133111EB) & MASK
x ^= x >> 31
return x & MASK
class Node:
__slots__ = ('ts', 'value', 'prio', 'left', 'right')
def __init__(self, ts, value, prio):
self.ts = ts
self.value = value
self.prio = prio
self.left = None
self.right = None
def rotate_right(y):
x = y.left
y.left = x.right
x.right = y
return x
def rotate_left(x):
y = x.right
x.right = y.left
y.left = x
return y
def insert(node, ts, value, prio):
if node is None:
return Node(ts, value, prio)
if ts == node.ts:
node.value = value
return node
if ts < node.ts:
node.left = insert(node.left, ts, value, prio)
if node.left.prio < node.prio:
node = rotate_right(node)
else:
node.right = insert(node.right, ts, value, prio)
if node.right.prio < node.prio:
node = rotate_left(node)
return node
def floor_get(node, ts):
ans = None
cur = node
while cur is not None:
if ts < cur.ts:
cur = cur.left
else:
ans = cur.value
cur = cur.right
return ans
roots = {}
seeds = {}
out = []
for op in operations:
if op[0] == 'set':
_, key, value, ts = op
if key not in seeds:
seeds[key] = fnv1a(key)
prio = mix64(ts ^ seeds[key])
roots[key] = insert(roots.get(key), ts, value, prio)
else:
_, key, ts = op
out.append(floor_get(roots.get(key), ts))
return out
Time complexity: Expected O(log v) per set and O(log v) per get for a key with v stored versions, using a treap per key.. Space complexity: O(total_versions) across all keys..