0

Good afternoon, I've been stumped with this for a while now. I have a doubly linked list with sentinel nodes created, and I'm trying to create a custom iterator to loop through said doubly linked list. My implementation of this is as follows:

    def __iter__(self): 
        self.cur = self.__header
        return self

    def __next__(self):
        for i in range(len(self)):
            if self.cur.next != self.__trailer:
                print('test')
                print(i)
                self.cur = self.cur.next
                return self.get_element_at(i)
            raise StopIteration

The self.__header variable is referring to the head sentinel node upon which the rest of the list is linked. The self.__trailer variable is referring to the tail sentinel node. What I am having an issue with is that say I have a doubly linked list called my_list, with the following values 'Doubly', 'Linked', 'List'. I then use the following code:

for node in my_list:
    print(node)

to invoke the custom iterator. The expected output of this code should be

test
0
Doubly
test
1
Linked
test
2
List

However the actual output is as follows:

test 
0
Doubly
test 
0 
Doubly
test
0
Doubly

Does anyone know what I am doing wrong? Thanks.

  • 1
    Looks like the `next` attribute of your list nodes is not being set correctly. You haven't shown us enough code to actually reproduce the problem, so all we can do is guess. – jasonharper Oct 21 '22 at 20:19
  • wait wait wait. `def __iter__(self): self.cur = self.__header`, are you defining `__iter__` and `__next__` on your *list class*? Don't do that. Iterators should be *a separate type*. That is the whole point! – juanpa.arrivillaga Oct 21 '22 at 20:20
  • In *any case* you really need to give us a [mcve] – juanpa.arrivillaga Oct 21 '22 at 20:21
  • Not directly related but [read this answer I wrote to another question](https://stackoverflow.com/questions/45685538/whats-the-advantage-of-using-yield-in-iter/45685692#45685692) about how containers and their iterators should be implemented, and why using generator functions makes this easy. But a container (e.g. a doubly linked list) *should not be it's own iterator* – juanpa.arrivillaga Oct 21 '22 at 20:25

1 Answers1

0

The loop in your __next__ is ostensibly being used to determine the index of the list node, but this doesn't work because it's starting at the current node, not the beginning of the list. The current node is of course always the 0th node relative to itself, but the node you're returning is the 0th node relative to the start of the list.

You shouldn't use a loop for this at all, since what you're trying to do is retrieve the next node, and you already have this as self.cur. I can't actually test this since you haven't provided a minimally reproducible example, but I believe this will get you the result you're looking for:

    def __next__(self):
        self.cur = self.cur.next
        if self.cur == self.__trailer:
            raise StopIteration
        return self.cur

Another option would be to make your __iter__ function return a new iterator (e.g. by making it a generator function) rather than making your class itself implement the iterator protocol:

    def __iter__(self): 
        cur = self.__header.next
        while cur != self.__trailer:
            yield cur
            cur = cur.next

Note that this approach is not only simpler (since you don't need to implement two different methods), it allows you to have multiple iterators over the same list, since the iterator state isn't an attribute of the list itself.

Samwise
  • 68,105
  • 3
  • 30
  • 44