Implement sliding-window timestamped average
Company: Bloomberg
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of time-based sliding-window data structures, streaming aggregation, and the handling of timestamped records with efficient incremental updates and evictions.
Constraints
- 0 <= len(operations) <= 2 * 10^5
- 0 <= window_size <= 10^9
- Operation times are non-decreasing across all operations
- -10^9 <= score <= 10^9
- All timestamps and current_time values are integers
Examples
Input: (10, [('insert', 1, 4), ('insert', 5, 8), ('get', 5), ('insert', 12, 6), ('get', 12)])
Expected Output: [6.0, 7.0]
Explanation: At time 5, both records 4 and 8 are inside the window, so the average is 6.0. Before inserting at time 12, the record at time 1 is evicted because 1 < 12 - 10. The remaining scores are 8 and 6, so the average at time 12 is 7.0.
Input: (3, [('insert', 1, 5), ('insert', 3, 7), ('get', 3), ('insert', 6, 9), ('get', 6)])
Expected Output: [6.0, 8.0]
Explanation: At time 3, both scores 5 and 7 are valid, so the average is 6.0. Before inserting at time 6, the score at time 1 is evicted because 1 < 6 - 3. Then scores 7 and 9 remain, giving an average of 8.0.
Input: (5, [('insert', 0, 10), ('insert', 5, 20), ('get', 5), ('get', 6)])
Expected Output: [15.0, 20.0]
Explanation: This checks the boundary condition. At time 5, timestamps in [0, 5] are valid, so both 10 and 20 are included. At time 6, the valid range is [1, 6], so the record at timestamp 0 is evicted and only 20 remains.
Input: (4, [('get', 0), ('insert', 2, 10), ('get', 2), ('get', 7)])
Expected Output: [0, 10.0, 0]
Explanation: The first query has no records, so it returns 0. At time 2, the only record is 10. By time 7, that record is too old because 2 < 7 - 4, so the final query again returns 0.
Input: (2, [('insert', 1, -2), ('insert', 1, 8), ('insert', 3, 6), ('get', 3), ('get', 4)])
Expected Output: [4.0, 6.0]
Explanation: When querying at time 3, the valid range is [1, 3], so all three scores -2, 8, and 6 are included, giving 12 / 3 = 4.0. At time 4, the valid range is [2, 4], so both timestamp-1 records are evicted and only 6 remains.
Solution
def solution(window_size, operations):
from collections import deque
window = deque()
running_sum = 0
result = []
def evict(cutoff):
nonlocal running_sum
while window and window[0][0] < cutoff:
_, score = window.popleft()
running_sum -= score
for op in operations:
if op[0] == 'insert':
_, timestamp, score = op
evict(timestamp - window_size)
window.append((timestamp, score))
running_sum += score
elif op[0] == 'get':
_, current_time = op
evict(current_time - window_size)
if window:
result.append(running_sum / len(window))
else:
result.append(0)
else:
raise ValueError('Unknown operation: {}'.format(op[0]))
return resultTime complexity: O(n), where n is the number of operations. Space complexity: O(k), where k is the number of records currently in the window at its largest size (O(n) worst-case).
Hints
- Because operation times never go backward, once a record expires it will never become valid again.
- Use a FIFO structure to store active records and keep a running sum so each get query can be answered in O(1) amortized time after evictions.