Implemeting custom iterable object in python

145 views Asked by At

This is my implementation of a custom singly linked list in Python.

 class SList:
        def __init__(self):
            self.root = None
            self.size = 0

        def insert(self, item):
            if not item:
                raise ValueError('Cannot add None item to a list')
            self.size += 1
            if self.root is None:
                self.root = Node(item)
            else:
                p = Node(item)
                p.next = self.root
                self.root = p

        """Remove the element at the specific index"""
        def remove(self, index):
            if index < 0 or index >= self.size:
                raise ValueError('Index cannot be negative or greater than the size of the list')

            current = self.root
            if index == 0:
                self.root = self.root.next
            else:
                for _ in range(index -1):
                    current = current.next
                p = current.next.next
                if p is not None:
                    current.next = p
                else:
                    current.next = None

            self.size -= 1

        def __len__(self):
            return self.size

        def __repr__(self):
            res = '[ '
            current = self.root
            while current is not None:
                res += str(current.data)
                res += ' '
                current = current.next
            res += ']'
            return res

        def __iter__(self):
            return self

        def next(self):
            ........

This is the Node object

class Node:
    def __init__(self, data):
        try:
            if not data:
                raise ValueError
            self.data = data
            self.next = None
        except ValueError:
            raise ValueError('Node cannot be instantiated without an item')

I'm at a little loss at implementing the iter method. I see there are multiple ways to implement it and yield seems to be the common way forward. Would appreciate some help in implmenting it with yield

2

There are 2 answers

0
PM 2Ring On BEST ANSWER

You can make your class iterable by making its __iter__ method a generator.

Here's some code that runs correctly on Python 2 or Python 3.

from __future__ import print_function

class Node(object):
    def __init__(self, data):
        if data is None:
            raise ValueError('Node cannot be instantiated without an item')
        self.data = data
        self.nextnode = None

    def __repr__(self):
        return 'Node({})'.format(self.data)

class SList(object):
    def __init__(self):
        self.root = None
        self.size = 0

    def insert(self, item):
        if item is None:
            raise ValueError('Cannot add None item to a list')
        self.size += 1
        if self.root is None:
            self.root = Node(item)
        else:
            p = Node(item)
            p.nextnode = self.root
            self.root = p

    def remove(self, index):
        """ Remove the element at the specific index """
        if index < 0 or index >= self.size:
            raise ValueError('Index cannot be negative or greater than the size of the list')

        current = self.root
        if index == 0:
            self.root = self.root.nextnode
        else:
            for _ in range(index - 1):
                current = current.nextnode
            current.nextnode = current.nextnode.nextnode

        self.size -= 1

    def __len__(self):
        return self.size

    def __repr__(self):
        res = []
        current = self.root
        while current is not None:
            res.append(current.data)
            current = current.nextnode
        return str(res)

    def __iter__(self):
        current = self.root
        while current is not None:
            yield current
            current = current.nextnode

# test 

a = SList()
for c in 'ABCDE':
    a.insert(c)

print(a)

gen = iter(a)
print('root', next(gen))
for node in gen:
    print(node)

a.remove(2)

print(list(a))

for node in a:
    print(node)

output

['E', 'D', 'C', 'B', 'A']
root Node(E)
Node(D)
Node(C)
Node(B)
Node(A)
[Node(E), Node(D), Node(B), Node(A)]
Node(E)
Node(D)
Node(B)
Node(A)
0
Shain On

Try to follow this explanation: pythontips.com/2013/09/29/the-python-yield-keyword-explained

In short and simplified form - in order to create a generator, you should create a function that contains the yield keyword once or more. Anytime yield is reached in the function execution, it is put on hold, and passes the value after the keyword to the caller.

def my_gen(arg):
    yield arg * 10
    for i in xrange(5):
            if i / 2 == 0:
                 yield i

for x in my_gen(3):
     print(x) 

Will print: 30 0 2 4

The constructor of the Node class is also faulty, in addition to catching your own exception, you also have an error with your if condition not data will be True not only if data is False or None, but also if data is 0,an empty list, empty string and many more. Use data is None instead.