How to fix this iterator? - python

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

Related

How to remove output 'None' when iterator is called?

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__()

how to get a cycle without using a loop in python?

im teaching my self python and i came across this interesting question which says:
Implement a generator cycle such that if we assign
i = cycle()
then repeated calls to
next(i)
return the values
me
myself
i
me
myself
i
...
I can't use a for loop but only a generator or stream. I cant use libraries. I need to output it 20 times
what I've tried so far but i cant manage to get the cycle to work:
def cycle(i):
saved = []
for el in m:
yield el
saved.append(el)
while saved:
for el in saved:
yield element
This is probably what you want:
def cycle(n=0):
saved=['me','myself','i']
while True:
yield saved[n]
n = (n+1) % 3
i = cycle()
for _ in range(20):
print(next(i))
If you don't want loops at all, try this:
class cycle:
def __init__(self,lst,maxstep = 20):
self.lst = lst
self.n = 0
self.len = len(lst)
self.maxstep = maxstep
def __next__(self):
if self.n>=self.maxstep: # remove this line
raise StopIteration # and this line, if you want infinite stream
ret = self.lst[self.n % self.len]
self.n += 1
return ret
obj = cycle(['me','myself','i'])
If you want to be able to call list on it, that is make it iterable:
class cycle():
def __init__(self,lst,maxstep = 20):
self.lst = lst
self.n = 0
self.i = 0
self.len = len(lst)
self.maxstep = maxstep
def __iter__(self):
while self.i < self.maxstep:
yield self.lst[self.i % self.len]
self.i += 1
def __next__(self):
if self.n>=self.maxstep:
raise StopIteration
ret = self.lst[self.n % self.len]
self.n += 1
return ret
obj = cycle([1, 2, 3])
print(list(obj))
# [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2]
# or
# print(next(obj))
# 1

Stop __next__ from returning None [duplicate]

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))

Python iterator returning unwanted '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))

Making a python iterator go backwards?

Is there anyway to make a python list iterator to go backwards?
Basically i have this
class IterTest(object):
def __init__(self, data):
self.data = data
self.__iter = None
def all(self):
self.__iter = iter(self.data)
for each in self.__iter:
mtd = getattr(self, type(each).__name__)
mtd(each)
def str(self, item):
print item
next = self.__iter.next()
while isinstance(next, int):
print next
next = self.__iter.next()
def int(self, item):
print "Crap i skipped C"
if __name__ == '__main__':
test = IterTest(['a', 1, 2,3,'c', 17])
test.all()
Running this code results in the output:
a
1
2
3
Crap i skipped C
I know why it gives me the output, however is there a way i can step backwards in the str() method, by one step?
EDIT
Okay maybe to make this more clear. I don't want to do a full reverse, basically what i want to know if there is an easy way to do the equivalent of a bidirectional iterator in python?
No, in general you cannot make a Python iterator go backwards. However, if you only want to step back once, you can try something like this:
def str(self, item):
print item
prev, current = None, self.__iter.next()
while isinstance(current, int):
print current
prev, current = current, self.__iter.next()
You can then access the previous element any time in prev.
If you really need a bidirectional iterator, you can implement one yourself, but it's likely to introduce even more overhead than the solution above:
class bidirectional_iterator(object):
def __init__(self, collection):
self.collection = collection
self.index = 0
def next(self):
try:
result = self.collection[self.index]
self.index += 1
except IndexError:
raise StopIteration
return result
def prev(self):
self.index -= 1
if self.index < 0:
raise StopIteration
return self.collection[self.index]
def __iter__(self):
return self
Am I missing something or couldn't you use the technique described in the Iterator section in the Python tutorial?
>>> class reverse_iterator:
... def __init__(self, collection):
... self.data = collection
... self.index = len(self.data)
... def __iter__(self):
... return self
... def next(self):
... if self.index == 0:
... raise StopIteration
... self.index = self.index - 1
... return self.data[self.index]
...
>>> for each in reverse_iterator(['a', 1, 2, 3, 'c', 17]):
... print each
...
17
c
3
2
1
a
I know that this doesn't walk the iterator backwards, but I'm pretty sure that there is no way to do that in general. Instead, write an iterator that walks a discrete collection in reverse order.
Edit you can also use the reversed() function to get a reversed iterator for any collection so that you don't have to write your own:
>>> it = reversed(['a', 1, 2, 3, 'c', 17])
>>> type(it)
<type 'listreverseiterator'>
>>> for each in it:
... print each
...
17
c
3
2
1
a
An iterator is by definition an object with the next() method -- no mention of prev() whatsoever. Thus, you either have to cache your results so you can revisit them or reimplement your iterator so it returns results in the sequence you want them to be.
Based on your question, it sounds like you want something like this:
class buffered:
def __init__(self,it):
self.it = iter(it)
self.buf = []
def __iter__(self): return self
def __next__(self):
if self.buf:
return self.buf.pop()
return next(self.it)
def push(self,item): self.buf.append(item)
if __name__=="__main__":
b = buffered([0,1,2,3,4,5,6,7])
print(next(b)) # 0
print(next(b)) # 1
b.push(42)
print(next(b)) # 42
print(next(b)) # 2
You can enable an iterator to move backwards by following code.
class EnableBackwardIterator:
def __init__(self, iterator):
self.iterator = iterator
self.history = [None, ]
self.i = 0
def next(self):
self.i += 1
if self.i < len(self.history):
return self.history[self.i]
else:
elem = next(self.iterator)
self.history.append(elem)
return elem
def prev(self):
self.i -= 1
if self.i == 0:
raise StopIteration
else:
return self.history[self.i]
Usage:
>>> prev = lambda obj: obj.prev() # A syntactic sugar.
>>>
>>> a = EnableBackwardIterator(iter([1,2,3,4,5,6]))
>>>
>>> next(a)
1
>>> next(a)
2
>>> a.next() # The same as `next(a)`.
3
>>> prev(a)
2
>>> a.prev() # The same as `prev(a)`.
1
>>> next(a)
2
>>> next(a)
3
>>> next(a)
4
>>> next(a)
5
>>> next(a)
6
>>> prev(a)
5
>>> prev(a)
4
>>> next(a)
5
>>> next(a)
6
>>> next(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
You can wrap your iterator in an iterator helper to enable it to go backward. It will store the iterated values in a collection and reuse them when going backwards.
class MemoryIterator:
def __init__(self, iterator : Iterator):
self._iterator : Iterator = iterator
self._array = []
self._isComplete = False
self._pointer = 0
def __next__(self):
if self._isComplete or self._pointer < len(self._array):
if self._isComplete and self._pointer >= len(self._array):
raise StopIteration
value = self._array[self._pointer]
self._pointer = self._pointer + 1
return value
try:
value = next(self._iterator)
self._pointer = self._pointer + 1
self._array.append(value)
return value
except StopIteration:
self._isComplete = True
def prev(self):
if self._pointer - 2 < 0:
raise StopIteration
self._pointer = self._pointer - 1
return self._array[self._pointer - 1]
The usage can be similar to this one:
my_iter = iter(my_iterable_source)
memory_iterator = MemoryIterator(my_iter)
try:
if forward:
print(next(memory_iterator))
else:
print(memory_iterator.prev())
except StopIteration:
pass
I came here looking for a bi-directional iterator. Not sure if this is what the OP was looking for but it is one way to make a bi-directional iterator—by giving it an attribute to indicate which direction to go in next:
class BidirectionalCounter:
"""An iterator that can count in two directions (up
and down).
"""
def __init__(self, start):
self.forward = True
# Code to initialize the sequence
self.x = start
def __iter__(self):
return self
def __next__(self):
if self.forward:
return self.next()
else:
return self.prev()
def reverse(self):
self.forward = not self.forward
def next(self):
"""Compute and return next value in sequence.
"""
# Code to go forward
self.x += 1
return self.x
def prev(self):
"""Compute and return previous value in sequence.
"""
# Code to go backward
self.x -= 1
return self.x
Demo:
my_counter = BidirectionalCounter(10)
print(next(my_counter))
print(next(my_counter))
my_counter.reverse()
print(next(my_counter))
print(next(my_counter))
Output:
11
12
11
10
i think thi will help you to solve your problem
class TestIterator():
def __init__(self):`
self.data = ["MyData", "is", "here","done"]
self.index = -1
#self.index=len(self.data)-1
def __iter__(self):
return self
def next(self):
self.index += 1
if self.index >= len(self.data):
raise StopIteration
return self.data[self.index]
def __reversed__(self):
self.index = -1
if self.index >= len(self.data):
raise StopIteration
return self.data[self.index]
r = TestIterator()
itr=iter(r)
print (next(itr))
print (reversed(itr))
ls = [' a', 5, ' d', 7, 'bc',9, ' c', 17, '43', 55, 'ab',22, 'ac']
direct = -1
l = ls[::direct]
for el in l:
print el
Where direct is -1 for reverse or 1 for ordinary.
Python you can use a list and indexing to simulate an iterator:
a = [1,2,3]
current = 1
def get_next(a):
current = a[a.index(current)+1%len(a)]
return current
def get_last(a):
current = a[a.index(current)-1]
return current # a[-1] >>> 3 (negative safe)
if your list contains duplicates then you would have to track your index separately:
a =[1,2,3]
index = 0
def get_next(a):
index = index+1 % len(a)
current = a[index]
return current
def get_last(a):
index = index-1 % len(a)
current = a[index-1]
return current # a[-1] >>> 3 (negative safe)
An iterator that visits the elements of a list in reverse order:
class ReverseIterator:
def __init__(self,ls):
self.ls=ls
self.index=len(ls)-1
def __iter__(self):
return self
def __next__(self):
if self.index<0:
raise StopIteration
result = self.ls[self.index]
self.index -= 1
return result
I edited the python code from dilshad (thank you) and used the following Python 3 based code to step between list item's back and forth or let say bidirectional:
# bidirectional class
class bidirectional_iterator:
def __init__(self):
self.data = ["MyData", "is", "here", "done"]
self.index = -1
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index >= len(self.data):
raise StopIteration
return self.data[self.index]
def __reversed__(self):
self.index -= 1
if self.index == -1:
raise StopIteration
return self.data[self.index]
Example:
>>> r = bidirectional_iterator()
>>> itr=iter(r)
>>> print (next(itr))
MyData
>>> print (next(itr))
is
>>> print (next(itr))
here
>>> print (reversed(itr))
is
>>> print (reversed(itr))
MyData
>>> print (next(itr))
is
This is a common situation when we need to make an iterator go back one step. Because we should get the item and then check if we should break the loop. When breaking the loop, the last item may be requied in later usage.
Except of implementing an iteration class, here is a handy way make use of builtin itertools.chain :
from itertools import chain
>>> iterator = iter(range(10))
>>> for i in iterator:
... if i <= 5:
... print(i)
... else:
... iterator = chain([i], iterator) # push last value back
... break
...
0
1
2
3
4
5
>>> for i in iterator:
... print(i)
...
6
7
8
9
please see this function made by Morten Piibeleht. It yields a (previous, current, next) tuple for every element of an iterable.
https://gist.github.com/mortenpi/9604377

Categories