2

This is my iterator for Fib squence:

class fibo:

  def __init__(self, n=0):
     self.n = n
     self.x1 = 0
     self.x2 = 1

  def next(self):
      if self.n == 0:
          raise StopIteration
      else:
          self.n = self.n -1
          tmp = self.x1
          self.x1, self.x2 = self.x2, self.x1 + self.x2
          return tmp

  def __iter__(self):
      return self

The result is

>>> [i for i in fibo(15)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

How do I modify the code such that instead of iterating over the first n Fibonacci numbers, successive calls iterate over the next n numbers, e.g., ????

>>> f=fibo(5)

>>> [i for i in f]
[0, 1, 1, 2, 3]

>>> [i for i in f]
[5, 8, 13, 21, 34]
>>> [i for i in f]
[55, 89, 144, 233, 377]
>>> [i for i in f]
[610, 987, 1597, 2584, 4181]
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
Shadumu
  • 121
  • 2

4 Answers4

2

To get the exact syntax you want is either outright illegal, or highly discouraged in Python, since the iterator protocol requires that an iterator keeps yielding StopIteration after it has done so once. From PEP 234, which introduces iterators:

  - Once a particular iterator object has raised StopIteration, will
  it also raise StopIteration on all subsequent next() calls?
  Some say that it would be useful to require this, others say
  that it is useful to leave this open to individual iterators.
  Note that this may require an additional state bit for some
  iterator implementations (e.g. function-wrapping iterators).

  Resolution: once StopIteration is raised, calling it.next()
  continues to raise StopIteration.

EDIT Thinking some more, I think what you want is actually 'legal' Python, since the list comprehension [i for i in fibo] implicitly calls the __iter__ method on fibo, which thus more or less asks for a new iterator (even if this is implemented by the same object). So the correct way to implement the behavior you want would be:

class Fibo:
    def __init__(self, n):
        self.n = n
        self.cnt = n
        self.x1 = 0
        self.x2 = 1

    def next(self):
        if self.cnt > 0:
            self.cnt -= 1
            tmp = self.x1
            self.x1, self.x2 = self.x2, self.x1 + self.x2
            return tmp
        else: 
            raise StopIteration # keeps raising

    def __iter__(self):
        self.cnt = self.n # reset the counter here
        return self

Which works like this:

In [32]: f = Fibo(3)
In [33]: it = iter(f)
In [34]: it.next()
Out[34]: 0
In [35]: it.next()
Out[35]: 1
In [36]: it.next()
Out[36]: 1
In [37]: it.next()
-> StopIteration
In [38]: it.next() # repeated call keeps raising
-> StopIteration
In [39]: it = iter(f) # explicitly reset iterator
In [40]: it.next()
Out[40]: 2
In [41]: it.next()
Out[41]: 3
In [42]: it.next()
Out[42]: 5
In [43]: it.next()
-> StopIteration

This shows the required behavior: it keeps raising StopIteration when it is exhausted, you need to explicitly call iter to reset it. This is slightly different from C.B.'s version, which simply wraps around, without requiring a reset:

In [45]: f = fibo(3)
In [46]: it = iter(f)
In [47]: it.next()
Out[47]: 0
In [48]: it.next()
Out[48]: 1
In [49]: it.next()
Out[49]: 1
In [50]: it.next()
-> StopIteration
In [51]: it.next() # no need to reset!
Out[51]: 2
In [52]: it.next()
Out[52]: 3
In [53]: it.next()
Out[53]: 5
In [54]: it.next()
-> StopIteration
Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
2

This works in 2.7

class fibo:

    def __init__(self, n=0):
        self.n = n
        self.x1 = 0
        self.x2 = 1
        self.current = 0 #We won't decrease n, but instead increase current until it
                             #Equals n

    def next(self):
        if self.n == self.current:
            self.current = 0
            raise StopIteration
        else:
            self.current +=1
            tmp = self.x1
            self.x1, self.x2 = self.x2, self.x1 + self.x2
            return tmp

    def __iter__(self):
        return self

f = fibo(5)
print [i for i in f]
print [i for i in f]

Output

[0, 1, 1, 2, 3]
[5, 8, 13, 21, 34]
C.B.
  • 8,096
  • 5
  • 20
  • 34
1

To get something like what you want, but with different syntax, you could use the answers to What is the most “pythonic” way to iterate over a list in chunks?. In particular, this code snippet from one of the answers:

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return izip_longest(*args, fillvalue=fillvalue)

This function will slice an iterable into "chunks" of equal size. You can use this with a non-terminating version of your existing fibo class:

class fibo:
  def __init__(self):
      self.x1 = 0
      self.x2 = 1

  def next(self):
      tmp = self.x1
      self.x1, self.x2 = self.x2, self.x1 + self.x2
      return tmp

  def __iter__(self):
      return self

Combined, you can now do something like this:

>>> f = grouper(fibo(), 5)
>>> f.next()
(0, 1, 1, 2, 3)
>>> f.next()
(5, 8, 13, 21, 34)
>>> f.next()
(55, 89, 144, 233, 377)

Note that this modified fibo never stops iterating, so you'll want to be careful giving it to anything that tries to eagerly iterate to the end.

As an aside, here's an alternate implementation of the iterator using a generator instead of a class:

def fibg():
    x1 = 0
    x2 = 1
    while True:
        yield x1
        x1, x2 = x2, x1 + x2
Community
  • 1
  • 1
Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
0

I think the best match for the behaviour you are after is an iterator that accepts an additional count of items to yield:

class fibo:
    def __init__(self, n=0):
        self.x1 = 0
        self.x2 = 1
        self.n = n
    def __iter__(self):
        return self
    def next(self, more=0):
        if more:
            self.n += more
            return
        while self.n:
           self.n -= 1
           current, self.x1, self.x2 = self.x1, self.x2, self.x2 + self.x1
           return current
        raise StopIteration

While this iterator is technically broken, it avoids the most serious issue of broken iterators -- having new items show up without user intervention; in other words: once StopIteration is raised it will keep raising until you specifically tell it you want more items:

>>> f = fibo(4)
>>> for n in f:
...   print n
... 
0
1
1
2
>>> f.next(7)
>>> for n in f:
...   print n
... 
3
5
8
13
21
34
55
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237