Constructing a Circular Linked List in Python and Counting Its Nodes

πŸ’‘ Problem Formulation: The task at hand involves implementing a circular linked list in Python, populating it with a defined number of nodes, and creating a function to count these nodes. A circular linked list is one where each node is connected in a circle, with the last node linking back to the first. For instance, given an input of n = 5, we expect the creation of a circular linked list with 5 nodes and upon counting, the output should be 5.

Method 1: Iterative Approach

Creating a circular linked list with an iterative method involves successively adding nodes, then linking the last node back to the first node to complete the circle. This approach also iterates over the list to count the number of nodes until it reaches the node where it started.

Here’s an example:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def create_circular_linked_list(n):
    head = None
    temp = None
    for i in range(n):
        new_node = Node(i)
        if not head:
            head = new_node
        else:
            temp.next = new_node
        temp = new_node
    temp.next = head
    return head

def count_nodes(head):
    count = 1
    current = head
    while current.next != head:
        count += 1
        current = current.next
    return count

# Example use
clist = create_circular_linked_list(5)
print(count_nodes(clist))

Output:

5

This snippet first defines a Node class with a constructor that initializes the node’s data and link to the next node. The create_circular_linked_list function generates a circular linked list with n nodes by iteratively linking new nodes. The count_nodes function determines the number of nodes by traversing the list until it circles back to the head, incrementing a counter along the way.

Method 2: Recursive Creation and Count

The recursive method uses a function that calls itself to create each node and link it appropriately in the circular list. Counting nodes is also performed recursively, passing along the updated count with each recursive call until the list loops back to the head node.

Here’s an example:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def create_circular_linked_list_recursive(last, n):
    if n == 0:
        return last.next
    new_node = Node(n)
    if not last:
        last = new_node
        last.next = last
    else:
        new_node.next = last.next
        last.next = new_node
    return create_circular_linked_list_recursive(new_node, n - 1)

def count_nodes_recursive(node, head, count=1):
    if node.next == head:
        return count
    return count_nodes_recursive(node.next, head, count + 1)

# Example use
last_node = create_circular_linked_list_recursive(None, 5)
count = count_nodes_recursive(last_node.next, last_node.next)
print(count)

Output:

5

This code defines a Node class, a create_circular_linked_list_recursive function to create the list, and a count_nodes_recursive function to count the nodes. The creation function links new nodes until the base case is reached, and the count function keeps calling itself while incrementing the count until it loops back to the head of the list.

Method 3: Using a Tail Pointer

The tail pointer method keeps a reference to the last node added to the list. This makes adding new nodes and linking the last node back to the head more efficient. Counting nodes is similar to the iterative approach but uses the tail pointer as the loop termination condition.

Here’s an example:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def create_circular_linked_list_tail(n):
    head = tail = None
    for i in range(n):
        new_node = Node(i)
        if not head:
            head = new_node
        else:
            tail.next = new_node
        tail = new_node
    tail.next = head
    return head

def count_nodes_tail(head, tail):
    count = 1
    current = head
    while current != tail:
        count += 1
        current = current.next
    return count + 1

# Example use
clist_head, clist_tail = create_circular_linked_list_tail(5)
print(count_nodes_tail(clist_head, clist_tail))

Output:

5

This code block introduces a Node class, a create_circular_linked_list_tail function to craft the list using a tail pointer, and a count_nodes_tail function for counting. The tail pointer streamlines adding new nodes to the list and finishing the circular link. The counting function iterates through the list, using the tail pointer as its stopping condition.

Method 4: Pythonic Approach with Generator

Leveraging Python’s generator functions can make the counting process more Pythonic and readable. A generator yields nodes on the fly, avoiding storing an explicit count, and stops when it completes the circle.

Here’s an example:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

def create_circular_linked_list(n):
    head = None
    tail = None
    for i in range(n):
        new_node = Node(i)
        if not head:
            head = new_node
        else:
            tail.next = new_node
        tail = new_node
    tail.next = head
    return head

def nodes_generator(head):
    current = head
    while True:
        yield current
        current = current.next
        if current == head:
            break

# Example use
clist = create_circular_linked_list(5)
count = sum(1 for _ in nodes_generator(clist))
print(count)

Output:

5

The Node class serves as the building block for the list. After creating the circular linked list with create_circular_linked_list, we use a generator function nodes_generator that yields nodes until the head node is reached again. The count is then generated using a generator expression that sums a sequence of ones for each node returned by the generator.

Bonus One-Liner Method 5: Pythonic Count with List Comprehension

If the size of the linked list is reasonable, one can utilize list comprehension to walk through the list and count nodes in a more Pythonic one-liner fashion.

Here’s an example:

# Assuming 'create_circular_linked_list' function is defined as above
clist = create_circular_linked_list(5)
count = len([node for node in nodes_generator(clist)])
print(count)

Output:

5

In this snippet, the nodes_generator function from the previous example is used once more. We create a list comprising each node from the generator and then simply use the len() function to count the number of nodes in the list.

Summary/Discussion

  • Method 1: Iterative Approach. Straightforward and intuitive, but may involve more code lines and direct manipulation of node references.
  • Method 2: Recursive Creation and Count. Elegant, but not ideal for very large lists due to potential stack overflow from deep recursion.
  • Method 3: Using a Tail Pointer. Efficient node addition, with counting directly associated with list structure. However, it requires keeping an extra reference to the tail.
  • Method 4: Pythonic Approach with Generator. Clean and easily readable code but may incur a slight performance hit due to generator overhead.
  • Bonus One-Liner Method 5: Extremely compact, but not suitable for very large lists as it generates an intermediate list structure.