<jupyter_start><jupyter_text>Implementation of Linked List using pythonLinked List is a type of data structure which stores data in the Random Memory location by links and pointers. A linked List has two parts     Data part:This stores the data element of the Node and also consists the reference of the address of the next element     Next Part: This stores the address of the next pointer via links     Below are some pictorial representation of how linked list looks like<jupyter_code>from IPython.display import Image
Image(filename='C:/Users/prakhar/Desktop/Python_Data_Structures_and_Algorithms_Implementations/Images/Linked_List.png',width=800, height=400)
from IPython.display import Image
Image(filename='C:/Users/prakhar/Desktop/LinkedList.png',width=800, height=400)
class Node: #It represents the node class which will have data and next initilized
    def __init__(self, data=None, next=None):
        self.data = data
        self.next = next

class LinkedList: # This class is used for having the defualt value of head (which is the first data element of LL) initilized to None
    def __init__(self):
        self.head = None

    def print(self): #helper function to print the different operartions of Linked List
        if self.head is None: 
            print("Linked list is empty")# when the head will be none then we will have linked list as empty as no elements are present that time 
            return
        itr = self.head #this is a iterator which iteratorates our LL 
        llstr = ''
        while itr:
            llstr += str(itr.data)+' --> ' if itr.next else str(itr.data)# if the iterator consists of any data we just append that to our llstr
            itr = itr.next
        print(llstr)
        
    def get_length(self): #this is used to calculate the length of the Linked List operation  which we are performing and which is invoked by our Method Call 
    
        count = 0
        itr = self.head
        while itr:
            count+=1
            itr = itr.next

        return count

    def insert_at_begining(self, data): #insertion at the beginning 
        node = Node(data, self.head)#for this we need to assign the value to the data and the next to the head.
        self.head = node
    def insert_at_end(self, data): #Insertion at Ending
    
        if self.head is None: #for this we need to assign the value to the data and the next to none
            self.head = Node(data, None)
            return

        itr = self.head

        while itr.next:
            itr = itr.next

        itr.next = Node(data, None)

    def insert_at(self, index, data): #if we insert by index
        if index<0 or index>self.get_length():
            raise Exception("Invalid Index")

        if index==0: #this means we want to insert at self.head
            self.insert_at_begining(data)
            return

        count = 0
        itr = self.head
        while itr:
            if count == index - 1: #in this case we will iterate till index -1 and will initialze value as data and next as itr.next
                node = Node(data, itr.next)
                itr.next = node
                break

            itr = itr.next
            count += 1

    def remove_at(self, index): #Checking the conditions 
        if index<0 or index>=self.get_length():
            raise Exception("Invalid Index")

        if index==0: # if index is zero we just move the self.head to self.head.next then it automaticaly removes the first element
            self.head = self.head.next
            return

        count = 0
        itr = self.head
        while itr: #again if we want to remove at a index we just make the index - 1 next pointer point towards the itr.next.next so this automatically skips the element in between
            if count == index - 1:
                itr.next = itr.next.next
                break

            itr = itr.next
            count+=1

    def insert_values(self, data_list):    #inserting by values
        self.head = None
        for data in data_list:
            self.insert_at_end(data) #if self.head is none that means we are the end of the LL
        
    def insert_after_value(self, data_after, data_to_insert): #here the difference is instead of index we use data_after for al operations
        if self.head is None:
            return

        if self.head.data==data_after:
            self.head.next = Node(data_to_insert,self.head.next)
            return

        itr = self.head
        while itr:
            if itr.data == data_after:
                itr.next = Node(data_to_insert, itr.next)
                break

            itr = itr.next

    def remove_by_value(self, data):
        if self.head is None:
            return

        if self.head.data == data:
            self.head = self.head.next
            return

        itr = self.head
        while itr.next:
            if itr.next.data == data:
                itr.next = itr.next.next
                break
            itr = itr.next<jupyter_output><empty_output><jupyter_text>A linked list has two major Advantage over lists or Array      You need not to pre-allocate space    Insertion is more efficient and easier  Pictorial Representation of Operations of Linked List<jupyter_code>Image(filename='C:/Users/prakhar/Desktop/Python_Data_Structures_and_Algorithms_Implementations/Images/Insertion_at_begin.png',width=800, height=400)<jupyter_output><empty_output><jupyter_text>Insertion at Beginning<jupyter_code>Image(filename='C:/Users/prakhar/Desktop/Python_Data_Structures_and_Algorithms_Implementations/Images/Insertion_at_end.png',width=800, height=400)<jupyter_output><empty_output><jupyter_text>Insertion at End<jupyter_code>Image(filename='C:/Users/prakhar/Desktop/Python_Data_Structures_and_Algorithms_Implementations/Images/Inserting_node_by_index.png',width=800, height=400)<jupyter_output><empty_output><jupyter_text>Insersting Node at Index<jupyter_code>Image(filename='C:/Users/prakhar/Desktop/Python_Data_Structures_and_Algorithms_Implementations/Images/deleting_at_beginning.png',width=800, height=400)<jupyter_output><empty_output><jupyter_text>Deletion in beginning<jupyter_code>Image(filename='C:/Users/prakhar/Desktop/Python_Data_Structures_and_Algorithms_Implementations/Images/deleting_node_by_index.png',width=800, height=400)<jupyter_output><empty_output><jupyter_text>Deletion of node by Index<jupyter_code>if __name__ == '__main__':
    ll = LinkedList()

    ll.insert_at_begining(5)
    ll.insert_at_begining(89)
    ll.print()
    print("length:", ll.get_length())
    #ll.insert_values(["banana","mango","grapes","orange"])
    #ll.insert_at(1,"blueberry")
    #ll.remove_at(2)
    #ll.print()

    #ll.insert_values([45,7,12,567,99])
    #ll.insert_at_end(67)
    #ll.print()
    #ll.insert_after_value("mango","apple")
    #ll.print()
    #ll.remove_by_value("orange")
    #ll.print()
    #ll.print()
    #ll.remove_by_value("banana")
    #ll.remove_by_value("mango")
    #ll.remove_by_value("apple")
    #ll.remove_by_value("grapes")
    #ll.print()<jupyter_output>89 --> 5
length: 2
{'fraction_non_alphanumeric': 0.08413325804630152, 'fraction_numerical': 0.010869565217391304, 'mean_word_length': 2.760615711252654, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 32, 'https://': 0, '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': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '261030', 'license': 'MIT', 'n_tokens_mistral': 2212, 'n_tokens_neox': 2104, 'n_words': 634, 'path': 'Linked_List/Single_Link_List_Operations.ipynb', 'repo_name': 'pdx97/Python_Data_Structures_and_Algorithms_Implementations', 'size': '7084'}