Python3: How can I control behavior of user defined class? - python

There is a sorting function as such:
def iterative_insertion_sort(A):
'''Sort A iteratively.'''
for i, key in enumerate(A[1:]):
while i > -1 and A[i] > key:
print(id(key), id(A[i+1]))
A[i + 1] = A[i]
print(id(key), id(A[i+1]))
i = i - 1
A[i + 1] = key
The sorting function is working fine with floats.
Sample output: ./insertion_sort.py .23 .21 .26
140566861513304 140566861513304
140566861513304 140566861513256
[0.21, 0.23, 0.26]
But I have my custom class called lList which have link(another custom class) type of elements. When I input instances of lList, the sort doesn't work correctly.
Sample output: 0.23 0.21 0.26 /
139732300992808 139732300992808
139732300992808 139732300992808
0.23 0.23 0.26 /
As we can see the id of key and the array element is different after the assignment statement in case of float.
But in case of the custom class even after the assignment operation, the id of key and array element is same. This is the cause of trouble.
I suspect the problem is because of the fact that floats are immutable, whereas my custom class isn't. My question is what is the best way to tackle the situation?
Note: I would prefer zero to minimal changes in my sorting procedure. However, I am willing to customize my lList or link class.
P.S. I have just posted only the relevant pieces of code. If the class definition is also needed, just mention it, I will add that too.
Many Thanks!
Update:
link definition:
class link:
'''Implement link.'''
def __init__(self, key=None):
self.key = key
self.nxt = None
self.prev = None
def __str__(self):
'''Print link.'''
if self:
return str(self.key)
else:
return '/'
def __gt__(self, other):
return self.key > other.key
This is the lList definition:
class lList:
'''Implement list of links.'''
def __init__(self):
self._head = None
self._numberOfLinks = 0
def list_insert(self, x):
'''Insert link x at the beginning of list.'''
x.nxt = self._head
if self._head:
self._head.prev = x
self._head = x
self._numberOfLinks += 1
def __str__(self):
'''Print list of links.'''
listFormat = ''
temp = self._head
while temp:
listFormat += str(temp) + ' '
temp = temp.nxt
else:
listFormat += '/ '
return listFormat
def get_data(self, position):
'''Return link from list at position position from head.'''
i = 0
temp = self._head
while i < position:
temp = temp.nxt
i += 1
return temp
def set_data(self, position, newLink):
'''Overwrite key of link at position distance from head of list with key of newLink.'''
i = 0
temp = self._head
while i < position:
temp = temp.nxt
i += 1
temp.key = newLink.key
def __getitem__(self, position):
if type(position) is slice:
return [self[i] for i in range(*position.indices(len(self)))]
elif type(position) is int:
if position < 0:
position += len(self)
if position >= len(self):
raise IndexError("The index (%d) is out of range."%position)
return self.get_data(position)
else:
raise TypeError("Invalid argument type.")
def __setitem__(self, position, value):
self.set_data(position, value)
def __len__(self):
return self._numberOfLinks
And this is the mimimal code that creates the same scene:
test = lList()
l = link(.26)
test.list_insert(l)
l = link(.21)
test.list_insert(l)
l = link(.23)
test.list_insert(l)
print(test)
iterative_insertion_sort(test)
print(test)

Yes, the problem is less one of immutability and more one of shared references.
When sorting floating point values, you store a reference to the float stored in A[1] in key. You then alter the reference in A[1] with the value from A[0]. That means that A[1] now points to a different object, and later on setting A[0] to key is fine.
But when sorting your linked list, you never alter A[1]. You alter an attribute of A[1]. key and A[1] continue to point to the same link instance, and setting A[1].key is visible in key.key too.
You can fix this by actually replacing the whole link object by a new one:
def set_data(self, position, newLink):
'''Overwrite key of link at position distance from head of list with key of newLink.'''
i = 0
temp = self._head
while i < position:
temp = temp.nxt
i += 1
newlink = link(newLink.key)
if temp.prev:
temp.prev.nxt = newlink
else:
self._head = newlink
newlink.nxt = temp.nxt
This makes it equivalent to setting a new value in a Python list object:
(4302695168, 4302695168)
(4302695168, 4303149248)
0.21 0.23 0.26 /

Related

Python Linked List Merge Sort not working

I have this assignment for uni that I have been working on for some time now, but I can't seem to figure out what's wrong. The goal is to merge two already-sorted linked lists into one sorted linked list.
The only function that I am allowed to change is the merge(a,b) function. I triple-checked, but I keep getting the following error:
in __repr__
out += str(node.weight)
AttributeError: 'ItemList' object has no attribute 'weight'
Is there anyone that can figure out what I should change? I'm very lost.
My code:
from __future__ import annotations
from typing import Optional
from dataclasses import dataclass
#dataclass
class Node:
weight: float
link: Optional[Node] = None
def merge(a: ItemList, b: ItemList) -> c:
"""
This function takes two linked lists, assumed sorted, and merges them
in-place, without creating new Nodes, just by changing the links.
"""
# create a new ItemList and dummynode as head:
c = ItemList()
c.head = Node(0)
# List1 is empty then return b
if a.head is None:
c._length += len(b)
b._length -= len(b)
return b
# if List2 is empty then return a
if b.head is None:
c._length += len(a)
a._length -= len(a)
return a
# If the weight of the element of a is smaller or equal to b's weight
if a.head.weight <= b.head.weight:
# We assign the weight of a's head to the head of c
c.head.weight = a.head.weight
#Give list a a new head (depletion) and subtract one from the length of a
a.head = a.head.link
a._length -= 1
# We will go through the algorithm again (recursively) to check for the element next-in-line.
c.head.link = merge(a, b)
# If the weight of the element of a is smaller or equal to b's weight
else:
# We assign the weight of a's head to the head of c
c.head.weight = b.head.weight
#Give list b a new head (depletion) and subtract one from the length of b
b.head = b.head.link
b._length -= 1
# We will go through the algorithm again (recursively) to check for the element next-in-line.
c.head.link = merge(a, b)
# return the merged list
return c
class ItemList:
head: Optional[Node]
def __init__(self):
"""Initialize an empty linked list for warehouse items."""
self.head = None
self._length = 0
def __len__(self) -> int:
return self._length
def insert(self, val: int) -> ItemList:
"""Insert a new item with given weight, at the beginning of the list"""
new_node = Node(weight=val, link=self.head)
self.head = new_node
self._length += 1
return self
def __repr__(self):
"""This function prints the list in a nice way."""
node = self.head
out = "["
while node is not None:
out += str(node.weight)
if node.link is not None:
out += ", "
node = node.link
out += "]"
return out
warehouse = (
ItemList()
.insert(8)
.insert(6)
.insert(4)
.insert(2)
.insert(0)
)
warehouse2 = (
ItemList()
.insert(9)
.insert(7)
.insert(5)
.insert(3)
.insert(1)
)
print(merge(warehouse,warehouse2))
You could write a recursive mergeNodes function:
def mergeNodes(a: Node, b: Node):
"""
This function takes two linked lists of Nodes, assumed sorted, and
merges them into a single linked list of Nodes, in-place, without
creating new Nodes, just by changing the links.
"""
if a is None:
return b
if b is None:
return a
if a.head.weight <= b.head.weight:
a.link = mergeNodes(a.link, b)
return a
else
b.link = mergeNodes(a, b.link)
return b
Merging the lists becomes very simple:
def merge(a: ItemList, b: ItemList) -> ItemList:
"""
This function takes two ItemList objects, assumed sorted, and merges them
into a new ItemList, without creating new Nodes, just by changing the links.
the original objects are destroyed
"""
# create a new ItemList:
c = ItemList()
c._length = a._length + b._length;
c.head = mergeNodes(a.head, b.head)
# clear the original lists because the links are no longer consistent
a._length = 0
a.head = None
b._length = 0
b.head = None
return c
Note that merge should be a method of class ItemList and merge a list passed as an argument into self.

Python list of classes , index() not working

Not sure what i'm doing wrong here. I have this class:
class Node:
'''
Class to contain the lspid seq and all data.
'''
def __init__(self, name,pseudonode,fragment,seq_no,data):
self.name = name
self.data = {}
self.pseudonode = pseudonode
self.seq_no = seq_no
self.fragment = fragment
def __unicode__(self):
full_name = ('%s-%d-%d') %(self.name,self.pseudonode,self.fragment)
return str(full_name)
def __cmp__(self, other):
if self.name > other.name:
return 1
elif self.name < other.name:
return -1
return 0
def __repr__(self):
full_name = ('%s-%d-%d') %(self.name,self.pseudonode,self.fragment)
#print 'iside Node full_name: {} \n\n\n ------'.format(full_name)
return str(full_name)
and putting some entries in a list :
nodes = []
node = Node('0000.0000.0001',0,0,100,{})
nodes.append(node)
>>> nodes
[0000.0000.0001-0-0]
node = Node('0000.0000.0001',1,0,100,{})
nodes.append(node)
>>> nodes
[0000.0000.0001-0-0, 0000.0000.0001-1-0]
i'm trying to get the index of a node in list nodes[]
>>> node
0000.0000.0001-1-0
>>> nodes.index(node)
0
0 is not what i was expecting. Not sure why this is happening.
edit
i'm after getting the index of the list where '0000.0000.0001-1-0' is.
The index function, when used on a container, relies on its element's __cmp__ function to return the index of the first element that it thinks is equal to the input-object. You probably know as much, since you implemented it for the node. But what you are expecting is that __cmp__ considers not only the name, but also the pseudonode and the fragment, right?
A straight-forward approach would be to consider them a tuple, which performs a comparison of elements from left to right, until the first inequality was found:
def __cmp__(self, other):
self_tuple = (self.name, self.pseudonode, self.fragment)
other_tuple = (other.name, other.pseudonode, other.fragment)
if self_tuple > other_tuple:
return 1
elif self_tuple < other_tuple:
return -1
return 0
If you want another order, you can use the tuples-ordering to define it.

Append method for linked list

class _ListNode:
def __init__(self, value, next_):
self._data = value
self._next = next_
return
class List:
def __init__(self):
self._front = None
self._count = 0
return
def _linear_search(self,key):
previous = None
current = self._front
index = 0
while current is not None and key > current._data:
previous = current
current = current._next
index += 1
if current._data != key:
previous = None
current = None
index = -1
return previous, current, index
def __contains__(self, key):
_, _, i = self._linear_search(key)
return i != -1
def append(self, value):
if self._front is None:
self._front = _ListNode(value,None)
else:
self._front._next = _ListNode(value,None)
self._count += 1
l = List()
lst = [1,2,3,4]
i = 0
n = len(lst)
while i < n:
l.append(lst[i])
i += 1
print("{}".format(l.__contains(3))
To explain more, I implement the linear search method and the contains method. The contains method check if the number is in the list or not (returns true or false). Now when I need to check that #3 in the list using contains method, the answer is false!! i cant know what's the problem
Your append method does not walk down the list. It simply always appends to self._front._next if self.front is already present. Meaning the contents at the end of the append loop are the first thing you appended, the last thing you appended and nothing in between.
To correct it walk the list looking for a _next equal to None and append there.
def append(self, value):
if self._front is None:
self._front = _ListNode(value, None)
else:
n = self._front
while n._next is not None:
n = n._next
n._next = _ListNode(value, None)
self._count += 1
You could also define a _str__ method to print the content of the List
e.g.
def __str__(self):
res = []
n = self._front
while n is not None:
res.append(str(n._data))
n = n._next
return ', '.join(res)
This is not a particularly efficient implementation as it builds an intermediate builtin list object.
You also don't need those bare return statements in your methods. You can remove those.

python create a binary search tree using existing function

I'm practicing creating a balanced binary search tree in python.
I already have these below, any idea on how to create a balance_bst funtion that passed a list of unique values that are
sorted in increasing order. It returns a reference to the root of a well-balanced binary search tree:
class LN:
def __init__(self,value,next=None):
self.value = value
self.next = next
def list_to_ll(l):
if l == []:
return None
front = rear = LN(l[0])
for v in l[1:]:
rear.next = LN(v)
rear = rear.next
return front
def str_ll(ll):
answer = ''
while ll != None:
answer += str(ll.value)+'->'
ll = ll.next
return answer + 'None'
# Tree Node class and helper functions (to set up problem)
class TN:
def __init__(self,value,left=None,right=None):
self.value = value
self.left = left
self.right = right
def height(atree):
if atree == None:
return -1
else:
return 1+ max(height(atree.left),height(atree.right))
def size(t):
if t == None:
return 0
else:
return 1 + size(t.left) + size(t.right)
def is_balanced(t):
if t == None:
return True
else:
return abs(size(t.left)-size(t.right)) <= 1 and is_balanced(t.left) and is_balanced(t.right)
def str_tree(atree,indent_char ='.',indent_delta=2):
def str_tree_1(indent,atree):
if atree == None:
return ''
else:
answer = ''
answer += str_tree_1(indent+indent_delta,atree.right)
answer += indent*indent_char+str(atree.value)+'\n'
answer += str_tree_1(indent+indent_delta,atree.left)
return answer
return str_tree_1(0,atree)
How do write the balance_bst?
def balance_bst(l):
Here is what I did:
def build_balanced_bst(l):
if l == None:
return None
else:
middle = len(l) // 2
return TN(l[middle],
build_balanced_bst(l[:middle]),
build_balanced_bst(l[middle + 1:]))
It gives me:
IndexError: list index out of range
How do I fix it?
I'm not going to write it for you since that's not what SO is about, but here's the general idea. Since the list is already sorted, the root should be the element in the middle of the list. Its left child will be the root of the balanced tree consisting of the elements to the left of the root in the list, and the right sub-tree will be the rest.

Python MyHashTable class: search method with linear probing

I need help implementing a method for my "MyHashTable" class:
def search(self, search_key):
The method is supposed to use linear probing to handle collision resolution. If the search_key is in the hash table then the method returns the slot number of the slot containing that search_key. If the search_key is not in the hash table, the method returns -1
My class looks like this:
class MyHashTable:
def __init__(self, capacity):
self.capacity = capacity
self.slots = [None] * self.capacity
def __str__(self):
return str(self.slots )
def __len__(self):
count = 0
for i in self.slots:
if i != None:
count += 1
return count
def hash_function(self, key):
i = key % self.capacity
return i
def insert(self, key):
slot = self.hash_function(key)
orig = slot
while True:
if self.slots[slot] is None:
self.slots[slot] = key
return slot
if self.slots[slot] == key:
return -2
slot = (slot + 1) % self.capacity
if slot == orig:
return -1
def search(self, search_key):
Any help or tutorial links would be awesome.
Thanks
You are only using a single list to store all the values, if you wanted a hash table you might use a list of lists where each list was a bucket but if you just want to check if the element is in your hash table with your own code:
def search(self, search_key):
hsh = self.hash_function(search_key)
if self.slots[hsh] is None:
return -1
while hsh < self.capacity:
if self.slots[hsh] == search_key:
return hsh
hsh += 1
return -1
You also have to handle the case where you have multiple collisions so we need at worst to check every element in the hash table to find the correct value:
def search(self, search_key):
hsh = self.hash_function(search_key)
if self.slots[hsh] is None:
return -1
for i in range(self.capacity):
mod = (hsh + i) % self.capacity
if self.slots[mod] == search_key:
return mod
return -1
The first while loop will probe one value over at a time but if we have wrapped around the list from multiple collisions it would miss elements at the start so using range and mod = (hsh + i) % self.capacity makes sure we check all entries like the example below.
m = MyHashTable(5)
m.insert(13) # 13 % 5 = 3
m.insert(73) # 83 % 5 = 3
m.insert(93) # 93 & 5 = 3
print(m.search(13)) # 3
print(m.search(73)) # 4
print(m.search(93)) # 0
print(m.search(2)) # -1
You can make your len method O(1) by keeping track of when you add a unique value to your hash table, there is also a nice wiki page on Open_addressing parts of which you can adopt into your code and it will help you create a proper mapping of keys to values and resized your hash table when needed. If you want to store more than just numbers you need to use a different hash function, I just use hash but you can use whatever you like. Also using in when your hash table is full and the key does not exist will cause an infinite loop so you will need to handle that case:
class MyHashTable:
def __init__(self, capacity):
self.capacity = capacity
self.slots = [None] * self.capacity
self.count = 0
def __str__(self):
return str(self.slots)
def __contains__(self, item):
return self.search(item) != -1
def __len__(self):
return self.count
def hash_function(self, key):
return hash(key) % self.capacity
def find_slot(self, key):
slot = self.hash_function(key)
while self.slots[slot] is not None and self.slots[slot] != key:
slot = (slot + 1) % self.capacity
return slot
def insert(self, key):
slot = self.find_slot(key)
if self.slots[slot] != key:
self.slots[slot] = key
self.count += 1
def search(self, key):
i = self.find_slot(key)
if self.slots[i] is not None:
return i
return -1
Add a __contains__ will also allow you to use in to test for membership:
m = MyHashTable(5)
m.insert("foo")
m.insert(73)
m.insert(93)
m.insert(1)
print(m.search(73))
print(m.search(93))
print(m.search(1))
print(m.search("foo"))
m.insert(73)
print(m.slots)
print(len(m))
print("foo" in m)
print(5 in m)
Output:
3
4
1
0
['foo', 1, None, 73, 93]
4
True
False

Categories