def add_head(self, value):
     '''
     Add value, before first node.
     '''
     new_node = List_Node(value)
     new_node._next = self._head
     self._head = new_node
     if self._tail is None:
         # If list used to be empty, update tail pointer
         self._tail = self._head
    def add_head(self, value):
        '''
        Add value into node just after head sentinel
        '''

        # EXERCISE 4: No special case for empty list
        # Modifications:
        # a) new_node._next should point to self._head._next, instead of self._head
        # b) self._head._next should be changed, instead of self._head
        # c) DELETE code that checks an empty tail.

        new_node = List_Node(value)
        new_node._next = self._head._next
        self._head._next = new_node
        return self._head
    def add_tail(self, value):
        '''
        Add value into node after the tail
        '''

#        print('--------')
#        print('Entering add_tail(', value ,')')

        new_node = List_Node(value)

#        print('before, list is:')
#        print(repr(self))
#        print('new node:', repr(new_node))
        if self._tail is None:

#            print('add_tail: empty list')

            # Special case: empty list
            # Create a list with a ONE node
            self._tail = new_node
            self._head = new_node
        else:

#            print('add_tail: general case')

            # General case: add after tail, change tail pointer
            self._tail._next = new_node
            self._tail = new_node
    def __init__(self, data=None):
        '''
        List constructor.  Can be invoked in three ways:
        Sentinel_List() makes an empty list.
        Sentinel_List(<another Sentinel_List>) makes a copy of the other list.
        Sentinel_List(<a python list>) makes list containing data's values.
        '''
        # EXERCISE 0 (done for you): create sentinel nodes
        self._head = List_Node()
        self._tail = List_Node()
        # link sentinels together.
        self._head._next = self._tail

        if data != None:
            if type(data) in (list, Sentinel_List):
                for x in data:
                    self.add_tail(x)
            else:
                raise ValueError("Can't build list from data of type " +
                                 str(type(data)))
    def add_tail(self, value):
        '''
        Add value into node at the end of the list
        '''

        # EXERCISE 5: Insert before tail sentinel
        # Modifications:
        # a) set new_node._next = self._tail
        # b) delete all code after that (it's all different)
        # c) walk down the list:
        #    - prev starts at self._head
        #    - keep going while prev._next is not self._tail
        #    - after loop, prev._next should now be new_node

        new_node = List_Node(value)  # Keep this line
        # Put a) here, and remove the lines below this one.
        new_node._next = self._tail

        _prev = self._head
        while _prev._next is not self._tail:
            _prev = _prev._next
        _prev = new_node
        return self._head
    def insert(self, value, index):
        '''
        Insert a value at a given index
        '''
        # Check if index is valid
        if index < 0 or index >= self.size():
            raise IndexError(str(index))

        if self._head == None:
            # Special case: empty list
            # Index will be zero (guaranteed), so just add to front
            self.add_head(value)
        else:
            # Walk down the list, stopping just before
            # insertion point
            prev = self._head
            for i in range(index - 1):
                prev = prev._next
            prev._next = List_Node(value, prev._next)
    def insert(self, value, index):
        '''
        Insert a value at a given index
        '''
        # Check if index is valid
        if index < 0 or index >= self.size():
            raise IndexError(str(index))

        # EXERCISE 8: skip head sentinel
        # Modifications:
        # a) Delete the special-case code (empty list)
        # b) Delete the else: (of course)
        # c) De-indent the code after the else (of course)
        # d) Take one more step: (range(index) instead of range(index-1))

        # Walk down the list, stopping just before
        # insertion point
        prev = self._head
        for i in range(index):
            prev = prev._next
        prev._next = List_Node(value, prev._next)