Awkward python class behaviour? - python

I have the following simple code:
class Node:
pass
def make_node(value):
n = Node
n.value = value
return n
if __name__ == '__main__':
list = range(100)
random.shuffle(list)
nodes = []
for i in range(len(list)):
nodes.append(make_node(list[i]))
for n in nodes:
print n.value
Upon printing the value at each of the nodes, they are all identical. It seems that each "new node" i built simply overwrites the value of all of the previous ones. Why are these not being set completely separately, and how can I fix it?

I think you want to call the Node constructor:
n = Node()
Otherwise, the assignment to n.value is the same as assigning to Node.value, which sets an attribute of the class, not the object you wanted to create. By returning the Node class object itself, your nodes list contains a bunch of references to the same Node class object.

Related

Python Linked List how value gets assigned

Below is a code that divides each number of the node by 10. For example, node = 2->3->3, output would be 0.2->0.3->0.3.
However I am confused on why self.head.next gets updated each time given it's cur that receive the change. Suppose a=1,b=2 and we make a(cur)=b(self.head), if we change the value of a to 3, that wouldn't affect b, b is still 2. Therefore I couldn't understand why changing cur would affect self.head.next. Thank you!
class node(object):
def __init__(self,value,next=None):
self.value=value
self.next=next
class linkedlist(object):
def __init__(self):
self.head=None
self.next=None
def test(self,List):
self.head=node(0)
cur=self.head
while List:
s=List.value/10
cur.next=node(s)
cur=cur.next
List=List.next if List else 0
return self.head.next
Suppose below is the input:
a=node(2)
a=node(2,a)
a=node(3,a)
c=linkedlist()
Below is the output:
c.test(a).value=0.3
c.test(a).next.value=0.2
c.test(a).next.next.value=0.2
I am confused on why self.head.next gets updated each time given it's cur that receive the change
That is because, at least in the first iteration of the loop, self.head is cur:
cur=self.head
After that, it builds a new list with updated values, using a "dummy" node as self.head (and thus the first cur) which is then discarded and only it's next is returned. However, I find that code rather confusing and overly complicated (I had a hard time understanding it myself), e.g. the ternary ... if ... else ... in the last line is redundant as List can not be None at that point. Also, there's no need in making that a class, since none of its member attributes are used beyond the scope of a single execution of the method.
Instead, you could use a simple function, e.g. using a loop and modifying the original list, or even simpler, recursively creating a new list:
def div(lst, d=10):
first = lst
while lst:
lst.value /= d
lst = lst.next
return first
def div(lst, d=10):
return node(lst.value / 10, div(lst.next, d)) if lst else None
For easier debugging, you can also add a __repr__ method to your node class:
def __repr__(self):
return "(%r %r)" % (self.value, self.next)

Circular, doubly linked lists with hash table space

I'm currently working on implementing a Fibonacci heap in Python for my own personal development. While writing up the object class for a circular, doubly linked-list, I ran into an issue that I wasn't sure of.
For fast membership testing of the linked-list (in order to perform operations like 'remove' and 'merge' faster), I was thinking of adding a hash-table (a python 'set' object) to my linked-list class. See my admittedly very imperfect code below for how I did this:
class Node:
def __init__(self,value):
self.value = value
self.degree = 0
self.p = None
self.child = None
self.mark = False
self.next = self
self.prev = self
def __lt__(self,other):
return self.value < other.value
class Linked_list:
def __init__(self):
self.root = None
self.nNodes = 0
self.members = set()
def add_node(self,node):
if self.root == None:
self.root = node
else:
self.root.next.prev = node
node.next = self.root.next
self.root.next = node
node.prev = self.root
if node < self.root:
self.root = node
self.members.add(node)
self.nNodes = len(self.members)
def find_min():
min = None
for element in self.members:
if min == None or element<min:
min = element
return min
def remove_node(self,node):
if node not in self.members:
raise ValueError('node not in Linked List')
node.prev.next, node.next.prev = node.next, node.prev
self.members.remove(node)
if self.root not in self.members:
self.root = self.find_min()
self.nNodes -=1
def merge_linked_list(self,LL2):
for element in self.members&LL2.members:
self.remove_node(element)
self.root.prev.next = LL2.root
LL2.root.prev.next = self.root
self.root.prev, LL2.root.prev = LL2.root.prev, self.root.prev
if LL2.root < self.root:
self.root = LL2.root
self.members = self.members|LL2.members
self.nNodes = len(self.members)
def print_values(self):
print self.root.value
j = self.root.next
while j is not self.root:
print j.value
j = j.next
My question is, does the hash table take up double the amount of space that just implementing the linked list without the hash table? When I look at the Node objects in the hash table, they seem to be in the exact same memory location that they are when just independent node objects. For example, if I create a node:
In: n1 = Node(5)
In: print n1
Out: <__main__.Node instance at 0x1041aa320>
and then put this node in a set:
In: s1 = set()
In: s1.add(n1)
In: print s1
Out: <__main__.Node instance at 0x1041aa320>
which is the same memory location. So it seems like the set doesn't copy the node.
My question is, what is the space complexity for a linked list of size n with a hash-table that keeps track of elements. Is it n or 2n? Is there anything elementary wrong about using a hash table to keep track of elements.
I hope this isn't a duplicate. I tried searching for a post that answered this question, but didn't find anything satisfactory.
Check In-memory size of a Python structure and How do I determine the size of an object in Python? for complete answers in determining size of objects
I have this small results on a 64 bits machine with python 3
>>> import sys
>>> sys.getsizeof (1)
28
>>> sys.getsizeof (set())
224
>>> sys.getsizeof (set(range(100)))
8416
The results are in bytes. This can give you a hint about how big sets are (they are pretty big).
My question is, what is the space complexity for a linked list of size n with a hash-table that keeps track of elements. Is it n or 2n? Is there anything elementary wrong about using a hash table to keep track of elements.
Complexity calculations never make a difference between n and 2n.Optimisation does. And it's commonly said "early optimisation is the root of all evil", to warn about potential optimisation pitfalls. So do as you think is best for supported operations.

How to traverse Linked-Lists Python

I am trying to figure out how I can traverse linked list in Python using Recursion.
I know how to traverse linked-lists using common loops such as:
item_cur = my_linked_list.first
while item_cur is not None:
print(item_cur.item)
item_cur = item_cur.next
I was wondering how I could turn this loop into a recursive step.
Thanks
You could do something like this:
def print_linked_list(item):
# base case
if item == None:
return
# lets print the current node
print(item.item)
# print the next nodes
print_linked_list(item.next)
It looks like your linked list has two kinds of parts. You have list nodes, with next and item attributes, and a wrapper object which has an attribute pointing to a the first node. To recursively print the list, you'll want to have two functions, one to handle the wrapper and a helper function to do the recursive processing of the nodes.
def print_list(linked_list): # Non-recursive outer function. You might want
_print_list_helper(linked_list.first) # to update it to handle empty lists nicely!
def _print_list_helper(node): # Recursive helper function, gets passed a
if node is not None: # "node", rather than the list wrapper object.
print(node.item)
_print_list_helper(node.next) # Base case, when None is passed, does nothing
Try this.
class Node:
def __init__(self,val,nxt):
self.val = val
self.nxt = nxt
def reverse(node):
if not node.nxt:
print node.val
return
reverse(node.nxt)
print node.val
n0 = Node(4,None)
n1 = Node(3,n0)
n2 = Node(2,n1)
n3 = Node(1,n2)
reverse(n3)

Python: printing all nodes of tree unintentionally stores data

I've created a general tree in python, by creating a Node object. Each node can have either 0, 1, or 2 trees.
I'm trying to create a method to print a list of all the nodes in a tree. The list need not be in order. Here's my simplistic attempt:
def allChildren(self, l = list()):
l.append(self)
for child in self.children:
l = child.allChildren(l)
return l
The first time I run this method, it works correctly. However, for some reason it is storing the previous runs. The second time I run the method, it prints all the nodes twice. Even if I create 2 separate trees, it still remembers the previous runs. E.g: I create 2 trees, a and b. If I run a.allChildren() I receive the correct result. Then I run b.allChildren() and recieve all of a's nodes and all of b's nodes.
You have a mutable value as the default value of your function parameter l. In Python, this means that when you call l.append(self), you are permanently modifying the default parameter.
In order to avoid this problem, set l to a new list every time the function is called, if no list is passed in:
def allChildren(self, l = None):
if l is None:
l = list()
l.append(self)
for child in self.children:
l = child.allChildren(l)
return l
This phenomenon is explained much more thoroughly in this question.
try this:
def allChildren(self, l = None):
if(l==None):
l = list()
l.append(self)
for child in self.children:
l = child.allChildren(l)
return l
And check out this answer for explanation.
If you're writing default parameter like l = list(), it will create list when compiling function, so it will one instance of list for all function calls. To prevent this, use None and create new list inside the function:
def allChildren(self, l = None):
if not l: l = []
l.append(self)
for child in self.children:
l = child.allChildren(l)
return l

Generator methods, deepcopy and copy

I am trying to avoid the use of deepcopy in a custom class (a Graph class)
The graphs have few attributes, such as vertices, edges, etc. and several generator methods (methods with yield).
I need to copy the graph: e.g. H = deepcopy(G) but not using deepcopy in order to speed up the program.
Then:
If I do not use deepcopy then the
generator methods in the new graph H
do not get the current state of the
generator methods in graph G.
If I do not use generator methods and
opt for using full list generator,
then I will waste computation time
doing nothing useful.
The solution was to try to deepcopy some specific generator methods, but I get errors.
It seems that the generators save references to, e.g. the vertices and edges of G and then when deepcopied to H the generators in H are still referencing the attributes of G (this sounds logical).
So, am I condemned to use deepcopy after all or not use generator methods?
Is there a third pythonic way?
I'm pretty sure I understand what you're getting at. Here's a simple example:
class Graph:
def __init__(self, nodes):
self.nodes = list(nodes)
self.nodegen = self.iternodes()
def iternodes(self):
for node in self.nodes:
yield node
def copy(self):
return Graph(self.nodes)
G = Graph([1, 2, 3, 4])
print G.nodegen.next()
H = G.copy()
print H.nodegen.next()
print G.nodegen.next()
Now of course this will print 1 1 2. You, however, want H.nodegen to remember the state of G.nodegen so that the call to H.nodegen.next() prints 2. A simple way is to make them the same object:
class Graph:
def __init__(self, nodes, nodegen=None):
self.nodes = list(nodes)
self.nodegen = self.iternodes() if nodegen is None else nodegen
def iternodes(self):
for node in self.nodes:
yield node
def copy(self):
return Graph(self.nodes, self.nodegen)
This will print 1 2 3, since calling H.nodegen.next() will advance G.nodegen as well. If that's not what you want, it seems fine to me to keep an internal counter, like this:
class Graph:
def __init__(self, nodes, jnode=0):
self.nodes = list(nodes)
self.nodegen = self.iternodes()
self.jnode = jnode
def iternodes(self):
while self.jnode < len(self.nodes):
self.jnode += 1
yield self.nodes[self.jnode-1]
def copy(self):
return Graph(self.nodes, self.jnode)
This will print 1 2 2, which I suspect is what you want. Of course you'll have to change how you take care of things like invalidating iterators when you change self.nodes, but I think it should be fairly straightforward.

Categories

Resources