Implementing Custom Iterators in Python
Iterators are a fundamental concept in Python, enabling efficient traversal of collections without loading the entire dataset into memory at once. This challenge asks you to implement your own iterator class, allowing you to control how a custom data structure is iterated over. Understanding iterators is crucial for writing performant and memory-efficient code, especially when dealing with large datasets.
Problem Description
You are tasked with creating a class called MyIterator that iterates over a sequence of numbers generated by a given function. The MyIterator class should implement the iterator protocol, meaning it must have an __iter__() method that returns the iterator object itself and a __next__() method that returns the next value in the sequence. The sequence will be generated until a specified condition is met.
Key Requirements:
__iter__()method: This method should return theMyIteratorobject itself.__next__()method: This method should:- Generate the next value in the sequence using the provided function.
- Return the generated value.
- Raise a
StopIterationexception when the sequence is exhausted (i.e., the condition is met).
- Initialization: The
MyIteratorclass should be initialized with a functiongenerator_functionand a stopping conditionstop_condition. Thegenerator_functiontakes a single integer argument (the current number) and returns the next number in the sequence. Thestop_conditionis a function that takes a single integer argument (the current number) and returnsTrueif the iteration should stop, andFalseotherwise.
Expected Behavior:
When an instance of MyIterator is used in a for loop or with the next() function, it should generate and return values from the sequence until the stop_condition is met. The StopIteration exception should be raised correctly to signal the end of the iteration.
Edge Cases to Consider:
- The
generator_functionmight return a value that immediately satisfies thestop_condition. - The sequence might be empty from the start (the initial value already satisfies the
stop_condition).
Examples
Example 1:
def generator(x):
return x + 1
def stop(x):
return x >= 5
iterator = MyIterator(generator, stop)
for num in iterator:
print(num)
Output:
1
2
3
4
Explanation: The generator function increments the input by 1, and the stop function returns True if the input is greater than or equal to 5. The iterator starts at 0, generates 1, 2, 3, and 4, and stops when the next value would be 5.
Example 2:
def generator(x):
return x * 2
def stop(x):
return x > 10
iterator = MyIterator(generator, stop)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
Output:
0
0
0
0
0
0
0
Explanation: The generator doubles the input, and the stop condition checks if the input is greater than 10. The iterator starts at 0, and the loop continues until the next value would be greater than 10.
Example 3: (Edge Case)
def generator(x):
return x + 1
def stop(x):
return x == 1
iterator = MyIterator(generator, stop)
for num in iterator:
print(num)
Output:
0
Explanation: The generator increments the input by 1, and the stop condition checks if the input is equal to 1. The iterator starts at 0, generates 1, and stops because the stop condition is met.
Constraints
- The
generator_functionandstop_conditionwill always be valid functions. - The initial value for the sequence is always 0.
- The
generator_functionwill always return an integer. - The
stop_conditionwill always return a boolean. - The code should be efficient and avoid unnecessary computations.
Notes
- Remember to implement both the
__iter__()and__next__()methods correctly. - The
StopIterationexception is crucial for signaling the end of the iteration. - Consider how the
generator_functionandstop_conditioninteract to determine the sequence. - Think about how to handle the initial value and the first iteration.
- The
MyIteratorclass should be reusable with differentgenerator_functionandstop_conditionarguments.
class MyIterator:
def __init__(self, generator_function, stop_condition):
self.generator_function = generator_function
self.stop_condition = stop_condition
self.current_value = 0
def __iter__(self):
return self
def __next__(self):
if self.stop_condition(self.current_value):
raise StopIteration
else:
value = self.generator_function(self.current_value)
self.current_value = value
return self.current_value