<jupyter_start><jupyter_text>Unordered ListExtra exercise in chapter 4 ([link](https://runestone.academy/runestone/books/published/pythonds3/BasicDS/ImplementinganUnorderedListLinkedLists.html)). As stated in the exercise:> The remaining methods append, insert, index, and pop are left as exercises. Remember that each of these must take into account whether the change is taking place at the head of the list or someplace else. Also, insert, index, and pop require that we name the positions of the list. We will assume that position names are integers starting with 0.We implement those methods as requested in the class.<jupyter_code>class Node:
    """A node of a linked list"""

    def __init__(self, node_data):
        self._data = node_data
        self._next = None

    def get_data(self):
        """Get node data"""
        return self._data

    def set_data(self, node_data):
        """Set node data"""
        self._data = node_data

    data = property(get_data, set_data)

    def get_next(self):
        """Get next node"""
        return self._next

    def set_next(self, node_next):
        """Set next node"""
        self._next = node_next

    next = property(get_next, set_next)

    def __str__(self):
        """String"""
        return str(self._data)


class UnorderedList:
    def __init__(self):
        self.head = None
        self._size = 0

    def is_empty(self):
        return self.head == None

    def add(self, item):
        temp = Node(item)
        temp.set_next(self.head)
        self.head = temp
        self._size += 1

    def size(self):
        return self._size

    def search(self, item):
        current = self.head
        while current is not None:
            if current.data == item:
                return True
            current = current.next

        return False

    def remove(self, item):
        current = self.head
        previous = None

        while current is not None:
            if current.data == item:
                break
            previous = current
            current = current.next

        if current is None:
            raise ValueError("{} is not in the list".format(item))
        if previous is None:
            self.head = current.next
        else:
            previous.next = current.next

        self._size -= 1

    def append(self, item):
        current = self.head 
        temp = Node(item)

        if self.size() == 0:
            self.head = temp 
        else:
            while current.next is not None:
                current = current.next
                
            current.set_next(temp)
        
        self._size += 1

    def insert(self, item, pos):
        if pos == 0 or self.size() == 0:
            self.add(item)
        elif pos == self.size() - 1:
            self.append(item)
        else:
            prev = None
            current = self.head
            current_pos = 0

            while current_pos < pos and current != None:
                current_pos += 1
                prev = current
                current = current.next

            temp = Node(item)
            prev.next = temp 
            temp.next = current

    def index(self, item):
        current = self.head 
        count = 0

        while current is not None:
            if current.data == item:
                return count 
            else:
                count += 1
                current = current.next

        return -1

    def pop(self, pos=None):
        current = self.head
        i = 0

        pos = self.size() - 1 if pos is None else pos
        item = None 

        while current is not None:
            if i == pos:
                item = current.data   
                break

        self.remove(item)  

        return item

    def __str__(self):
        s = ""
        current = self.head

        while current.next != None:
            s += f"{current.get_data()} -> "
            current = current.next
        
        s += str(current.get_data())

        return s

    def get_slice(self, start, stop):
        if start > self._size - 1 or stop > self._size - 1:
            raise Exception("Out of bounds exception")
        
        slice_ul = UnorderedList()
        i = 0
        current = self.head
        
        while i < start:
            current = current.next 
            i += 1

        while i < stop:
            slice_ul.append(current.data)
            current = current.next
            i += 1

        return slice_ul<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08617634027470093, 'fraction_numerical': 0.00509525919361985, 'mean_word_length': 1.5294117647058822, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 9, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '380773', 'license': 'MIT', 'n_tokens_mistral': 1256, 'n_tokens_neox': 1179, 'n_words': 400, 'path': 'chapter4/unordered_list.ipynb', 'repo_name': 'RiccardoMPesce/PythonDS3-Selected-Assignments', 'size': '4514'}