Python 3 Recursive LinkedList Node Insertion - python

I'm trying to create a recursive function to insert into a linked list at a certain position to solve a hackerrank challenge, as part of educating myself in python. However, (according to hackerrank) it seems to be incorrect. Could someone help explain where I might be going wrong?
"""
Insert Node at a specific position in a linked list
head input could be None as well for empty list
Node is defined as
class Node(object):
def __init__(self, data=None, next_node=None):
self.data = data
self.next = next_node
return back the head of the linked list in the below method.
"""
def InsertNth(head, data, position):
# if we've found our position, then insert Node with pointer to next/None
if head == None or position == 0 :
return Node(data, head)
# if we're at the next to last position, then update it's pointer to our new Node
elif position == 1 :
head.next = Node(data, head.next)
# otherwise, step position down, and perform recursion
else :
InsertNth(head.next, data, position-1)
I've put in comments to try to explain what I'm understanding to be my logic so if anything's not clear, please let me know.

Posted a solution that was found acceptable by hackerrank:
def InsertNth(head, data, position):
# if we have found our position, then create a new Node
if head == None or position == 0 :
return Node(data, head)
else :
next_node = head
while position > 1 :
next_node = next_node.next
position -= 1
next_node.next = Node(data, next_node.next)
return head

Here is my implementation of it. Hope it helps. Feel free to ask questions if there's anything you're unsure about.
def InsertNth(head: Optional[Node], position: int, data: Optional[Node]) -> Optional[Node]:
"""Returns a new list where the second list is spliced into, or inserted at, given an index."""
if (position > 0):
if (head is not None):
return Node(head.data, splice(head.next, position - 1, data))
else:
if (data is not None):
if (data.next is not None):
return Node(data.data, splice(head, position, data.next))
else:
return Node(data.data, head)
else:
return None
else:
# If position <= 0, splice data list at start of head list
# This is the same process whether the position < 0 from the start or becomes < 0.
if (data is not None):
if (data.next is not None):
return Node(data.data, splice(head, position - 1, data.next))
else:
return Node(data.data, head)
else:
if (head is not None):
return head
else:
return None

Since you asked for recursive solution. Here is my implementation which got accepted.
def insertNodeAtPosition(llist, data, position):
# Write your code here
if(llist==None or position<0):
return llist
if(position==0):
node = SinglyLinkedListNode(data)
node.next = llist
return node
llist.next = insertNodeAtPosition(llist.next,data,position-1)
return llist

Related

When a method has no return statement, how do I print out it's return value?

EDITED.
I am learning about Linked Lists. For each process applied by a Method, it is printed out to the console. So, adding, removing, searching (i.e, displaying the result of a search), are all streamed to stdout, but I cannot seem to do this for the insertion Method even though the insert Method is executed.
Some Methods have a return statement, while others rely on the __repr__() for conversion to string, to then be streamed to the console. The insertion Method (not mine, but a course worked example) takes two arguments and does not have a return statement. The most consistent error message I get when attempting to print is TypeError: %d format: a real number is required, not NoneType, or TypeError: not enough arguments for format string, where I have replaced %d with %s.
What I do not understand is, why I am unable to display test data for the insert Method, while I can do so for all others.
The code,
#!/usr/bin/env python3
class Node:
data = None
next_node = None
def __init__(self, data):
self.data = data
def __repr__(self):
return "<Node data: {}>".format(self.data)
# Linked List
class LinkedList:
def __init__(self):
self.head = None
def is_empty(self):
return self.head == None # corrected
def size(self):
current = self.head
count = 0
while current:
count += 1
current = current.next_node
return count
# Adding a node
def add(self, data):
new_node = Node(data)
new_node.next_node = self.head
self.head = new_node
# Searching the List
def search(self, key):
current = self.head
while current:
if current.data == key:
return current
else:
current = current.next_node
return None
# Inserting into the List
def insert(self, data, index):
if index == 0:
self.add(data)
if index > 0:
new_data = Node(data)
position = index
current = self.head
while position > 1:
current = current.next_node
position -= 1
past_node = current
future_node = current.next_node
past_node.next_node = new_data
new_data = current.next_node
# Removing a node from the List
def remove(self, key):
current = self.head
previous = None
found = False
while current and not found:
if current.data == key and current == self.head:
found = True
self.head = current.next_node
elif current.data == key:
found = True
previous.next_node = current.next_node
return current
def __repr__(self):
nodes = []
current = self.head
while current:
if current is self.head:
nodes.append("[Head: {}]".format(current.data))
elif current.next_node is None:
nodes.append("[Tail {}]".format(current.data))
else:
nodes.append("[{}]".format(current.data))
current = current.next_node
return '-> '.join(nodes)
Test output;
l = LinkedList()
l.add(1)
l.add(2)
l.add(3)
l.add(5)
l.add(6)
l.add(7)
length = l.size()
print("Size of list: {}".format(length)) # Size of list: 6
print(l) # [Head: 7]-> [6]-> [5]-> [3]-> [2]-> [Tail: 1]
seek = l.search(7)
print("Found: {}".format(seek)) # Found: <Node data: 7>
between = l.insert(4, 3)
if between is not None:
print(f"Inserted {between} at index {between}")
else:
print("A problem with code") # A problem with code
gone = l.remove(1)
print("Removed: {}".format(gone)) # Removed: <Node data: 1>
# Note the insertion of '4' at index 3
print(l) # [Head: 7]-> [6]-> [5]-> [4]-> [3]-> [Tail: 2]
THIS CODE WORKS!
Other variants of the print format have been tried f"{}", .format() and even an attempt at conversion to string str() was made, with no luck. Could someone explain exactly what the problem is (though, I believe it to be a NoneType issue) and how to resolve it?
I hope my question is clearer. Thank you.
There are several issues with the code you presented, including the following:
The Node class should not define data and next_node as class attributes. They should be instance attributes. Luckily, the constructor creates an instance attribute data (hiding the class attribute), but for next_node this is not done, which makes your linked list unusable.
In line with the previous comment, you should have self.next_node = None in your constructor.
[You corrected this in an edit to your question: The method name is_empty suggests that it will return a boolean indicating whether the list is empty or not. But instead it makes the list empty. That seems wrong.]
[You corrected this in an edit to your question: insert can call a method add which is not defined.]
In insert, when index is 0, the code will still continue after the first if and reference a variable new_data that has not been defined (since the second if condition was not true). You should avoid that any of the other code is executed when index is 0. You can do this with a return.
In insert, in the while loop there is no verification whether current is None. If that happens, current = current.next_node will raise an error.
new_data = current.next_node is useless and leaves the next_node attribute of next_node uninitialised.
Not an issue, but in remove, the found name is not very useful. Just break out of the loop when the node has been found and removed. Also, avoid having the current.data == key condition executed twice for the same node.
In remove, in the loop, you never change current nor previous, and so the loop hangs.
[You corrected this in an edit to your question: In the main code, the list is empty at the moment that l.insert is called, so it is strange to pass 3 as value for the index parameter, as that index is out of range. As mentioned in a previous bullet, this will trigger an error. If you want to add a node at index 3, you'll first have to add nodes at indexes 0, 1, and 2.]
The insert method does not return anything, so capturing its return value is not going to give you anything else than None. If you really want to get some feedback from it, then do like you did for the remove method: have it return the relevant node. In that case you should also let add have a return value.
Here is some working code with the above issues addressed and more:
class Node:
def __init__(self, data):
self.data = data
self.next_node = None # next_node neads to be an instance attribute, not a class attribute
def __repr__(self):
return "<Node data: {}>".format(self.data)
class LinkedList:
def __init__(self):
self.head = None
def is_empty(self):
return self.head == None # Don't MAKE it empty!
def size(self):
current = self.head
count = 0
while current:
count += 1
current = current.next_node
return count
def add(self, data):
new_node = Node(data)
new_node.next_node = self.head
self.head = new_node
return new_node # Return the new node
def search(self, key):
current = self.head
while current:
if current.data == key:
return current
else:
current = current.next_node
return None
def insert(self, data, index):
if index == 0:
# Don't continue after this call to self.add
return self.add(data) # Return the new node
current = self.head
while index > 1 and current: # Protect against out of range index
current = current.next_node
index -= 1
if current: # Protect against out of range index
new_data = Node(data)
# Make sure the new node gets a next_node assignment
new_data.next_node = current.next_node
current.next_node = new_data
return new_data # Return the new node
def remove(self, key):
current = self.head
previous = None
while current:
if current.data == key: # Check this only once per node
if current == self.head:
self.head = current.next_node
else:
previous.next_node = current.next_node
break # No need for variable - just exit
previous = current # Update previous
current = current.next_node # Move to next node
return current
def __repr__(self):
nodes = []
current = self.head
while current:
if current is self.head:
nodes.append("[Head: {}]".format(current.data))
elif current.next_node is None:
nodes.append("[Tail {}]".format(current.data))
else:
nodes.append("[{}]".format(current.data))
current = current.next_node
return '-> '.join(nodes)
l = LinkedList()
l.add(1)
l.add(2)
l.add(3)
l.add(5)
l.add(6)
l.add(7)
length = l.size()
print("Size of list: {}".format(length)) # Size of list: 6
print(l) # [Head: 7]-> [6]-> [5]-> [3]-> [2]-> [Tail: 1]
seek = l.search(7)
print("Found: {}".format(seek)) # Found: <Node data: 7>
node = l.insert(4, 3)
print("Inserted {}".format(node)) # Inserted: <Node data: 4>
gone = l.remove(1)
print("Removed: {}".format(gone)) # Removed: <Node data: 1>
# Note the insertion of '4' at index 3
print(l) # [Head: 7]-> [6]-> [5]-> [4]-> [3]-> [Tail: 2]
insert does not "have a problem" outputting data - just like standard Python lists, it is an in-place operation. You are modifying the list on which it is applied.
insert() does not need to return anything, as all the information you need is provided by you when calling it - you need to pass a list, you need to pass data to insert and you need to pass an index at which the element is to be placed - there is no new information to be gained from returning anything.
Related question:
Why don't list operations return the resulting list?
I think you're confusing 2 things here. The value after the return statement is what the function call is replaced with when the function is called. So for example:
def square(x):
return x*x
square(4)
here the square(4) would be replaced with 4*4. And if you don't explicitly use a return statement than a None is returned after the last command in the function/method.
Whereas repr() is a way to specifiy the string representation of that object. So for example:
class A:
pass
a = A()
print(a)
might create a cryptic output of <main.A at 0x7fbc841c9490>. So if you want it to be more descriptive you could add a repr() method:
class Point:
def __init__(self, x,y):
self.x = x
self.y = y
def __repr__(self):
return f"Coordinates of the point are x: {self.x}, y: {self.y}"
p = Point(2,4)
And instead of the cryptic default message you'd get:
Coordinates of the point are x: 2, y: 4
So the representation is how the obj is converted to a string whereas the return value is what the function call is replaced with.
print is TypeError: %d format: a real number is required, not
NoneType, or TypeError: not enough arguments for format string, where
I have replaced %d with %s.
So this creates errors because %d and %s expect numbers and strings when the return type of a method without return is None.

Sorting a linked list (swapping nodes)

I am trying to sort a LinkedList containing of such nodes:
class Node:
def __init__(self, data, next=None):
self.data, self.next = data, next
The idea is to get a sequence (list/tuple/string) from which a LinkedList is created:
def __init__(self, seq):
self.front = self.Node(None)
node = self.front
for item in seq:
node.next = self.Node(item)
node = node.next
The logic of the sort is that in each iteration you go through the linkedlist, compare each item with the first, if its smaller/greater then you perform a swap, continue going through the linked list and do the same comparation and operation. Shown here:
def min_sort(self, reverse):
self.count = 0
first = self.front
while first.next is not None:
print(self.get_list())
self.progress(first, reverse)
self.count += 1
first = first.next
def progress(self, first, reverse):
node = first
if (node is None): return
while True:
node = node.next
if node is None or node.next is None: break
if (reverse):
if (first.next.data < node.next.data):
self.vymen(first, node, node.next)
else:
if (first.next.data > node.next.data):
self.vymen(first, node, node.next)
The issue is with the swap itself (function self.vymen(...)), where with basic LinkedList of numbers it usually works, however when loaded with a string e.g. "I code in Python", the spaces dont get swapped to the correct position making the whole result messed up.
The swap itself is:
def vymen(self, first, prev, swap):
prevX, currX, prevY, currY = first, first.next, prev, swap
if (self.front.next.data == swap.data): return
if (currX is None or currY is None): return
if (currX != prevY):
if (prevX is not None): prevX.next = currY
else: self.front = currY
if (prevY is not None): prevY.next = currX
else: self.front = currX
temp = currX.next
currX.next = currY.next
currY.next = temp
else:
temp = currY.next
currX.next = temp
currY.next = currX
prevX.next = currY
Its really just an issue with the spaces as I can see and I dont understand whats the issue, since it swaps the first space on the beginning but not the rest. I would be thankful if someone would be able to point out a mistake or give me an idea whats happening :)
There are two issues in your code:
The following statement in vymen is the cause of trouble:
if (self.front.next.data == swap.data): return
Its intention is to avoid an unnecessary swap when the data of the swapped nodes are equal. But then it should not reference self.front.next, but curX -- as that is the node that is to be swapped:
if currX.data == currY.data:
return
The if condition that follows after it, is not useful at this point:
if (currX is None or currY is None): return
...as we already referenced swap.data (i.e. currY.data) at this point. So if this really were possible, it should be checked before referencing currY.data or currX.data. So combine it with the previous if:
if currX is None or currY is None or currX.data == currY.data:
return
The main loop in progress will skip a node when two consecutive(!) nodes are swapped. In that case, before the call to vymen, node will become currX that will hop over node.next. And so after the call, node will still reference the same node, but its position in the list is one further. By consequence, there is a node that will not haven an iteration of the loop. This is a cause of getting wrong outputs, even with plain numbers. To solve this, you could adapt vymen so it will return the node that will be at the same position as node was at the start of the call. The caller should then re-assign that reference to its node variable.
Some other remarks:
In Python it is not needed to put parentheses around if conditions
In vymen, the following condition should always be true:
if (prevX is not None): prevX.next = currY
else: self.front = currY
This should never be true, because you have designed your linked list to have a sentinel node at its front (having value None). So you don't even want self.front = currY to ever execute! prevX should never be None. It never is None in the way you call this function either. So just remove this construct and just assign:
prevX.next = currY
The same should happen with the if..else that follows it.
The vymen shoudn't need its last argument (swap), as it should always be the value of prev.next. So the function could start like this:
def vymen(self, first, prev):
prevX, currX, prevY, currY = first, first.next, prev, prev.next
And the call would be (note also the assignment of the return value):
node = self.vymen(first, node)
It is a pity that right after the function header of vymen, you define new variables for exactly the same as first and prev: prevX and prevY. Why not name the parameter variables like that immediately?
It is a pity that in progress you have an if with a break as the first statement in the while True body. You can better rewrite this so to have the condition in the while statement
Taking it all together, this is the resulting code for the two methods that had problems:
def progress(self, first, reverse):
if first is None or first.next is None:
return
node = first.next
while node.next: # Use a condition to exit instead of break
if reverse:
if first.next.data < node.next.data:
# Take the return value, and pass one argument less
node = self.vymen(first, node)
else:
if first.next.data > node.next.data:
# Take the return value, and pass one argument less
node = self.vymen(first, node)
node = node.next
def vymen(self, prevX, prevY): # Use the desired variable names
# No need for the swap argument
currX, currY = prevX.next, prevY.next
# Corrected comparison of the two nodes' data, and put the
# conditions in the right order:
if currX is None or currY is None or currX.data == currY.data:
return prev # return the node at the position of prev
if currX != prevY:
# Make the following assignments unconditionally
prevX.next = currY
prevY.next = currX
temp = currX.next
currX.next = currY.next
currY.next = temp
# Return the reference of the node that is in the position
# of prev: it is still in the same position:
return prev
else:
temp = currY.next
currX.next = temp
currY.next = currX
prevX.next = currY
# Return the reference of the node that is in the position
# of prev: it has moved, so now it is...:
return currY

Inserting and getting the position in a Linked List class using python

I am new to Data Structure and algorithms. I'm a self-taught Python programmer. I am doing a course on it and I wanted to make a linked list, get a specific position in the linked list, insert, and delete an element in the list.
So I wrote my code, and to me, it seems fine. It's not giving me any errors, but it's not executing as well.
This is the code that I wrote,
class Element(object):
def __init__(self, value):
self.value = value
self.next = None
class LinkedList(object):
def __init__(self, head=None):
self.head = head
def append(self, new_element):
current = self.head
if self.head:
while current.next:
current = current.next
current.next = new_element
else:
self.head = new_element
def get_position(self, position):
"""Get an element from a particular position.
Assume the first position is "1".
Return "None" if position is not in the list."""
current = self.head
if self.head:
while current.next:
if current == position:
return current
else:
continue
else:
return None
def insert(self, new_element, position):
"""Insert a new node at the given position.
Assume the first position is "1".
Inserting at position 3 means between
the 2nd and 3rd elements."""
current = self.head
if self.head:
while current.next:
if current.next == position:
current.next = new_element
break
else:
continue
else:
self.head = new_element
The error is in get position function and insert function
can anyone tell me what is it that I'm doing wrong?
Is there an issue with the loop or something?
Please help me, I'll be grateful!! Thanks.
Some issues in get_position:
current == position is not the condition you need to verify. position is a number and current is an Element, so they don't really compare. You need to verify whether the position is 1 or 2, ... depending on how far you are in your list.
The loop never advances current to the next node. So this represents an infinite loop.
The while condition should not check current.next, but current. Otherwise you will never do a check for the last node in the list.
Here is your code corrected:
def get_position(self, position):
if position < 1: # Just in case the position is too small
return
current = self.head
while current and position > 1:
position -= 1
current = current.next
return current
So the loop ends whenever position gets to be decreased to 1, or there is no more node. In the latter case the return value will be None.
Although your question is about the get_position function, your insert also has the same problems. On top of that it also treats the head case wrong. It should also change the head when the provided position is 1.
The insert method can in fact make use of the above function to find the node that should precede the node to be inserted:
def insert(self, new_element, position):
if position == 1:
new_element.next = self.head
self.head = new_element
else:
before = self.get_position(position-1)
if before is None:
raise ValueError("invalid position")
new_element.next = before.next
before.next = new_element

Pattern for multiple while loop exit conditions in Python

I spent some time looking through similarly named questions on here, but none seem to address the question of what to do if you have multiple exit conditions, each with their own logical path after exit. Here's what I mean.
I got started thinking about this while writing a function to add an element to a unique-element linked list:
class Node:
def __init__(self, key, next=None, count=1):
self.key = key # Unique key corresponding to this node
self.next = next # Reference to next node in linked list
self.count = count # Occurrences of key above in data
node = get_prepopulated_list()
# Iterate over linked list until element is found or list is exhausted
while node.key != key and node.next is not None:
node = node.next
# If found element, increment counter for that element
if node.key == key:
node.count += 1
# Else, add new node to end
else:
node.next = Node(key)
Notice how at least one of the two exit conditions is checked again after exit, leading to code duplication. Sure, we could flip which condition is being checked, but still there will remain code duplication.
In this case, we could use something like this, which might be a bit cleaner:
node = get_prepopulated_list()
# Iterate over linked list until element is found or list is exhausted
while node.key != key and node.next is not None:
try:
node = node.next
# Break out on 'None' condition
except AttributeError:
break
# Use implicit 'None' check from try-except to add new node to end on None
else:
node.next = Node(key)
return
# Otherwise, found element, so increment counter for that element
node.count += 1
But this isn't really any better, first because it adds a required escape during the else of the while-else, second because it adds some bloated error handling inside the loop, and finally because it breaks rule 2 of the Zen of Python: "Explicit is better than implicit". Beyond that, it's also not extensible to more than 2 exit conditions.
So I have two questions:
Is there a better way of handling such a case with 2 exit conditions and matching logical paths?
Is there a better way of handling the case with 3 or more exit conditions and matching logical paths?
while node.next is not None:
if node.key != key:
node = node.next
else:
node.count += 1
return
node.next = Node(key)
Note: will work only if get_prepopulated_list() is not None
Minimal, Reproducible Example
class Node:
def __init__(self,key):
self.key = key
self.next = None
self.count = 1
class LinkedList:
def __init__(self):
self.head = None
def add(self,key):
if self.head is None:
self.head = Node(key)
else:
node = self.head
while node.next is not None:
if node.key != key:
node = node.next
else:
node.count += 1
return
node.next = Node(key)
def show(self):
node = self.head
while node is not None:
print(node.key,'-->',node.count)
node = node.next
ll = LinkedList()
ll.add(5)
ll.add(6)
ll.add(2)
ll.add(5)
ll.add(7)
ll.show()

How to delete a given node from a linked list using Python

I am trying to learn linked list in Using python,
Could someone please guide me how to delete a particular give node from a linked list?
#!/usr/bin/python
class Node(object):
def __init__(self, data=None, next=None):
self.data = data
self.next = next
def __str__(self):
return str(self.data)
def print_list(node):
while node:
print node,
node = node.next
print
def delete_node(node, node_to_remove):
if first_node == None:
return
pass
# way of creating linked list
def create_linked_list1(n):
linked_list = Node(1)
head = linked_list
for i in range(1, n):
head.next = Node(i)
head = head.next
return linked_list
node1 = create_linked_list1(10)
print_list(node1)
The obvious solution is this:
def delete_by_index(node, index):
for _ in range(index):
prev_node, node = node, node.next
prev_node.next = node.next
However, this won't be able to delete the first node. In order to do that, you need to make it return the new list head, which will normally be the old list head, but will instead be the old second node in the case where you deleted the head. So:
def delete_by_index(node, index):
if not index:
return node.next
head = node
for _ in range(index):
prev_node, node = node, node.next
prev_node.next = node.next
return head
It should be obvious how to simplify this, and you should do so.
Another option is to factor out the "searching" and "deleting" parts. Write an nth function (this should be easy), then you can do this:
def delete_by_index(node, index):
if not index:
return node.next
prev_node = nth(node, index-1)
prev_node.next = prev_node.next.next
return node
If you know about recursive functions, you might also want to figure out how to write either delete_by_index or nth recursively (it's one of the easiest recursive functions to write).
You may also want to trap the error caused by deleting, say, the 15th node of a 10-node list and make it something nicer. Figure out what error you would get in that case (or, if you can't figure it out, just run it and see), then try/except that, and raise an IndexError instead.
While you're at it, you may want to add a delete_by_data(node, data) function and maybe a delete_by_identity(node, child_node) for further practice.
Assume the following singly linked list with a pointer, head, to the first node
head ⇢ n0 → n1 → … → n i - 1 → n i → n i + 1 → … → n N-1 → None
def delete(i):
if i == 0: # there is no prev node, increment head
head = head.next
else:
prev = get_node(i-1) # find prev node, node[i-1]
prev.next = prev.next.next # remove node i
Because this is a singly linked list get_node(i-1) will have to start at head and increment i-1 times to find the node.
NOTE: If this was a doubly linked list, given node[i] you can find node[i-1] using node[i].prev.
v-> w-> x->y> z and if we want to delete x so that new Linked List is v -> w-> y-> z and we have access to only x
position of the next node to x i.e. position of the Node Y
next_node = x.next
swapping the Y to X and then deleting Y `x.data = next_node.data
x.next = next_data.next
Deletion of a node using python in single linked list.
def delete(self,data):
if self.head.data==data:
temp=self.head.next
del self.head
self.head=temp
else:
p=self.head
while p.next.data!=data:
p=p.next
temp=p.next.next
del p.next
p.next=temp
Delete by value:
def delete(head, val):
if head is None:
return None
prev = head
temp = head.next
while temp is not None:
if temp.val == val:
prev.next = temp.next
return val
else:
prev = temp
temp = temp.next
return None

Categories