I am currently taking an online computer science introductory course and have just learned the concept of a linked list. Though I understand the concept of linked lists, I still am unsure as how to deal with linked lists.
As such, I seek out help in solving the following problem, which will be of significant help for my understanding of linked lists:
Write a function (not in LinkedList class definition) that given a linked list, will change that linked list to filter out odd numbers. Immediately after the function returns, the linked list will only have even numbers.
I am unsure as to how to access the nodes in the list and check whether they are odd or even and remove or keep them accordingly.
I apologize if this seems like a trivial question, but I would appreciate any help that might help me learn.
The code for the linked list and node classes (as provided by the online course):
class Node:
def __init__(self, data=None, next_node=None):
self.data = data
self.next = next_node
def __str__(self):
return str(self.data)
class LinkedList:
def __init__(self):
self.length = 0
self.head = None
def print_list(self):
node = self.head
while node is not None:
print(node, end=' ')
node = node.next
print('')
def add_at_head(self, node):
node.next = self.head
self.head = node
self.length += 1
def remove_node_after(self, node):
if node.next is not None:
temp = node.next
node.next = node.next.next
temp.next = None
self.length -= 1
def remove_first_node(self):
if self.head is None:
return
temp = self.head
self.head = self.head.next
temp.next = None
self.length -= 1
def print_backward(self):
def print_nodes_backward(node):
if node.next is not None:
print_nodes_backward(node.next)
if node is not None:
print(node, end=' ')
if self.head is not None:
print_nodes_backward(self.head)
print('')
Let's say you have a bare-bones simple linked list that looks like this:
class LinkedList:
class ListNode:
def __init__(self, data):
self.data = data
self.next = None
def __init__(self):
self.head = None
def add(self, data):
if self.head is None:
self.head = LinkedList.ListNode(data)
else:
current_node = self.head
while current_node.next is not None:
current_node = current_node.next
current_node.next = LinkedList.ListNode(data)
def __str__(self):
ret = "["
current_node = self.head
while current_node is not None:
ret = ret + str(current_node.data)
if current_node.next is not None:
ret = ret + ", "
current_node = current_node.next
ret = ret + "]"
return ret
In other words, the LinkedList contains a single head, which is a ListNode. Every element in the Linked List is contained in a ListNode, and each ListNode points towards the next element in the list.
As you can see, for adding an element to the list, we either create a node at the head if the list is empty (i.e. self.head is None), or we traverse to the end of the list by continuously jumping to the .next element for each ListNode, starting from the head. We also use this paradigm for printing a string representation of our list.
So, to remove any node from the linked list, we can simply change the node that references it, so that the node we want to remove gets skipped. At which point it will disappear.
To remove all list nodes containing odd-numbered data, we might do something like this:
def remove_odds(self):
# special case: head node
# remove odd head elements by simply setting head to the next element after
while (self.head is not None) and (self.head.data % 2 == 1):
self.head = self.head.next
# regular case: the rest of the nodes
current_node = self.head
while (current_node is not None) and (current_node.next is not None):
# if the next node's data is odd, then
if current_node.next.data % 2 == 1:
# skip that node by pointing this node's .next to the next node's .next
current_node.next = current_node.next.next
# otherwise, move forwards in the list
else:
current_node = current_node.next
Proof of concept:
>>> lst = LinkedList()
>>> lst.add(2)
>>> lst.add(5)
>>> lst.add(6)
>>> lst.add(3)
>>> lst.add(7)
>>> lst.add(8)
>>> lst.add(10)
>>> lst.add(1)
>>> lst.add(4)
>>> print(lst)
[2, 5, 6, 3, 7, 8, 10, 1, 4]
>>> lst.remove_odds()
>>> print(lst)
[2, 6, 8, 10, 4]
Copied from comment: The idea is to iterate through the list head-to-tail while remembering the previous node; when you find a garbage node, apply remove_node_after to the remembered node, or move the head to the current node if we haven't had time to remember anything yet.
The code would be something like this (untested):
class LinkedList:
# ...
def delete_if(self, pred):
prev = None
curr = self.head
while curr:
if pred(curr.data):
if prev:
self.remove_node_after(prev)
else:
self.head = curr
prev = curr
curr = curr.next
llist.delete_if(lambda x: x % 2 == 1) # delete if odd
# Mahmoud AL-Mokdad
# this course on Udemy By SEfactoru right😁
# use my code 😉
def filter_even(ll):
first_node = ll.head
while (first_node is not None) and (first_node.data % 2 != 0):
ll.remove_first_node()
first_node = ll.head
node = first_node
while node is not None and node.next is not None:
if node.next.data % 2 != 0:
ll.remove_node_after(node)
else:
node = node.next
Related
I am trying to remove the kth element from the END of a linked list. Here is my code
class LinkedList:
def __init__(self, value):
self.value = value
self.next = None
def removeKthNodeFromEnd(head, k):
if k == 0 or head is None:
return
temp = head
while temp is not None and k > 1:
temp = temp.next
k -= 1
if temp is None or k > 1:
return head
trailing = None
leading = head
while temp.next is not None:
trailing = leading
leading = leading.next
temp = temp.next
if trailing is None:
head = head.next
else:
trailing.next = leading.next
head = LinkedList(0)
head.next = LinkedList(1)
head.next.next = LinkedList(2)
head.next.next.next = LinkedList(3)
head.next.next.next.next = LinkedList(4)
head.next.next.next.next.next = LinkedList(5)
head.next.next.next.next.next.next = LinkedList(6)
head.next.next.next.next.next.next.next = LinkedList(7)
head.next.next.next.next.next.next.next.next = LinkedList(8)
head.next.next.next.next.next.next.next.next.next = LinkedList(9)
removeKthNodeFromEnd(head, 10)
while head is not None:
print(head.value)
head = head.next
But this does not work and prints out all the values in the linked list from 0 to 9. Why is this the case? If the node to delete is the head node which I check by checking trailing is None, then I update head = head.next. I am changing head to be the next node. I can return head after this update and the result of head = removeKthNodeFromEnd(head, k) will give me the desired output but why can't I do it the other way without returning anything? For instance in my solution I can get rid of any element in between the first and last node including the last and works just fine. The original head gets updated and node gets removed. But when trying to update the head node head = head.next it does not work.
One way I achieved this is by doing the following...
if trailing is None:
head.value = head.next.value
head.next = head.next.next
But why must I use the values? To me this seems the same as
if trailing is None:
head = head.next
head.next = head.next.next
but does not work
It doesn't work because head in that function is a local name. Assigning to a local variable never does anything to other variables, even not when they happen to have the same name (like the global head).
The "trick" you have by moving a linked list value with head.value = head.next.value will not work when the list only has one node.
One way to do this, is to return the (potentially modified) value of head, and expect the caller to assign this back to their own head variable.
So:
in removeKthNodeFromEnd make sure all return statements are like return head, and that you add a return head at the very end. In short, removeKthNodeFromEnd should always return head.
in the main program, change the call to head = removeKthNodeFromEnd(head, 10)
That will solve your issue.
Here are some ideas to make your code more elegant:
Create a separate class for maintaining the head of the linked list
Improve the constructor, so that it is easier to initialise a linked list
Add an iterator so it is easier to print a linked list
class Node: # renamed
def __init__(self, value, nxt=None):
self.value = value
self.next = nxt # is now an argument
class LinkedList: # added
def __init__(self, *values):
self.head = None
if values: # populate the list with values
for value in reversed(values):
self.push_front(value)
def push_front(self, value):
self.head = Node(value, self.head)
def __iter__(self):
node = self.head
while node:
yield node.value
node = node.next
def removeKthNodeFromEnd(self, k):
if k <= 0:
return
temp = self.head
for _ in range(k):
if not temp: # k is out of range
return
temp = temp.next
if temp:
trailing = self.head
while temp.next:
trailing = trailing.next
temp = temp.next
trailing.next = trailing.next.next
else:
self.head = self.head.next
lst = LinkedList(*range(10))
print(*lst)
lst.removeKthNodeFromEnd(0)
print(*lst)
And if you maintain a size attribute, you can do:
class LinkedList: # added
def __init__(self, *values):
self.head = None
self.size = 0
if values:
for value in reversed(values):
self.push_front(value)
def push_front(self, value):
self.head = Node(value, self.head)
self.size += 1
def __iter__(self):
node = self.head
while node:
yield node.value
node = node.next
def removeAt(self, k):
if 1 < k <= self.size:
trailing = self.head
for _ in range(k - 2):
trailing = trailing.next
trailing.next = trailing.next.next
elif 1 == k <= self.size:
self.head = self.head.next
def removeKthNodeFromEnd(self, k):
self.removeAt(self.size + 1 - k)
I am a bit new to python and I have seen the correct solutions to the reversing the linkedlist problem but I wanted to know why my solution does not work. In particular, reverse function stays inside the while loop for the code below because of "new_list.head.next=prev" line
class Node:
def __init__(self, value):
self.value = value
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, value):
if self.head is None:
self.head = Node(value)
return
node = self.head
while node.next:
node = node.next
node.next = Node(value)
def __iter__(self):
node = self.head
while node:
yield node.value
node = node.next
def __repr__(self):
return str([v for v in self])
def reverse(linked_list):
new_list = LinkedList()
if linked_list is None:
return new_list
node = linked_list.head
new_list.head = node
while node.next:
prev = node
node = node.next
new_list.head = node
new_list.head.next = prev
return new_list
if __name__ == "__main__":
a = LinkedList()
b = [1,2,3,4,5]
for item in b:
a.append(item)
print a
c = reverse(a)
print c
If you tag your question with Python3 please make sure it runs in python 3.
The reason is because you are mixing up points and creating an infinite loop. Print the value and it may help you find the bug. I am going to use the values to point out the issue.
while node.next:
# First node that comes in value = 1
print(node.value) #
prev = node # prev = 1
node = node.next # node = 2
new_list.head = node # You are setting the head = 2 (i.e. head = node.next)
new_list.head.next = prev # You are setting next of head = 1 (i.e. head.next = node.next.next)
# however this also set node.next.next = 1
# So going into the next loop: node.value = 2 and node.next.value = 1
Because of this pointer confusion you are forever looping between your first and second node.
This is how your reverse can look:
def reverse(linked_list):
new_list = LinkedList()
if linked_list is None:
return new_list
node = linked_list.head
new_list.head = Node(node.value)
while node.next:
node = node.next
prev_head = new_list.head
new_list.head = Node(node.value)
new_list.head.next = prev_head
return new_list
With it I got desired output of print c: [5, 4, 3, 2, 1]
General advise: create new Node instead of assignment to node in initial list.
It's a little easier to reason about this (at least to me) if you think about two references:
• One to the remaining part of the original list you haven't seen
• One to the head of the new list
At each iteration move the remaining up and set the old remaining to the head of the new list. Order is important here — as you've seen it's easy to accidentally change next on two different variables that are pointing the same node if you're not careful:
def reverse(linked_list):
new_list = LinkedList()
if linked_list is None:
return new_list
remaining = linked_list.head
while remaining:
prev_head = new_list.head # the old head becomes the first link
new_list.head = remaining # new head becomese the first remaining
remaining = remaining.next # move remaing one up the chain
new_list.head.next = prev_head # point the new head to the previous
return new_list
I'm a Noob, struggling to understand and implement a singly linked list, that adds items at the tail. I believe the only code that is not working is the add function, which I can't figure out the logic for. I believe I want to set the first node to be the head, and then insert each other element at the tail, changing the pointer for head to point to the 2nd item when adding it, then the pointer for the 2nd item to point to the third etc., but can't figure out how to go about coding that (to deal with an unknown number of strings, here 3 for simplicity.
strings = ["one", "two", "three"]
class Node:
def __init__(self,data,nextNode=None):
# populate the Node, with data and pointer
self.data = data
self.nextNode = nextNode
def getData(self):
# method to get value of this node
return self.data
def setData(self,val):
# set value of node to val
self.data = val
def getNextNode(self):
# get the pointer to the next node
return self.nextNode
def setNextNode(self,val):
# set pointer to the next node
self.nextNode = val
class LinkedList:
def __init__(self, head = None, tail = None):
# initial properties of linked list, size 0
self.head = head
self.tail = tail
self.size = 0
def getSize(self):
# get size of linked list
return self.size
def addNode(self,data):
# Head should point to first node, which will have a value, and a Null pointer
if (self.size == 0):
newNode = Node(data, self.tail)
self.head.getNextNode() = newNode
else:
# All additional nodes should be inserted at tail, and with the pointers for the prior nodes changed to point to the new node
newNode = Node(data, self.tail)
self.tail = newNode
self.size += 1
return True
def printNode(self):
curr = self.head
while curr:
print(curr.data)#, curr.nextNode)
curr = curr.getNextNode()
mylist = LinkedList()
for i in strings:
mylist.addNode(i)
mylist.printNode()
# desired output: Head -> one --> two --> three/Tail
There were many little mistakes, please find them in code below. And let me know if you don't understand something.
One important change is a new node shouldn't have access to its next node. Its already the last node, so there can't be any node next to it. Also please pay close attention to else block of addNode function.
strings = ["one", "two", "three","four","five"]
class Node:
def __init__(self,data):
# populate the Node, with data and pointer
self.data = data
self.nextNode = None
def getData(self):
# method to get value of this node
return self.data
def setData(self,val):
# set value of node to val
self.data = val
def getNextNode(self):
# get the pointer to the next node
return self.nextNode
def setNextNode(self,val):
# set pointer to the next node
self.nextNode = val
class LinkedList:
def __init__(self, head = None, tail = None):
# initial properties of linked list, size 0
self.head = head
self.tail = tail
self.size = 0
def getSize(self):
# get size of linked list
return self.size
def addNode(self,data):
# Head should point to first node, which will have a value, and a Null pointer
if (self.size == 0):
self.head = Node(data)
self.tail = self.head
self.size = 1
else:
# All additional nodes should be inserted at tail, and with the pointers for the prior nodes changed to point to the new node
newNode = Node(data)
self.tail.nextNode = newNode
self.tail = newNode
self.size += 1
return True
def printNode(self):
curr = self.head
while curr:
print(curr.data)#, curr.nextNode)
curr = curr.getNextNode()
mylist = LinkedList()
for i in strings:
mylist.addNode(i)
mylist.printNode()
Basically, I'm implementing a LinkedList class and then implementing various methods to use it. Below is the code for the LinkedList class(and its dependent Node class)
# A simple linked list implementation
class Node:
# attributes:
# data (can be anything)
# next (another Node)
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
# attributes:
# head (a Node)
# ****************
# methods:
# insert
# find
# delete
def __init__(self):
self.head = None
def __str__(self):
output = []
current_node = self.head
while current_node:
output.append(str(current_node.data))
current_node = current_node.next
return(", ".join(output))
# Creates a Node with the given data and inserts into the front of the list.
def insert(self, data):
new_node = Node(data)
new_node.next = self.head
self.head = new_node
# Finds the first node with the given data. Returns None if there is no such node.
def find(self, data):
current_node = self.head
while(current_node):
if (current_node.data == data):
return current_node
current_node = current_node.next
return None # We reached the end of the list without finding anything.
# Deletes given node. Can be used in conjunction with find.
def delete(self, deleted_node):
if deleted_node == self.head:
self.head = self.head.next
return
current_node = self.head
while(current_node.next != deleted_node):
current_node = current_node.next
current_node.next = deleted_node.next
Then, I'm trying to implement a rotate(my_list, N) function, which will, well, rotate my_list by N. The following is my code:
import linkedlists as ll
from ErrorHandler import sanitize
import random, math, time, copy
def length(my_list):
sanitize(my_list, ll.LinkedList)
if my_list.head == None: return 0
count = 1 #First item! Ah, ah, ah
current_node = my_list.head
while current_node.next != None:
current_node = current_node.next
count += 1 #One more item! Ah, ah, ah
return count
def get_Nth(my_list, N):
sanitize(my_list, ll.LinkedList)
if my_list.head == None: return None
current_node = my_list.head
count = 0
while current_node.next != None:
if count == N:
return current_node
count +=1
current_node = current_node.next
return current_node
def rotate(my_list, N):
sanitize(my_list, ll.LinkedList)
if my_list.head == None: return None
N = N % length(my_list)
lent = length(my_list)
get_Nth(my_list, lent-1).next = my_list.head
my_list.head = get_Nth(my_list, lent-N)
get_Nth(my_list, lent-N-1).next = None
However, calling rotate() on a LinkedList containing the numbers from 0 to 9 in ascending order returns 8,9,0,1,2,3,4,5. Why? I'm pretty sure it has to do with the third- and second-to-last lines, because when assigning get_Nth(my_list, lent-1).next to my_list.head, it only points to my_list.head, instead of the Node object my_list.head points to at the time.
How can I fix this?
Your error is right here: get_Nth(my_list, lent-N-1).next = None
I'm assuming you called rotate(my_list, 2) so at this point, the list looks like this [8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, ...]. So when you call get_Nth(my_list, lent-N-1), lent-N-1 is 7, and well, the element at index 7 is actually 5.
You should just use lent-1.
I'm reading Cracking the Coding Interview and doing practice problems and I'm stuck on this one:
"Implement an algorithm to delete a node in the middle (i.e., any node but the first and the last node, not necessarily the exact middle) or a singly linked list, given only access to that node.
EXAMPLE
Input: the node from the linked list a->b->c->d->e->f
Result: nothing is returned, but the new linked list looks like a->b->d->e->f"
Here's my code :
class Node:
def __init__(self, data = None, nextnode = None):
self.data = data
self.nextnode = nextnode
def __str__(self):
return str(self.data)
class LinkedList():
def __init__(self, head = None):
self.head = head
def insert(self, data):
new_node = Node(data)
new_node.nextnode = self.head
self.head = new_node
def remove(self, data):
current = self.head
absent = True
if current == None: print('List is empty')
if current.data == data:
self.head = current.nextnode
absent = False
while current.nextnode:
if current.nextnode.data == data:
absent = False
if current.nextnode.nextnode:
current.nextnode = current.nextnode.nextnode
else: current.nextnode = None
else: current = current.nextnode
if absent: print('Element not in list')
def size(self):
current = self.head
size = 0
while current:
current = current.nextnode
size += 1
return size
def find(self, data):
current = self.head
if current == None: print('List is empty')
search = True
while current and search:
if current.data == data:
print(current)
search = False
current = current.nextnode
if search: print('Not found')
def print_list(self):
current = self.head
while current:
print(current, end = ' ')
current = current.nextnode
print('')
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node1.nextnode = node2
node2.nextnode = node3
node3.nextnode = node4
list1 = LinkedList(node1)
list1.insert(2 ****EDITED node2 to 2 here****)
print_list(list1)
def delmid(ll, n):
current = ll.head
if current == n:
print('Can\'t delete first node')
return
while current.nextnode:
if current.nextnode == n:
if current.nextnode.nextnode:
current.nextnode = current.nextnode.nextnode
return
else:
print('Can\'t delete last node')
return
delmid(list1, node2)
print_list(list1)
I can't figure out why it doesn't seem to think that ll.head and node2 are the same ... It does work if I get rid of the line list1.insert(node2) ...
I don't understand ...
EDIT: after reading the first sentence of the solution in the book, apparently i did it wrong anyways .... "given only access to that node" means you don't know the head of the list ... back to the drawing board ...
Because your insert method is wrong:
def insert(self, data):
new_node = Node(data)
new_node.nextnode = self.head
self.head = new_node
Your method does not insert node2 itself as a node: it creates a new node with node2 as payload (data). That is something different.
You can define a method:
def insert_node(self, node):
node.nextnode = self.head
self.head = new_node
Nevertheless this will create a loop since now node1 will be pointing to node2 and node2tonode1`. So the resulting linked list will be a rounded list with two elements, like:
node1 --> node2
^---------/
EDIT: since you solved that one. There is also a problem with your delmid method.
The main problem is that in your while loop you need to walk through the linked list, and you do not do that: current always remains the same, so:
def delmid(ll, n):
current = ll.head
if current == n:
print('Can\'t delete first node')
return
while current.nextnode:
if current.nextnode == n:
if current.nextnode.nextnode:
current.nextnode = current.nextnode.nextnode
return
else:
print('Can\'t delete last node')
return
current = current.nextnode
Should fix that.
Insert operation
You misunderstood your own insert-implementation.
list1.insert(node2) inserts a new node with node2 as content:
def insert(self, data):
new_node = Node(data) # <== wrap node2 into another node instance
new_node.nextnode = self.head
self.head = new_node
Comparison of Nodes
The ==-operator internally works by calling the method __eq__(self, other). In your case you didn't provide and implementation for this method, so the default is used to compare by all variables, which includes nextnode. Thus two nodes can only be equal, if they are precisely the same. To get this corrected, use a custom comparison method in Node:
def __eq__(self, other):
return type(other) is Node && other.data == self.data
This __eq__-method would work by first checking that other definitely is of type Node and afterwards compare by the data stored in each instance.
Delmid
Going a bit further than the actual question:
while current.nextnode:
if current.nextnode == n:
if current.nextnode.nextnode:
current.nextnode = current.nextnode.nextnode
return
else:
print('Can\'t delete last node')
return
This loop will run infinitely, unless the list has at most a size of 1. To fix this step through the list by current = current.nextnode.
Improving Delmid
The actual purpose of this task was to get you used to another way of manipulating linked lists: swapping.
Instead of searching the entire list for the predecessor of n, you could just check that n is neither the first nor the last node, swap out the value with the value of it's consecutive node and delete the consecutive node:
def delmid(ll, n):
if ll.head == n:
print('Can\'t delete first node')
return
if n.nextnode is None:
print('Can\'t delete last node')
return
# swap values
tmp = n.data
n.data = n.nextnode.data
n.nextnode.data = tmp
# remove node
n.nextnode = n.nextnode.nextnode