Mod 4 Homeork Enhanced DoublyLinkedList Starter Code: # Do not modify this class class Node: 'Node object to be used in DoublyLinkedList' def __init__(self, item, _next=None, _prev=None): 'initializes new node objects' self.item = item self._next = _next self._prev = _prev def __repr__(self): 'String representation of Node' return f"Node({self.item})" class DoublyLinkedList: def __init__(self, items=None): 'Construct a new DLL object' self._head = None self._tail = None self._len = 0 self._nodes = dict() # dictionary of item:node pairs # initialize list w/ items if specified if items is not None: for item in items: self.add_last(item) def __len__(self): 'returns number of nodes in DLL' return self._len # TODO: Modify the 4 methods below to keep `self._nodes` up-to-date def add_first(self, item): 'adds item to front of dll' # add new node as head self._head = Node(item, _next=self._head, _prev=None) self._len += 1 # if that was the first node if len(self) == 1: self._tail = self._head # otherwise, redirect old heads ._tail pointer else: self._head._next._prev = self._head def add_last(self, item): 'adds item to end of dll' # add new node as head self._tail = Node(item, _next=None, _prev=self._tail) self._len += 1 # if that was the first node if len(self) == 1: self._head = self._tail # otherwise, redirect old heads ._tail pointer else: self._tail._prev._next = self._tail def remove_first(self): 'removes and returns first item' if len(self) == 0: raise RuntimeError("cannot remove from empty dll") # extract item for later item = self._head.item # move up head pointer self._head = self._head._next self._len -= 1 # was that the last node? if len(self) == 0: self._tail = None else: self._head._prev = None return item def remove_last(self): 'removes and returns last item' if len(self) == 0: raise RuntimeError("cannot remove from empty dll") # extract item for later item = self._tail.item # move up tail pointer self._tail = self._tail._prev self._len -= 1 # was that the last node? if len(self) == 0: self._head = None else: self._tail._next = None return item # TODO: Add a docstring and implement def __contains__(self, item): raise NotImplementedError # TODO: Add a docstring and implement def neighbors(self, item): raise NotImplementedError # TODO: Add a docstring and implement def remove_node(self, item): raise NotImplementedError Mod 4 Homework - Enhanced DoublyLinkedList Doubly Linked Lists (DLLs) support O ( 1 ) addition and removal from either end, making them a great choice for queues or deques. A downside of DLLs (and other linked data structures) is that it takes linear time ( O ( n )) to access nodes in the middle - in a traditional DLL, we have to start at the beginning and continually call node._next until we get to the node we're looking for. Here, we'll enhance a DLL with a dictionary of item:node pairs to give us O ( 1 ) access to interior nodes. This will speed up membership tes.