Linked list- current vs. current.next when appending - python

Node:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def add() part one:
def add(self, newData):
if self.head is None:
self.head = Node(newData)
return
current = self.head
def add() part 2
Why does this work?
while current.next:
current = current.next
current.next = Node(newData)
And this doesn't?
while current:
current = current.next
current = Node(newData)
Aren't "current.next" in first one and "current" in second one same?

The only difference between the two loop codes is the last line, where you either assign to current.next or to current. Those do fundamentally different things.
When you assign to current.next, you're modifying an attribute of the Node object referenced by current. That change happens in place, so you'll see the change through other references (like the chain of nodes earlier in the list). That's what you want to happen.
In the modified version, when you assign to current, you're only rebinding the variable name in the local namespace. You're not changing any node! You could compare it to:
a = 1
b = a
b = 2
print(a) # still prints 1
Here b is current in the second version of your code. a is the next attribute of the last node of the list (which was called current.next in the working version of the code). Rebinding the local name b doesn't change the other reference to the same value.

Related

Can't add node in circular linked list if nodes are made without the function

I need to add a node at the start of the circular linked list
I can add the node if the list is empty and I add every node using the function but if the list already has nodes then the function doesn't work
class Node:
def __init__(self,data):
self.data = data
self.ref = None
class CircularLinkedList:
def __init__(self):
self.head = None
self.tail = None
def atStart(self,newData):
newNode = Node(newData)
if self.head is None:
self.head = newNode
self.tail = newNode
self.tail.ref = newNode
if self.tail:
newNode.ref = self.tail.ref
self.tail.ref = newNode
def printList(self):
if self.head is None:
print("Circular linked list is empty")
n = self.head
while n:
print(n.data)
n = n.ref
if n == self.head:
break
cll = CircularLinkedList()
cll.head = Node("Monday")
e2 = Node("Tuesday")
e3 = Node("Wednesday")
cll.head.ref = e2
e2.ref = e3
e3.ref = cll.head
cll.atStart("Sunday")
cll.atStart("Monday")
cll.printList()
I need to change the head node using the atStart function and print the whole list but it prints the three nodes I have made (cll.head,e2,e3) or just the newNode using the atStart if list is empty
I tried debugging and seems like whenever the list is not empty it only takes the newNode data and doesnt set the ref of it head node and just prints the old three nodes
There are a few issues:
In atStart the if self.tail: condition will always be true. This is problematic, as only one of the two alternatives should be executed, not both. Yet when the first block executes (the case where the list was empty), self.tail gets a value, so this second if block will also execute (the case where the list is not empty). This should not be an if, but just a plain else: -- you want to cover all remaining cases (where the list was not empty).
In the case the list was not empty, the atStart method does not make the new node the head of the list, so the list becomes inconsistent.
The main program should not tinker with the head or ref attributes -- this should be left to the class code only. This leads to all kinds of problems. In this case the problem is that cll.tail remains None, and so the list is inconsistent and you cannot rely on the correct execution of the atStart method, because that assumes that tail was set correctly. You could add cll.tail = e3 to fix that particular issue, but again, this is bad practice. The logic for attaching nodes to eachother should be performed within the class code only.
Not a problem, but it is overkill for a circular list to have two node references (head and tail): in a non-empty list the head always follows the tail node, so there really is no need to remember what the head is when you have the tail. In short: just keep track of the tail.
Here is the adapted code:
class Node:
def __init__(self,data):
self.data = data
self.ref = None
class CircularLinkedList:
def __init__(self):
# No need for a head reference, as it is always coming after the tail
self.tail = None
def atStart(self,newData):
newNode = Node(newData)
if self.tail is None:
self.tail = newNode
else: # Must be ELSE to avoid executing both blocks
newNode.ref = self.tail.ref
self.tail.ref = newNode # new node always becomes the head
def printList(self):
if self.tail is None:
print("Circular linked list is empty")
n = self.tail.ref
while n:
print(n.data)
if n == self.tail:
break
n = n.ref
The main program corrected:
cll = CircularLinkedList()
# Don't alter the atttributes of the linked list or its nodes! Only use methods:
cll.atStart("Wednesday")
cll.atStart("Tuesday")
cll.atStart("Monday")
cll.printList()
print("----")
cll.atStart("Sunday")
cll.atStart("Monday")
cll.printList()
From www.geeksforgeeks.org:
The circular linked list is a linked list where all nodes are
connected to form a circle. In a circular linked list, the first node
and the last node are connected to each other which forms a circle.
There is no NULL at the end.
def atStart(self,newData):
if self.head is None:
# Initialize head and tail
newNode = Node(newData)
self.head = newNode
self.tail = newNode
else:
# Copy the old head node
old_head = self.head
# Make a new head node
self.head = Node(newData)
# Update the reference of the new head node to point the old_head
self.head.ref = old_head
# Now update the tail ref to point to the head (circular)
self.tail.ref = self.head
Explanation:
In this code if head and tail are empty, for the the first call of atStart method the head and tail will be the same, that's self.head is self.tail which evaluates to True. It's possible to implement this code a better way, where for example __init__ or atStart methods should except two values instead of one to initialize the head and tail together. Because in this way, the data of the head will reside in the tail uselessly even after adding a new head. If you call this:
cll.head.data == cll.tail.data
Now it's interesting what you will get. The first call to atStart method will evaluate to True, the second call may not evaluate to true. That's, whatever the first cll.head.data held will be there in cll.tail.data always. Effectively we're wasting cll.tail.data.
A better approach would be
def atStart(self, *newData):
if self.head is None:
# Initialize head and tail (each with its own data)
self.head = Node(newData[0])
self.tail = Node(newData[1])
# tail references head (circular)
self.tail.ref = self.head
#rest of code from above....
The second call to the atStart method will update the head of the circular list to point to the new head node (created to add the last value) and the tail reference will be updated to point back to the new head. Note that the ref attribute of the new head will be updated to reference the old head, it's a very important step or you will end up losing your old data.

in python, self. variable got passed to 2 different variables, are they changing at the same time?

class LinkList:
def __init__(self):
self.head=None
def initList(self, data):
self.head = ListNode(data[0])
r = self.head
p = self.head
for i in data[1:]:
node = ListNode(i)
p.next = node
p = p.next
return r
In the function, self.head gets set twice to two different variables - r and p.
In the for loop only p got updated in the loop, what is the difference between r and p and why self.head is passed two times to them?
Do they get updated at the same time?
In other words, r = self.head was executed at the very beginning of the program, the value should not changed anymore, but seems like after the whole function got executed, r automatically updated, is it because self.head makes it updated? and how self.head got updated itself?
p is used as a sliding pointer, visiting each node in the list once. r is used to remember where the first node was and the first node (the start of the list) is returned with return r. However, the line r = self.head is superfluous, instead the function could just end with return self.head

Pointer in doubly linked list pointing at the wrong thing

I'm trying to convert a string into a doubly linked list. I want to be able to traverse it, find a match, and then insert another linked list at a certain position. However, I'm having trouble with my pointers. I have 'current.prev', 'current', and 'current.next' pointers that should all be pointing at the nodes in that order. Instead, my .prev and current pointers always point to the same thing. I need to be able to point at the different parts of the linked list, otherwise I won't be able to traverse back to a certain spot so I can insert a new linked list.
this is the code that creates doubly linked lists for me:
#-----------------------doubly-linked-lists-----------------------#
class NodeD:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
class DoublyLL:
def __init__(self):
self.head = None
def append(self, new_data):
new_node = NodeD(new_data)
if self.head is None:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
new_node.prev = last
return
def printList(self, node):
while node:
print(node.data, end=""),
last = node
node = node.next
this is the code that makes a string into a linked list:
#making-linked-lists-from-strings#
def dnaToLL(dnainput):
dnaLL = DoublyLL()
head = NodeD(dnainput[0])
dnaLL.head = head
current = dnaLL.head
for i in range(1, len(dnainput)):
current.prev = current
current = current.next
current.next = NodeD(dnainput[i])
return dnaLL
I've been using this to test where the pointers are pointing:
dnaLL = dnaToLL(DNA)
dnaLL = dnaLL.head
print(dnaLL.prev.data +dnaLL.data+dnaLL.next.data)
but with this input:
S1 = "neho"
DNA = "imfinehowru"
I get this output:
iim
any idea where I've gone wrong? I am assuming it's where I've tried to make strings into doubly linked lists, but any other order that I put the pointers in gives me errors.
Thank you for your help!
The problem is that whe you do current.prev = current, you're saying "the previous node is the current node" - not the actual previous node.
Keep in mind that the first node in the list should have .prev = None. You'd only want to set .prev for future nodes. This modification should fix your dnaToLL() function:
def dnaToLL(dnainput):
dnaLL = DoublyLL()
head = NodeD(dnainput[0])
dnaLL.head = head
current = dnaLL.head
for i in range(1, len(dnainput)):
# note how the order is changed
current.next = NodeD(dnainput[i])
current.next.prev = current
current = current.next
return dnaLL

Understanding a Linked List implementation in Python

I have found an implementation of a Linked List in Python, online, but it doesn't have any explanation or comments.
I understand the underlying concepts of a Linked List, but there is one key part of the code I don't understand:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def get_data(self):
return self.data
def get_next(self):
return self.next
def set_data(self, data):
self.data = data
def set_next(self, next):
self.next = next
class LinkedList:
def __init__(self):
self.head = None
def is_empty(self):
return self.head == None
def add(self, item):
temp = Node(item)
temp.set_next(self.head)
self.head = temp
def size(self):
current = self.head
count = 0
while current != None:
count += 1
current = current.get_next()
return count
def search(self, item):
current = self.head
while current != None:
if current.get_data() == item:
return True
else:
current = current.get_next()
return False
def remove(self, item):
current = self.head
previous = None
found = False
while not found:
if current.get_data() == item:
found = True
else:
previous = current
current = current.get_next()
if previous == None:
self.head = current.get_next()
else:
previous.set_next(current.get_next())
I don't understand how the size, search and remove methods in the LinkedList class are able to call functions from the Node class via the current variable, after setting it to self.head, which seems to be contained within the scope of the LinkedList class.
Is it because the add method sets self.head = temp, where temp is a Node object?
If possible, could someone explain how this works?
You stated that:
I don't understand how the size, search and remove methods in the LinkedList class are able to call functions from the Node class via the current variable, after setting it to self.head, which seems to be contained within the scope of the LinkedList class.
You can see that in the code, initializing a LinkedList performs this line of code:
self.head = None
Since the head is set to none, the size, search, and remove methods will not run through the whole code. Rather, it will stop when the self.head == None, which is pretty much in the beginning.
For example, let's take a look at the size method.
def size(self):
current = self.head
count = 0
while current != None:
count += 1
current = current.get_next()
return count
In this function, current is set to self.head which is null unless you have added any nodes by calling the add() method. More on that later.
count is set equal to 0. Then a while loop begins which only runs if the current is not None. But since the current is set to self.head which is None, the while loop will not run and the function will return count which is 0. This is a correct implementation because there are currently no nodes in the linkedlist.
Now onto how you can add nodes.
The add method:
def add(self, item):
temp = Node(item)
temp.set_next(self.head)
self.head = temp
Here, the add method takes in itself and an item. The item is an object of some sort whether it be a string, integer, float, etc. Now a variable temp is created and set to a new node which is finally using something from the Node class. Then, temp's next node is set to head and the head is set to temp. What this does is that the linked list continuously updates the head.
Like this:
(head)
NODE1
ADD ONE MORE NODE
(head)
NODE2 NODE1
And so on...
Happy Coding!

Sorted Doubly Linked List Python

I'm having trouble understanding and implementing a Doubly Linked List. I can grasp most of the concepts of a Linked List. Here is my code so far (in Python)
*This is a purely academic exercise. I would normally use list and dict.
class DoublyNode(object):
"""A node of the SortedDoublyLL object.
DoublyNode(item, next=None, previous=None) -> a new DoublyNode with data as
its data, and next and previous as its neighbors."""
def __init__(self, data, next = None, previous = None):
"""Make a new DoublyNode from item, pointing to next and previous."""
self.data = data
self.next = next
self.previous = previous
class SortedDoublyLL(object):
"""A Sorted Doubly Linked List.
SortedDoublyLL() -> new SortedDoublyLL list that is empty
SortedDoublyLL(sequence) -> a SortedDoublyLL initialized from sequence's
items.
"""
def __init__(self, sequence = []):
"""Make a new SortedDoublyLL from the elements of sequence."""
if len(sequence) == 0:
self.head = None
self.tail = None
else:
cur_node = None
prev_node = None
sequence.sort()
sequence.reverse()
for element in sequence:
prev_node = cur_node
cur_node = DoublyNode(element, cur_node, prev_node)
self.head = cur_node
self.tail = DoublyNode(sequence[0])
Change your loop to
for element in sequence:
prev_node = cur_node
cur_node = DoublyNode(element, None, prev_node)
prev_node.next = cur_node
Because the line prev_node = cur_node precedes the call DoublyNode(element, cur_node, prev_node), you end up setting both the previous and next elements to the previous element so you are ending up with a linked list that just has two links to the previous element. So you might as well just pass None as the next parameter1 and then initialize it manually on the next pass of the loop. This has the advantage of leaving it as None on the last element of the list.
1 Using the name next as a parameter in the constructor will shadow the builtin function next which advances an iterator. You could use the name next_ which is the canonical thing to do. Using next as an attribute isn't a problem because that qualifies the name so that no shadowing will occur. It will mess up in some syntax highlighters though.

Categories