Getting Started with a Singly Linked List (Data Structure) πŸ’»πŸ’»!!

Getting Started with a Singly Linked List (Data Structure) πŸ’»πŸ’»!!

What is a Singly Linked list ⛓️⛓️...?

Simply put, a singly Linked List can be defined as a collection of nodes where each node consists of two parts --> a data value and a pointer that points to the next node in the list.

- Graphical Illustration of a node πŸƒπŸƒin a Linked List: linked-list-node.png

We use the term "Head" to define the first node in the Linked List. If the list is empty, the head points to NULL !!

  • Graphical Illustration of a Singly Linked List:

singly linked list find nth node from last educative.png

How to Create a Singly Linked List πŸ€”πŸ€”!!

Since Linked List consists of nodes, we need to make a class that represents the instance of a node.

  • I will be using Python 🐍 for this blog. If you aren't familiar with Python, I will try my best to be a lot more explanative at every point.

Node Representation

class Node:

    data = None
    next_node = None

    def __init__(self, data):
        self.data = data

    def __repr__(self):
        return "<Node Data: %s>" % self.data
  • The class "Node" helps us to create an object for storing a single node of a linked list. It models two attributes - data and the link to the next node in the List.
    1. Python repr Function returns a printable representation of an object in Python.
    2. The init method lets the class initialize the object's attributes and serves no other purpose.
    3. The self in Python represents the instance of the class. By using the β€œself”, we can access the attributes and methods of the class in python.

Now, since we have a class "Node" to model each node of the Linked List, we can actually move one step up the Ladder and start creating our very first Singly Linked List.

class LinkedList:
    head = None

    def __init__(self):
        self.head = None

    def isEmpty(self):
        return self.head == None
    def __repr__(self):
        return "I am a Linked List"
  • In the above code snippet, we just declared a class named "LinkedList", added an attribute named "head" and coded a few methods to get started.

    1. init will initialize the head of the Linked List to None. A practice that we follow to set attributes to a safe state.

    2. isEmpty() function checks if the list is empty. It does so by comparing "head" to "None"

    3. We have coded a repr function to return a statement just so that when we issue the name of the Linked List, we get something back to the screen. We will update it later!!

Okay, so now, since we have configured our Linked List class and have coded a few methods, why don't we test them? huh.

I am using an online Python Interpreter by Programiz to test my Code.

pic-1.png

  • What goes in the above Testing..?
    1. I just declared a variable "wordle" and made it an instance of the class "LinkedList"
    2. Next, I just wrote 'wordle' and pressed ENTER KEY and my repr function gets executed and I get to see "I am a Linked List" on the Screen.
    3. Lastly, I just test my isEmpty() function which returns "True" since we don't have anything inside our Linked List "wordle".

Now, let's do some Insertions πŸ”¨πŸ”¨!!

We will break the insertion process into three parts

  • Inserting a new node at the beginning of the list
  • Inserting a new node at the end of the list
  • Inserting a new node at any position in the list

Inserting a New Node at the beginning of the Linked List!!

We will start off by inserting a New Node at the beginning of the Linked List!!

  • We will call this method "push".
  • This method ADDs a new Node containing data at the head of the list
    def push(self, data):
         // 1. Using new Data, create an instance of a new node
         new_node = Node(data)
         // 2.  Assign the "next_node" property of "new_node" to current head of the Linked List
         new_node.next_node = self.head
         // 3. Move the "head" property of the Linked List to point to the new_node
         self.head = new_node
    
    We can now update our repr function to actually display some Data to have a better representation of our Linked List.
def __repr__(self):

        nodes = []
        current = self.head

        while current:
            if current is self.head:
                nodes.append("[Head: %s]" % current.data)
            elif current.next_node is None:
                nodes.append("[Tail: %s]" % current.data)
            else:
                nodes.append("[%s]" % current.data)

            current = current.next_node

        return '->'.join(nodes)
  • WHAT we did in the above code snippet πŸ€·β€β™‚οΈπŸ€·β€β™‚οΈ..?

    1. We declared an array "nodes" and then a variable "current" and set its value to the current head of the Linked List.
    2. "while current" is equals to "while current != None". This will make sure that we iterate over the whole Linked List
    3. Inside the while loop, we have an if-elif-else construct. After every iteration, we are moving to next node in our list using "current = current.next_node"
    4. After exiting while loop, return '->'.join(nodes) will return all the elements of the array "node" joined together with this "->" symbol for a nice representation!!
  • Since we are updating repr, let's include one more functionality to see the size of our Linked list
def size(self):
        count = 0
        current = self.head
        while current:
            count = count+1
            current = current.next_node

        return count
  • In the above code snippet:

    1. We declared a variable "count" and set its value to 0. Declared another variable "current" and set its value to the current Head of our Linked List.
    2. Then we start a while loop that will keeping iterating until we reach the end of the loop.
    3. We each iteration, we are incrementing the value of the count. Therefore, when we exit the while loop, we have a value of count set exactly to a number of nodes in our list.
    4. At last, we return the count variable.

That was so much coding 😩!! I know, so for a break, why don't we do some TESTING πŸ“‹πŸ“‹..?

pic-push.png

  • What goes in the above Testing..?
    1. I just declared a variable "wordle" that is an instance of the class "LinkedList"
    2. Now, I called the push method three times on the wordle object, each time with a different value.
    3. Next, I just wrote 'wordle' and pressed ENTER KEY and my repr function gets executed and I get to see a beautiful representation of our Linked List on the Screen.

Inserting a New Node at the end of the Linked List!!

Now, let's make another insert function that will Insert a New Node at the end of the Linked List!!

  • We will call this method "append".
  • This method ADDs a new Node containing data at the end of the list
def append(self, data):
        // 1. Using new Data, create an instance of a new node
        new_node = Node(data)
        // 2. Check if the list is empty, if so make a new node as head of the list
        if self.head is None:
            self.head = new_node
            return
        //3. Else, traverse through the list until you get to the last node
        current = self.head

        // while current.next_node!=None: is same as while current.next_node:
        while current.next_node:
            current = current.next_node

        // 4. Change the "next_node" property of the last node
        current.next_node = new_node

πŸ›ŽοΈπŸ›ŽοΈImportant - Why did we use this logic..?

 while current.next_node:
            current = current.next_node

Because we know that whatever the last node will be in the Linked List, its "next_node" property will point to the "None". We will be using this logic a lot in the coming methods that we design!!

Inserting a new node containing data at any position in the list

  • We will call this method "insert".
  • This method ADDs a new Node containing data at the position specified in the function call!!
  • We will break this function into 4 cases:
    1. position is 1 i.e., at the beginning of the list
    2. position is one offset more than the size of the list. For instance, if the size of the list is 5 and the value of the position is 6. It means --> to insert at the end of the list
    3. position is out of bounds
    4. Neither at the beginning nor at the end --> means anywhere in the middle!!
def insert(self, position, data):
        // 1. if position is 0, which basically means to insert a new node at the beginning of the list
        if position == 1:
            self.push(data)

        // 2. if position is just one offset more than length of the list, which basically means to insert a new node at the end of the list
        elif position == self.size()+1:
            self.append(data)
       // 3. In case, the position entered is out of Bound, we will raise an exception
        elif position > self.size()+1 or position < 0:
            raise Exception(
                "Position entered is out of Bounds of the Linked List Size!!")
    // 4.  position is anywhere in the middle
        else:
            index = 1
            current = self.head
            new_node = Node(data)

            while index < position-1:
                current = current.next_node
                index += 1

            prev_node = current
            next_node = current.next_node

            prev_node.next_node = new_node
            new_node.next_node = next_node

Now, since we have made all the required insert methods. I think we can do some testing on them. In the testing below, all I did is declared a variable "wordle" and made it an instance of the class "LinkedList". Then I used all three types of insertion methods that we designed. After every function call, I am just writing "wordle" and pressing "ENTER - KEY" that calls my "repr" function and we get to see a beautiful representation of our Linked list.

testing-2.png

Now, since we have learned how to insert nodes into a Linked list, we can officially design a method to remove the existing nodes in a Linked List.

  • EQKATo_U8AAg9nx.jpg

If you have the same question, we got you covered πŸ˜‰πŸ˜‰!!

Removing a node at any position in the list:

  • We will call this method "remove".
  • This method removes an existing Node containing data at the position specified in the function call!!
def remove(self, position):
        // 1. if position is out of bounds!!
        if position > self.size() or position < 1:
            raise Exception(
                "Position entered is out of Bounds of the Linked List Size!!")

        // 2. If position is 1, which basically means to remove the node at the beginning of the list
        elif (position == 1):
            current = self.head
            self.head = current.next_node
       // 3. 
        else:
            index = 1
            previous = None
            current = self.head

            while current and index < position:
                previous = current
                current = current.next_node
                index += 1

            previous.next_node = current.next_node
            current.next_node = None

Let's test this "remove" method that we just designed!! In the testing below, I started off as usual by creating an instance of the class "LinkedList" named "wordle". Then we added few values to it using "push" method.

Now, we test our remove method by removing from different positions. You will notice that we get an error/exception when we try to remove from position 7 since our Linked List at that point consist of just two nodes.

testing-3.png

Looking up any Node at any position in the list:

Let's wrap this Blog up by adding one last functionality to Linked List that will help us to look up any Node in our List.

  • We will name this function "lookup"πŸ€“πŸ€“.
  • It will return us the node located at the position specified in the function call
def lookUp(self, position):

  // 1. If position is 1, it basically means we need to return the head of the linked list
        if position == 1:
            return self.head

// 2.  If position is equals to the size of the list, we need to return the last node in the list
        elif position == self.size():
            // traverse through the list until you get to the last node
            current = self.head
            while current.next_node:
                current = current.next_node

            return current
// 3. if position entered is out of bounds!!
        elif position > self.size() or position < 0:
            raise Exception(
                "Position entered is out of Bounds of the Linked List Size!!")
// 4. If node looking up is anywhere in the between
        else:
            index = 1
            current = self.head

            while index < position:
                current = current.next_node
                index += 1

            return current

Here is the Entire code that we designed until now:

class Node:

    data = None
    next_node = None

    def __init__(self, data):
        self.data = data

    def __repr__(self):
        return "<Node Data: %s>" % self.data


class LinkedList:
    head = None

    def __init__(self):
        self.head = None

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

    def size(self):
        count = 0

        current = self.head
        while current:
            count = count+1
            current = current.next_node

        return count

    def push(self, data):

        new_node = Node(data)
        new_node.next_node = self.head

        self.head = new_node

    def append(self, data):

        new_node = Node(data)
        if self.head is None:
            self.head = new_node
            return

        current = self.head
        while current.next_node:
            current = current.next_node

        current.next_node = new_node

    def lookUp(self, position):

        if position == 1:
            return self.head
        elif position == self.size():
            current = self.head
            while current.next_node:
                current = current.next_node

            return current

        elif position > self.size() or position < 0:
            raise Exception(
                "Position entered is out of Bounds of the Linked List Size!!")

        else:
            index = 1
            current = self.head

            while index < position:
                current = current.next_node
                index += 1

            return current

    def remove(self, position):
        if position > self.size() or position < 1:
            raise Exception(
                "Position entered is out of Bounds of the Linked List Size!!")
        elif (position == 1):
            current = self.head
            self.head = current.next_node

        else:
            index = 1
            previous = None
            current = self.head

            while current and index < position:
                previous = current
                current = current.next_node
                index += 1

            previous.next_node = current.next_node
            current.next_node = None

    def insert(self, position, data):

        if position == 1:
            self.push(data)

        elif position == self.size()+1:
            self.append(data)

        elif position > self.size()+1 or position < 0:
            raise Exception(
                "Position entered is out of Bounds of the Linked List Size!!")
        else:
            index = 1
            current = self.head
            new_node = Node(data)

            while index < position-1:
                current = current.next_node
                index += 1

            prev_node = current
            next_node = current.next_node

            prev_node.next_node = new_node
            new_node.next_node = next_node

    def __repr__(self):

        nodes = []
        current = self.head

        while current:
            if current is self.head:
                nodes.append("[Head: %s]" % current.data)
            elif current.next_node is None:
                nodes.append("[Tail: %s]" % current.data)
            else:
                nodes.append("[%s]" % current.data)

            current = current.next_node

        return '->'.join(nodes)

FINAL TESTING πŸ–₯️πŸ–₯️!!

testing-4.png

  • We tested all the methods that we designed. Hopefully, this blog would get you started with Singly Linked List Data Structure.

I publish a new blog every single week. Lol πŸ˜‚, this is my first blog. Well, I will try to write one every single week. Nevertheless, I am always working on some cool Projects. So feel free to follow me on these platforms:

16929412-doodle-style-the-end-sketch-with-cartoon-mushroom-cloud-and-text-message-in-format.webp

©️ Hamit Sehjal 2022

Β