How do I call a method from my outer class in an inner class of my custom-made linked list? - python

I'm learning python and challenging myself by writing my own linked list from scratch. I'm using a tradition structure of an inner node class which holds a piece of data and a reference to the next node. Right now I'm trying to create a __repr__ method that returns a string representation of a node. The string it returns looks like this: "This node contains {0}. The next node is {1}." .format(self.data, self.next.data)
It works fine unless there's only 1 node in the list, which gave me the following error: AttributeError: 'NoneType' object has no attribute 'data'.
To get around this, I check first to see if there's only one node in the list, and I return the following string: "This node contains {0}. There is no next node." .format(self.data)
This is what my __repr__ method looks like right now:
def __repr__(self):
if MyLinkedList.get_size() == 1:
return "This node contains {0}. There is no next node." . format(self.data)
return "This node contains {0}. The next node is {1}." .format(self.data, self.next.data)
This is what the whole linked list class looks like so far:
class MyLinkedList(object):
head = None
size = None
def __init__(self):
self.size = 0
def get_head(self):
return self.head
def get_size(self):
return self.size
def is_empty(self):
if self.size == 0:
return True
else:
return False
def __repr__(self):
result = "["
curr = self.head
while curr != None:
if curr.next == None:
result += curr.data
break
result += curr.data
result += ", "
curr = curr.next
result += "]"
return result
def add_to_head(self, data):
new_node = MyLinkedList.Node(data)
if self.size == 0:
self.head = new_node
else:
new_node.next = self.head
self.head = new_node
self.size += 1
def delete_from_head(self):
if (self.size == 0):
self.head = None
else:
new_head = self.head.next
self.head = new_head
self.size =- 1
class Node():
next = None
def __init__(self, data):
self.data = data
def get_data(self):
return self.data
def get_next(self):
return self.next
def __repr__(self):
if MyLinkedList.get_size() == 1:
return "This node contains {0}. There is no next node." . format(self.data)
return "This node contains {0}. The next node is {1}." .format(self.data, self.next.data)
But now when I try to print the string representation of any node, it gives me the following error: TypeError: get_size() missing 1 required positional argument: 'self'
Is there any way to fix this issue? All I'm trying to do is to call my outer class's get_size() method in my inner node class, and check if that value is 1. Is there any other way to make it so my node's __repr__ method returns the string I want it to return when there's only one node in the list?
Also, if you spot other improvements you could make to my code, I would be glad to hear them.

You can only invoke get_size() on an instance of your MyLinkedList class. A node shouldn't know anything about the linked list class anyway. Just take advantage of the node's next pointer instead:
def __repr__(self):
suffix = "There is not next node" if self.next is None else "The next node is {}".format(self.next.data)
return "This node contains {}. {}.".format(self.data, suffix)

I believe you need to add a self argument into the get_size() method. Such as:
def __repr__(self):
if MyLinkedList.get_size(self) == 1:
return "This node contains {0}. There is no next node." . format(self.data)
return "This node contains {0}. The next node is {1}." .format(self.data, self.next.data)

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.

Implementing an insert method on a linked list on python

Attempting to create a method for a singly linked list and struggling to understand why this test case is failing. In my second class SLinkedList, I have a method called insert, it takes the argument pos which is an integer. Now in my test case when I add middle to position 4 it stops referencing any further nodes the linked list meaning that the node containing the data middle does not have a reference to the node containing 77. I'm confused why this is happening? I've programmed it such that when current_pos==pos we set the next of our current (new_node) to be current.getNext() (77). Haven't I assigned the next of 2 to middle and the next of middle to 77?
class SLinkedListNode:
# an instance of this class is a node in a Single Linked List
# a node has a reference to data and reference to next
def __init__(self,initData,initNext):
self.data = initData
self.next = initNext
def getNext(self):
return self.next
def getData(self):
return self.data
def setData(self,newData):
self.data = newData
def setNext(self,newNext):
self.next = newNext
class SLinkedList:
# an instance of this class is a Singly-Linked List object
# it has reference to the first node in the list
def __init__(self):
self.head = None
self.size = 0
def add(self,item):
# adds an item at the start of the list
new_node = SLinkedListNode(item,None)
new_node.setNext(self.head)
self.head = new_node
self.size = self.size + 1
def append(self,item):
# adds an item at the end of the list
new_node = SLinkedListNode(item,None)
current = self.head # Start the traversal
if self.size == 0: # check if list is empty
self.add(item)
else:
while (current.getNext()!=None):
current= current.getNext() # traversing the list
current.setNext(new_node)
self.size = self.size +1
def insert(self,pos,item):
# inserts the item at pos
# pos should be a positive number (or zero) of type int
assert type(pos)==int,'Error:pos is not an integer'
assert pos>=0,'Error:pos must be positive'
current=self.head
new_node= SLinkedListNode(item,None)
if pos==0:
self.add(item)
elif pos==self.size:
self.append(item)
else:
current_pos=0
while(current.getNext()!=None):
if (pos-1)==current_pos:
print(current.getData())
current.setNext(new_node)
if pos==current_pos:
print(current.getData())
new_node.setNext(current.getNext())
current=current.getNext()
current_pos+=1
self.size+=1
# 1--> 2--->inserteditem---> 3-->4---> 5---> 6
# TO DO: write assert statement that tests if pos is int
# TO DO: write assert statement that tests that pos is not negative
# TO DO: COMPLETE THE METHOD
def remove(self,item):
# remove the node containing the item from the list
if self.size == 0:
raise Exception('List is Empty')
current = self.head
previous = None
found = False
while current != None and not found:
if current.getData() == item:
found = True
else:
previous = current
current = current.getNext()
if not found:
raise Exception('Item not in list')
else:
if previous == None: # the item is in the first node of the list
self.head = current.getNext()
else: # item is not in the first node
previous.setNext(current.getNext())
self.size = self.size -1
def index(self,item):
# finds the location of the item in the list
if self.size == 0:
raise Exception('List is empty')
position = 0
found = False
current = self.head
while current != None and not found:
if current.getData() == item:
found = True
else:
current = current.getNext()
position = position + 1
if found:
return position
else:
return 'Item not found'
def pop(self):
# removes the node from the end of the list and returns the item
if self.size == 0:
raise Exception('List is empty')
current = self.head
previous = None
while current.getNext() != None:
previous = current
current = current.getNext()
if previous == None:
self.head = None
else:
previous.setNext(None)
self.size = self.size -1
return current.getData()
def __str__(self):
# returns a string representation of the list
current = self.head
string = ''
while current != None:
string = string + str(current.getData())+'->'
current = current.getNext()
return string
def getSize(self):
return self.size
def main():
# Testing Singly-Linked List
slist = SLinkedList()
slist.add(2)
slist.add(4)
slist.add('A')
slist.append(77)
slist.append(6)
slist.append('Z')
print('Original List:', slist.getSize(), 'elements')
print(slist)
print()
slist.insert(0,'start')
print('After inserting the word start at position 0:', slist.getSize(), 'elements')
print(slist)
print()
slist.insert(7,'end')
print('After inserting the word end at position 7:', slist.getSize(), 'elements')
print(slist)
print()
slist.insert(4,'middle')
print('After inserting middle at position 4:', slist.getSize(), 'elements')
print(slist)
if __name__=="__main__":
main()
Take a look at this code snippet from your insert-method:
else:
current_pos=0
while(current.getNext()!=None):
if (pos-1)==current_pos:
print(current.getData())
current.setNext(new_node)
if pos==current_pos:
new_node.setNext(current.getNext())
current=current.getNext()
current_pos+=1
Once the first if-condition is met, you're setting your new node as the current node's next node. Bear in mind, this new node has no reference to the rest of the list. The second if statement will not execute during this iteration, so the next line to be executed is current=current.getNext(), which sets current to be your new node, still without reference to the rest of the list. Therefore, in the next iteration of the while loop current.getNext() evaluates to None and your iteration terminates right then and there, effectively removing all nodes after your new node from the list.
To fix this, remove the second if and set the new node's next node in the previous if statement. This way, you'll maintain the reference to the rest of the list.
On a side note, get- and set-methods are very unpythonic, you can access and modify these attributes directly, e.g. by using current.next = whatever. Also, a while-loop iterating over your entire list seems fairly inefficient for the insert task since you exactly know the index to insert the new node into. Also also, your insert-method will break for positions greater than the list's length, you might want to add another check for that

Why does calling a function and writing statements explicitly work differently in Python?

I need an insert to head operation for a linked list that I implemented. However, doing this operation by function call (like insertToHead) and writing the statements explicitly where I need them produces different results. I wonder which property of Python leads to that difference but I couldn't figure it out.
To be more specific, let's say that I have the following class for the linked list:
class Node:
value = None
nextNode = None
def __init__(self, value):
self.value = value
def insertToHead(self, value):
newHead = Node(value)
newHead.nextNode = self
return newHead
For a linked list with a single element (say, 2) I want to insert a node (say, 0) to the head to make linked list 0 -> 2.
I created the linked list the following way
head = Node(2)
Then I tried to insert 0 to head two ways:
Writing the statements explicitly where I need them
newHead = Node(0)
newHead.next = head
head = newHead
Now head is 0, not 0 -> 2.
Calling insertToHead
head = head.insertToHead(0)
head is 0 -> 2 after this statement.
Does anyone know why these two approaches result in differently?
You have a typo. newHead.next should be newHead.nextNode.
A simple implementation of Singly Linked Lists:
class Node:
def __init__(self, value = None, nextNode = None):
self.value = value
self.nextNode = nextNode
class LinkedList:
def __init__(self):
self.head = None # will point to the head of the list
self.tail = None # will point to the tail of the list
self.size = 0 # size of the linked list
def insert_to_head(self, data):
# when push front, the head of the linked list will be Node()
self.head = Node(data, self.head)
if self.tail == None: # if tail is None, means it is a single element
self.tail = self.head
self.size += 1 # increase size by one
def __str__(self):
ret_str = ""
node = self.head
while node != None:
ret_str += str(node.value) + " -> "
node = node.nextNode
return ret_str
myLinkedList = LinkedList()
myLinkedList.insert_to_head(3)
myLinkedList.insert_to_head(2)
print(myLinkedList)

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!

Python - Linked List node comparision requires additional dereferencing

I am using Python 3.6.3. I am trying to write a simple Linked List in Python. Here is my code:
class Node(object):
"""Represents a node within a linked list"""
def __init__(self, data, next=None):
self.stuff = data
self.next = next
def __str__(self):
return str(self.stuff)
class LinkedList(object):
def __init__(self):
self.head = None
self.size=0
def append(self, data):
if not self.head:
self.head = Node(data)
return
else:
n=self.head
while n.next:
n = n.next
new_node = Node(data)
n.next = new_node
return
def insertAfter(self, data, newNode):
if not self.head:
return
else:
n=self.head
while n and n.stuff != data:
n = n.next
if not n:
return
else:
newNode.next = n.next
n.next = newNode
return
def printlist(self):
if not self.head:
print("List is empty")
return
else:
n = self.head
while n:
print(str(n))
n = n.next
return
ll = LinkedList()
ll.append(Node("1"))
ll.append(Node("2"))
ll.append(Node("3"))
ll.insertAfter("2", Node("2.5"))
ll.printlist()
I expect it to print:
1
2
2.5
3
But, instead it prints:
1
2
3
After debugging, I realized that changing this line in the insertAfter method:
while n and n.stuff != data:
to:
while n and n.stuff.stuff != data:
prints the expected output.
I don't understand why it is doing this.
Please help.
Thank you
The problem is you are not adding 1, 2, 3 to the LinkedList and letting it create a Node to wrap each. You are adding a Node whose stuff variable is 1, 2, and 3. When you call the append method to add each Node, they get wrapped in another Node by the append method. Therefore you need to call Node.stuff.stuff to access the actual element that is stored.
Look at what your append method is actually doing. It accepts some parameter data and then creates a Node with either the line self.head = Node(data) or the line new_node = Node(data), depending on if the LinkedList already has a head Node or not.
Change ll.append(Node("1")) to just ll.append("1"). Alternatively, change your append method to assume it is being passed a Node object. The first solution is much more preferable as the Node class has little use outside the context of the LinkedList class and the LinkedList doesn't work unless it is populated with Nodes anyways; it seems like extra work to make the user of the LinkedList class have to create a Node every single time.
EDIT: Also what is the reason you are passing in numbers as strings? You don't need to put 1, 2, 2.5, and 3 in quotes unless you specifically want them to be strings- but if so, why?

Categories