It may help to go through the program and visualise the state of the linked list:
When l = ll()
is executed, the constructor runs and l
gets to reference that data structure:
l
↓
┌────────────┐
│ head: None │
└────────────┘
Then l.head = Node(6)
will run the Node
constructor, and then assign the reference it gets from that to the above pictured head
member.
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│
▼
┌────────────┐
│ data: 6 │
│ next: None │
└────────────┘
A similar thing happens with second = Node(2)
, but now the returned reference is assigned to a name, not an attribute:
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second
▼ ↓
┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │
│ next: None │ │ next: None │
└────────────┘ └────────────┘
With l.head.next = second
the next
attribute of the first node gets a reference to the second node:
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second
▼ ↓
┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │
│ next: ─────────►│ next: None │
└────────────┘ └────────────┘
Now the more challenging insertEnd(9)
call is made. In insertEnd
, newnode = Node(value)
creates a new node and assigns its reference to a local name:
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second newNode (local)
▼ ↓ ↓
┌────────────┐ ┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │ │ data: 9 │
│ next: ─────────►│ next: None │ │ next: None │
└────────────┘ └────────────┘ └────────────┘
Another local name gets a reference to the first node that is already in the list: currentNode = l.head
:
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second newNode (local)
▼ ↓ ↓
┌────────────┐ ┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │ │ data: 9 │
│ next: ─────────►│ next: None │ │ next: None │
└────────────┘ └────────────┘ └────────────┘
↑
current (local)
Then the loop starts. It will make one iteration where it updates the local name to get another reference: currentNode = currentNode.next
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second newNode (local)
▼ ↓ ↓
┌────────────┐ ┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │ │ data: 9 │
│ next: ─────────►│ next: None │ │ next: None │
└────────────┘ └────────────┘ └────────────┘
↑
current (local)
At this point the loop condition is no longer true, so the loop exits. At this point we can be sure that current
references the tail node in the linked list. Finally, that tail node gets an update to its next
attribute, so it links to the new node: currentNode.next = newnode
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second newNode (local)
▼ ↓ ↓
┌────────────┐ ┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │ │ data: 9 │
│ next: ─────────►│ next: ─────────►│ next: None │
└────────────┘ └────────────┘ └────────────┘
↑
current (local)
The insertEnd
function returns, so all its local names are discarded, and the main program sees this:
l
↓
┌────────────┐
│ head: ─┐ │
└────────│───┘
│ second
▼ ↓
┌────────────┐ ┌────────────┐ ┌────────────┐
│ data: 6 │ │ data: 2 │ │ data: 9 │
│ next: ─────────►│ next: ─────────►│ next: None │
└────────────┘ └────────────┘ └────────────┘