Where the Numbers Settle
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- easy
- Seniority
- L5
- Asked in
- Meta
Problem
Even a mean that lands on a whole number reports as a float, and each sensor's mean covers only the readings recorded for it so far. We need a StreamAverager for our sensor dashboard, where add(key, value) records one reading and get_averages() reports every sensor's current mean. run_stream_averager(operations) replays a list of ['add', key, value] and ['get_averages'] entries on one averager and returns one result per operation.
The answer moves with the data.
Example 1
Input:
operations = [["add","sensor_a",10],["add","sensor_b",20],["add","sensor_a",30],["get_averages"]]
Output:
[null,null,null,{"sensor_a":20,"sensor_b":20}]Example 2
Input:
operations = [["get_averages"]]
Output:
[{}]Worked solution and explanation
What this really is
Under the sensor-reading costume, this is per-key aggregation state that has to survive a whole stream of operations. By the time get_averages() runs, the readings it needs have already streamed past, so the object has to carry them forward: nothing about the current call can reconstruct them. And a mean is two facts, not one, a total and a count kept together per key, so a single value per sensor can never answer it. The trap that actually bites is the division. Reach for // and 1.5 collapses to 1, so a clean-looking mean silently drops its fraction; pool every sensor into one running figure and you report a blended number instead of one average per key. Either way get_averages() hands back numbers that look fine and are wrong. (There is a seniority tell hiding here too: keep a running sum and count and each read is one division, but buffer the raw values in a list per key and every get_averages() re-sums the whole history.)
Break it down
Step 1: Carry a sum and a count per key
Two dicts are enough: one maps each key to the total of its readings, the other to how many readings it has seen. You never store the raw values, so memory grows with the number of distinct keys, not with the length of the stream.
Step 2: Start empty without a constructor
Give the class sums = None and counts = None as class-level defaults, then replace them with real dicts on the instance the first time add runs. A fresh averager reads the None defaults so it starts empty, and a get_averages() before any add can short-circuit straight to {}.
Step 3: Fold each reading in
add folds the new reading in: self.sums[key] = self.sums.get(key, 0) + value, and the same one-line pattern bumps the count. dict.get with a default of 0 handles the first sighting of a key without a separate branch.
Step 4: Divide only when asked
get_averages walks the keys and returns self.sums[key] / self.counts[key]. Python's / is float division, so 40 / 2 is 20.0 and the fraction always survives. With no keys the loop never runs and you return {}.
Step 5: Replay the stream
The driver run_stream_averager(operations) builds one averager and walks the ops in order, appending the return of add (which is None) for an ['add', key, value] and the dict from get_averages() for a ['get_averages'], producing exactly one result per operation.
The solution
class StreamAverager:
sums = None
counts = None
def add(self, key, value):
if self.sums is None:
self.sums = {}
self.counts = {}
self.sums[key] = self.sums.get(key, 0) + value
self.counts[key] = self.counts.get(key, 0) + 1
def get_averages(self):
if self.sums is None:
return {}
averages = {}
for key in self.sums:
averages[key] = self.sums[key] / self.counts[key]
return averages
def run_stream_averager(operations):
averager = StreamAverager()
results = []
for op in operations:
if op[0] == "add":
results.append(averager.add(op[1], op[2]))
else:
results.append(averager.get_averages())
return resultsCommon follow-up questions
- How would you add a `remove(key, value)` method? (Tests subtracting from the sum and decrementing the count, and handling a key whose count reaches zero.)
- What if you needed a windowed average over the last N values per key? (Tests keeping a bounded `deque` per key alongside the running sum.)
- How would you make this thread-safe for concurrent writers? (Tests knowledge of locks or concurrent data structures for multi-threaded access.)