Search code examples
pythonooplinked-list

Updating the tail in a circular singly linked list


I am writing a method to insert a value at a given location in a circular linked list:

    def insertCSLL(self,value,location): #Insertion of a node in Singly LL
        if self.head is None:
            return  "The Head refernce is None"
        else:
            newNode = Node(value)
            if location == 0:      # insertion at first position
                newNode.next = self.head
                self.head = newNode
                self.tail.next = newNode

In the last line self.tail.next points to the newNode

Do I also need to write self.tail = newNode, so to arrive at this?

            newNode = Node(value)
            if location == 0:      # insertion at first position
                newNode.next = self.head
                self.head = newNode
                self.tail.next = newNode
                self.tail = newNode  # <<<

Is this last line optional?


Solution

  • is this last line optional?

    It should not be there as it introduces a bug: it makes self.tail equal to self.head which should only happen when the list has fewer than 2 nodes. But since this code is in the else block, it means the new node is added to a list which already has an element, and then head and tail should not be the same reference.

    Not your question, but you should obviously deal with the other cases as well, not only where location is 0. And in those cases there is a case where you may want to add that assignment. This is the case where location is equal to the number of nodes that the list has (before the insertion), indicating that the insertion should happen right after the current tail, and then become the tail.

    Suggestion

    For a circular list it should not be necessary to maintain two references (head and tail). As in a non-empty list it is always supposed to be true that the head is the successor of the tail, you can choose to only maintain a reference to the tail.

    Here is an implementation of how that could work:

    class Node:
        def __init__(self, value, nxt=None):
            self.value = value
            self.next = nxt or self  # Default is a self reference!
            
    
    class CircularSinglyLinkedList:
        def __init__(self):
            self.tail = None
    
        def get_at(self, location):
            if self.tail:
                curr = self.tail.next
                for _ in range(location):
                    curr = curr.next
                return curr     
        
        def insert(self, value, location=0):
            if not self.tail:
                self.tail = Node(value)
            else:
                prev = self.get_at(location - 1) if location else self.tail
                # insert node after prev
                prev.next = Node(value, prev.next)
                # if the node was inserted after the tail (and location was not 0)
                #    then make the new node the tail 
                if prev == self.tail and location:
                    self.tail = prev.next
    
        # Iterator can serve many purposes, like printing
        def __iter__(self):  
            if self.tail:
                curr = self.tail.next
                while curr is not self.tail:
                    yield curr.value
                    curr = curr.next
                yield curr.value
    

    Demo run:

    lst = CircularSinglyLinkedList()
    lst.insert(1)
    lst.insert(2)
    lst.insert(3)
    lst.insert(4)
    lst.insert(5, 4)
    print(*lst)  # 4 3 2 1 5