Simulate a concurrent transactional key-value store under linearizable global-lock semantics. The operations are already listed in the exact atomic order in which they take effect, so your job is to process them in that order. Each thread has its own nested transaction stack. A thread can see its own uncommitted changes, but other threads cannot see them until the outermost commit. If a thread has no active transaction, put and delete update the shared global store immediately. Reads inside a transaction first check that thread's own transaction layers from newest to oldest, then the latest committed global store. This model is not snapshot isolation: if another thread commits later, future reads can observe that new global value unless the current thread has shadowed the key in its own transaction.
Examples
Input: [('put', 1, 'x', 1), ('begin', 2), ('get', 2, 'x'), ('put', 2, 'x', 5), ('get', 2, 'x'), ('get', 1, 'x'), ('commit', 2), ('get', 1, 'x')]
Expected Output: [1, 5, 1, True, 5]
Explanation: Thread 2 sees its own uncommitted write, while thread 1 does not see it until thread 2 commits.
Input: [('begin', 1), ('put', 1, 'a', 10), ('begin', 1), ('delete', 1, 'a'), ('get', 1, 'a'), ('get', 2, 'a'), ('rollback', 1), ('get', 1, 'a'), ('commit', 1), ('get', 2, 'a')]
Expected Output: [None, None, True, 10, True, 10]
Explanation: The inner delete is visible only to thread 1 and is undone by rollback. After the outer commit, thread 2 can see the value.
Input: [('commit', 3), ('rollback', 3), ('put', 1, 'k', 7), ('delete', 2, 'k'), ('get', 1, 'k')]
Expected Output: [False, False, None]
Explanation: Edge case: thread 3 has no active transaction. Thread 2 deletes 'k' globally because it is not inside a transaction.
Input: [('begin', 1), ('put', 1, 'x', 2), ('begin', 1), ('put', 1, 'x', 3), ('commit', 1), ('get', 1, 'x'), ('get', 2, 'x'), ('rollback', 1), ('get', 1, 'x')]
Expected Output: [True, 3, None, True, None]
Explanation: The child commit merges into thread 1's parent transaction, but thread 2 still cannot see the value until the outermost commit.
Input: [('put', 1, 'v', 1), ('begin', 2), ('get', 2, 'v'), ('delete', 1, 'v'), ('get', 2, 'v')]
Expected Output: [1, None]
Explanation: This shows the model is not snapshot isolation: thread 2 begins before the delete, but a later read still sees the latest global committed state.
Solution
def solution(operations):
TOMBSTONE = object()
global_store = {}
stacks = {}
result = []
def read_value(tid, key):
layers = stacks.get(tid, [])
for layer in reversed(layers):
if key in layer:
return None if layer[key] is TOMBSTONE else layer[key]
return global_store.get(key, None)
for op in operations:
cmd = op[0]
if cmd == 'begin':
_, tid = op
stacks.setdefault(tid, []).append({})
elif cmd == 'put':
_, tid, key, value = op
layers = stacks.get(tid, [])
if layers:
layers[-1][key] = value
else:
global_store[key] = value
elif cmd == 'delete':
_, tid, key = op
layers = stacks.get(tid, [])
if layers:
layers[-1][key] = TOMBSTONE
else:
global_store.pop(key, None)
elif cmd == 'get':
_, tid, key = op
result.append(read_value(tid, key))
elif cmd == 'commit':
_, tid = op
layers = stacks.get(tid, [])
if not layers:
result.append(False)
else:
current = layers.pop()
if layers:
layers[-1].update(current)
else:
for key, value in current.items():
if value is TOMBSTONE:
global_store.pop(key, None)
else:
global_store[key] = value
if not layers:
stacks.pop(tid, None)
result.append(True)
elif cmd == 'rollback':
_, tid = op
layers = stacks.get(tid, [])
if not layers:
result.append(False)
else:
layers.pop()
if not layers:
stacks.pop(tid, None)
result.append(True)
return resultTime complexity: A get is O(d) for transaction depth d of that thread; a commit is O(k) for the number of keys in the committed layer; all other operations are O(1) average. Space complexity: O(u + p), where u is the number of committed keys in the global store and p is the total number of pending key updates across all active thread-local transaction layers.