The Counting Machine
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
Problem
Our batch scorer builds every position-weighted reading up front, where each reading is multiplied by its place in the batch, and we want those readings handed out one at a time instead. Implement CountUp over values as an iterator that produces the next weighted reading on each request and stops cleanly once the batch is exhausted, then have count_up(values) drive one to the end and return everything it produced, in order.
It knows where it stopped last time.
Example 1
Input:
values = [2,3,4]
Output:
[2,6,12]
Example 2
Input:
values = [10,0,-2,4]
Output:
[10,0,-6,16]
Worked solution and explanation
This is the iterator protocol with the safety net taken away. A generator or a comprehension ends on its own; a hand-written __next__ does not, and that is the whole test. Anyone can multiply a reading by its position. What separates candidates is the guard that raises StopIteration the moment the cursor passes the end, plus a weight that starts at one. Skip the guard and list(CountUp(values)) never gets a clean stop: it reads past the last element and dies with IndexError, so even the empty batch blows up. Use the raw 0-based cursor as the weight and the first reading is zeroed: [2, 3, 4] comes back as [0, 3, 8].
Building it
Step 1: Keep the batch and a cursor
__init__ stores values and sets self.index to 0. The cursor is the only state the iterator owns; it never copies or mutates the batch it was handed.
Step 2: Return self from __iter__
That makes CountUp its own iterator, so list() and a for loop can consume it directly without a separate iterator object.
Step 3: Guard before you read
The first line of __next__ compares self.index to len(self.values) and raises StopIteration once the cursor has run off the end. It has to come before the read: checked afterward, the empty batch already fails on self.values[0].
Step 4: Weight by self.index + 1, then advance
Compute the 1-based position, multiply, move the cursor, return. Advancing after the read keeps the cursor pointing at the next unread reading, so the guard on the next call sees the truth.
Step 5: Drive it from count_up
count_up(values) is one line: list(CountUp(values)). The class is what is being graded; the driver just gives the harness a function to call.
class CountUp:
def __init__(self, values):
self.values = values
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.values):
raise StopIteration
position = self.index + 1
weighted = self.values[self.index] * position
self.index += 1
return weighted
def count_up(values):
return list(CountUp(values))- Generator function
- A
yieldinside a loop overenumerate(values, start=1)stops when the loop stops; Python raisesStopIterationon your behalf. - Hand-written iterator class
- Nothing ends on its own. The class owns the cursor and must raise
StopIterationitself, which is exactly the capability this problem checks.
Common follow-up questions
- How would you make `CountUp` reusable, so iterating it twice gives the same readings both times? (Tests separating the iterable from the iterator: `__iter__` returning a fresh cursor instead of `self`.)
- What is the difference between an iterator and an iterable? (Iterables only need `__iter__`; iterators carry both `__iter__` and `__next__` and hold position.)
- How would you write the same thing as a generator, and what would you give up? (Tests `yield` as the shorter form and whether the candidate sees the implicit `StopIteration` it hides.)