Well, I wrote an iterator Divisors(n) that gets a natural number n as its argument and generates divisors of n (from the smallest to the largest). Here's code:
class Divisors:
def __init__(self, n):
self.n = n
self.i = 0
def __iter__(self):
return self
def __next__(self):
self.i += 1
if self.i == self.n + 1:
raise StopIteration
if self.n % self.i == 0:
return self.i
But, when I run it, I get:
>>> print([x for x in Divisors(12)])
[1, 2, 3, 4, None, 6, None, None, None, None, None, 12]
Question is: How to remove these None's from list to obtain:
[1, 2, 3, 4, 6, 12]
Yes, I know this can be done like this:
def divisors(n):
i = 1
while i <= n:
if n % i == 0:
yield i
i += 1
but I am interested in way I mentioned above.
You could keep incrementing self.i till it evenly divides the self.n:
class Divisors:
def __init__(self, n):
self.n = n
self.i = 0
def __iter__(self):
return self
def __next__(self):
self.i += 1
if self.i == self.n + 1:
raise StopIteration
# Keep incrementing
while self.n % self.i != 0:
self.i += 1
# Now it is guaranteed to divide
return self.i
I suspect this might work:
def __next__(self):
self.i += 1
if self.i == self.n + 1:
raise StopIteration
if self.n % self.i == 0:
return self.i
else
return self.__next__()
Some perfer the following style which should have a similar effect.
def __next__(self):
self.i += 1
if self.i == self.n + 1:
raise StopIteration
if self.n % self.i == 0:
return self.i
return self.__next__()
Related
I'm trying to write an iterator Digits(n) that generates digits of a natural number n in reverse order. Here's my code so far:
class Digits:
def __init__(self, n):
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.n < 10:
return self.n
return self.n // 10
Output should be:
>>> print([x for x in Digits(1337)])
[7, 3, 3, 1]
I feel I should implement somewhere self.n % 10 (I hope) , but I don't know where. Any suggestions?
Does this help?
class Digits:
def __init__(self, n):
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.n == 0:
raise StopIteration
digit = self.n % 10
self.n = self.n // 10
return digit
print([x for x in Digits(1337)])
Output:
[7, 3, 3, 1]
Just for completeness: it is rarely preferred to self-implement an iterator with __next__ instead of a generator with yield:
def digits(n):
while n > 10:
yield n % 10
n //= 10
yield n
This also relieves you from having to raise StopIteration manually.
You never actually modify the value, so don't iterate it. Try something like
def __next__(self):
if self.n == 0:
raise StopIteration()
ret_val = self.n % 10
self.n = //= 10
return ret_val
Why is my iterator returning extra 'None' in the output. For the parameters/example below, I am getting [None,4,None] instead of the desired [4] Can anyone explain why I am getting the extra None and how I can fix it? The print out 'returning' only appears once so I am assuming only one item should be appended to the returning calling function.
code:
class Prizes(object):
def __init__(self,purchase,n,d):
self.purchase = purchase
self.length = len(purchase)
self.i = n-1
self.n = n
self.d = d
def __iter__(self):
return self
def __next__(self):
if self.i < self.length:
old = self.i
self.i += self.n
if (self.purchase[old])%(self.d) == 0:
print("returning")
return old+1
else:
raise StopIteration
def superPrize(purchases, n, d):
return list(Prizes(purchases, n, d))
purchases = [12, 43, 13, 465, 1, 13]
n = 2
d = 3
print(superPrize(purchases, n, d))
Output:
returning
[None, 4, None]
As people in the comments have pointed out, your line if (self.purchase[old])%(self.d) == 0: leads to the function returning without any return value. If there is no return value supplied None is implied. You need some way of continuing through your list to the next available value that passes this test before returning or raising StopIteration. One easy way of doing this is simply to add an extra else clause to call self.__next__() again if the test fails.
def __next__(self):
if self.i < self.length:
old = self.i
self.i += self.n
if (self.purchase[old])%(self.d) == 0:
print("returning")
return old+1
else:
return self.__next__()
else:
raise StopIteration
Functions return None if you don't have an explicit return statement. That's what happens in __next__ when if (self.purchase[old])%(self.d) == 0: isn't true. You want to stay in your __next__ until it has a value to return.
class Prizes(object):
def __init__(self,purchase,n,d):
self.purchase = purchase
self.length = len(purchase)
self.i = n-1
self.n = n
self.d = d
def __iter__(self):
return self
def __next__(self):
while self.i < self.length:
old = self.i
self.i += self.n
if (self.purchase[old])%(self.d) == 0:
return old+1
raise StopIteration
def superPrize(purchases, n, d):
return list(Prizes(purchases, n, d))
purchases = [12, 43, 13, 465, 1, 13]
n = 2
d = 3
print(superPrize(purchases, n, d))
Here is my code:
class Prizes(object):
def __init__(self, purchases, n, d):
self.p = purchases
self.n = n
self.d = d
self.x = 1
def __iter__(self):
return self
def __next__(self):
print(self.x)
if self.x % self.n == 0 and self.p[self.x - 1] % self.d == 0:
self.x = self.x + 1
return self.x - 1
elif self.x > len(self.p):
raise StopIteration
self.x = self.x + 1
def superPrize(purchases, n, d):
return list(Prizes(purchases, n, d))
An example of usage:
superPrize([12, 43, 13, 465, 1, 13], 2, 3)
The output should be:
[4]
But actual output is:
[None, None, None, 4, None, None].
Why does it happen?
Your problem is your implementation of __next__. When Python calls __next__, it will always expect a return value. However, in your case, it looks like you may not always have a return value each call. Thus, Python uses the default return value of a function - None:
You need some way to keep program control inside of __next__ until you have an actually return value. This can be done using a while-loop:
def __next__(self):
while True:
if self.x % self.n == 0 and self.p[self.x - 1] % self.d == 0:
self.x = self.x + 1
return self.x - 1
elif self.x > len(self.p):
raise StopIteration
self.x = self.x + 1
Wrap it with while so that your method doesn't return a value until you found one:
def __next__(self):
while True:
if self.x % self.n == 0 and self.p[self.x - 1] % self.d == 0:
self.x = self.x + 1
return self.x - 1
elif self.x > len(self.p):
raise StopIteration
self.x = self.x + 1
Things working with iterators call __next__ expecting it to return a value, but the method returns a value only under a condition, otherwise it reaches the end of the method and it returns None.
Why is my iterator returning extra 'None' in the output. For the parameters/example below, I am getting [None,4,None] instead of the desired [4] Can anyone explain why I am getting the extra None and how I can fix it? The print out 'returning' only appears once so I am assuming only one item should be appended to the returning calling function.
code:
class Prizes(object):
def __init__(self,purchase,n,d):
self.purchase = purchase
self.length = len(purchase)
self.i = n-1
self.n = n
self.d = d
def __iter__(self):
return self
def __next__(self):
if self.i < self.length:
old = self.i
self.i += self.n
if (self.purchase[old])%(self.d) == 0:
print("returning")
return old+1
else:
raise StopIteration
def superPrize(purchases, n, d):
return list(Prizes(purchases, n, d))
purchases = [12, 43, 13, 465, 1, 13]
n = 2
d = 3
print(superPrize(purchases, n, d))
Output:
returning
[None, 4, None]
As people in the comments have pointed out, your line if (self.purchase[old])%(self.d) == 0: leads to the function returning without any return value. If there is no return value supplied None is implied. You need some way of continuing through your list to the next available value that passes this test before returning or raising StopIteration. One easy way of doing this is simply to add an extra else clause to call self.__next__() again if the test fails.
def __next__(self):
if self.i < self.length:
old = self.i
self.i += self.n
if (self.purchase[old])%(self.d) == 0:
print("returning")
return old+1
else:
return self.__next__()
else:
raise StopIteration
Functions return None if you don't have an explicit return statement. That's what happens in __next__ when if (self.purchase[old])%(self.d) == 0: isn't true. You want to stay in your __next__ until it has a value to return.
class Prizes(object):
def __init__(self,purchase,n,d):
self.purchase = purchase
self.length = len(purchase)
self.i = n-1
self.n = n
self.d = d
def __iter__(self):
return self
def __next__(self):
while self.i < self.length:
old = self.i
self.i += self.n
if (self.purchase[old])%(self.d) == 0:
return old+1
raise StopIteration
def superPrize(purchases, n, d):
return list(Prizes(purchases, n, d))
purchases = [12, 43, 13, 465, 1, 13]
n = 2
d = 3
print(superPrize(purchases, n, d))
I am trying to write iterator class that will allow me to specify the length of steps the iterator makes. But I am stuck with that problem.
My code:
class Reverse:
def __init__(self, data, step):
self.data = data
self.index = len(data)
self.step = step
def __iter__(self):
return self
def __next__(self, step):
if self.index <= 0:
raise StopIteration
self.index = self.index - self.step
return self.data[self.index]
rev = Reverse('Drapsicle', 2)
this shows me letter 'l' always
rev.__next__(2)
but loop gives me: =TypeError: next() missing 1 required positional argument: 'step'`:
for char in rev:
print(char)
Your __next__ method should not take any arguments (other than self). You are not even using the step argument, you are (correctly) using self.step. Just remove the argument:
def __next__(self):
if self.index <= 0:
raise StopIteration
self.index = self.index - self.step
return self.data[self.index]
Next, you have an error; you want to test if the index drops below 0 after subtracting, otherwise you generate negative indices. You could test the index together with the step subtracted:
def __next__(self):
next_index = self.index - self.step
if next_index < 0:
raise StopIteration
self.index = next_index
return self.data[self.index]
Demo:
>>> class Reverse:
... def __init__(self, data, step):
... self.data = data
... self.index = len(data)
... self.step = step
... def __iter__(self):
... return self
... def __next__(self):
... next_index = self.index - self.step
... if next_index < 0:
... raise StopIteration
... self.index = next_index
... return self.data[self.index]
...
>>> rev = Reverse('Drapsicle', 2)
>>> for char in rev:
... print(char)
...
l
i
p
r
I realize this is not a direct answer to the question, but in case you just want to get the job done, and think you need to implement it all yourself; you don't.
from itertools import islice
def reverse_step(iterable, step):
# To behave like your code; starts with step-th item
start = step - 1
for item in islice(reversed(iterable), start, None, step)):
yield item
To implement this:
>>> 'Drapsicle'[::-2]
'ecsaD'
as your own iterator:
class Reverse:
def __init__(self, data, step):
self.data = data
self.index = len(data) - 1
self.step = step
def __iter__(self):
return self
def __next__(self):
if self.index < 0:
raise StopIteration
value = self.data[self.index]
self.index -= self.step
return value
Example:
>>> list(Reverse('Drapsicle', 2))
['e', 'c', 's', 'a', 'D']
Note:
self.index starts with len - 1
__next__() does not accept any argument except self
first you get value then you decrement the index
A more flexible design would separate the reversing (e.g., delegate it to the reversed() builtin) and using step != 1 (accept an arbitrary reversible iterable and/or use/implement extended slicing) e.g., based on #Cyphase's suggestion:
>>> list(islice(reversed('Drapsicle'), None, None, 2))
['e', 'c', 's', 'a', 'D']