Linked List ques - python

My doubt is when I make A.next=None, Shouldnt kam variable also store None? Why is still pointing to Node 6?
class Node:
def __init__(self, data): # data -> value stored in node
self.data = data
self.next = None
a=Node(5)
b=Node(6)
c=Node(7)
d=Node(8)
a.next=b
b.next=c
c.next=d
kam=a.next
a.next=None
while kam is not None:
print(kam.data)
kam=kam.next

That's because you make next attribute of a instance None, not c node. When Python runs kam=a.next what really happens is that kam becomes whatever a.next value is pointing at. If you later change a.next then it's not implied that kam will change.

My doubt is when I make A.next=None, Shouldnt kam variable also store None? Why is still pointing to Node 6?
No. Python performs assignments. It thus sets kam to reference to what a.next is referencing at that moment. It thus copies the reference. If you later alter that value, that will not reflect on the kam itself, since it made a copy of the reference at the moment you assigned it.
So kam is referring to b since of the moment of assignment, a.next was referring to b:
kam=a.next # kam = b
a.next=None # a.next = None

No kam is pointing to b. And when you do A.next = None the next pointer of A points to none but kam holds reference to b. Think of them to be exclusive.

Related

Does breaking a linked list help Python's garbage collector?

I was wondering whether — when I have the opportunity — I should break up a singly-connected linked list in Python when I don't need it anymore, or if I should not bother myself with it.
List example:
class Link:
def __init__(self, next=None):
self.next = next
L = Link(Link(Link(Link())))
Breaking the links:
def break_(link):
if link is not None:
break_(link.next)
link.next = None # link broken
# application
break_(L)
Tree example:
class Node:
def __init__(self, l=None, r=None):
self.l = l
self.r = r
T = \
Node(
Node(
Node(),
Node()
),
Node(
Node(),
Node()
),
)
Breaking the links:
def break_(node):
if node is not None:
break_(node.l)
break_(node.r)
node.l = None # link broken
node.r = None # link broken
# application
break_(T)
Basically, what I wonder is, what is the performance-wise optimal way to write your code in this situation. Is the GC prepared to deal with large linked structures? Doesn't it have to run potentially long DFSes with counters and such to determine which objects can be freed? Isn't it simpler to just break the links and give the GC a bunch of loose objects with zero references anywhere?
I could benchmark this, but I'm looking for an explanation (ideally from Karl), if it's available. Thank you.
You can add a __del__ method to your class to see when the object is about to be cleaned up:
class Node:
def __init__(self, name, l=None, r=None):
self.name = name
self.l = l
self.r = r
def __del__(self):
print(f'__del__ {self.name}')
T = \
Node('0',
Node('0L',
Node('0LL'),
Node('0LR'),
),
Node('0R',
Node('0RL'),
Node('0RR'),
),
)
del T # decreases the reference count of T by 1
What happens with del T is that the reference count of the object referred to by T is decreased by 1. In this case, it happens that the reference count reaches 0. CPython knows this because it stores a reference count together with each object. When the reference count of an object reaches 0, the object will be marked for cleanup. This cleanup happens before control is given back to user code. So for the above example, it means the object that T originally referred to is immediately cleaned up. This first invokes __del__ and then the corresponding C-code for cleanup. This in turn goes through the object's instance dict and decreases the reference count for each other object stored there by 1. If another object's reference count reaches 0 in that process, it is also marked for cleanup. Then this procedure repeats until no objects are marked for cleanup and control is given back to the main loop.
This is the output from the example:
__del__ 0
__del__ 0L
__del__ 0LL
__del__ 0LR
__del__ 0R
__del__ 0RL
__del__ 0RR
As mentioned above, when 0 is cleaned up, all other objects it references have their reference count reduced by 1 (i.e. 0L and 0R) and get cleaned up as well if the ref count reaches 0.
If you manually break the links of the list by setting the corresponding instance attribute to None this adds additional overhead, as it has to actually modify all of these objects in memory (updating their instance dict). The reference count of the child objects reaching 0 is more of a byproduct here. Also note that the order of cleanup is changed since the clean function goes depth first:
def clean(node):
if node is not None:
clean(node.l)
clean(node.r)
node.l = None # this updates the object in memory
node.r = None
clean(T)
produces
__del__ 0LL
__del__ 0LR
__del__ 0RL
__del__ 0RR
__del__ 0L
__del__ 0R
__del__ 0

Printing linked-list in python

In my task first I need to make single linked-list from array.
My code:
class Node:
def __init__(self,data):
self.data = data
self.next = next
class Lista:
def __init__(self, lista=None)
self.head = None
def ispis(self):
printval = self.head
while printval .next is not None:
print(printval.next.data)
printval = printval.next
if __name__ == '__main__'
L = Lista ([2, "python", 3, "bill", 4, "java"])
ispis(L)
With function ispis I need to print elements of linked-list. But it says name "ispis" is not defined. Cannot change ispis(L) !
EDIT: removed next from and ispis(self) is moved outside Lista class
while printvla.next is not None:
EDIT2:
It shows that L is empty so thats why it won't print anything. Should I add elements to class Node ?
ispis is a method in a class. But you are calling the function as if it is a normal function outside the class.
Atleast you have created the object correctly. Below would be the correct way of calling the method inside the class.
L.ispis()
This question sounds suspiciously like a homework assignment. If the instructor is trying to teach you how to create linked lists, you need to go back to what you need to do:
A node when set up for the first time only needs the data. Typically the next pointer/value would be set to None (meaning no next member).
Your __init__ method for your Lista class needs to do something with its argument.
I believe if you need to use your ispls function to operate on a class, then the function probably isn't supposed to be a member of Lista.
I think your ispls loop shouldn't be testing its .next member. This would fail if you had a None to begin with. You should be testing the current instance rather than its next. That way, when you move on to the next node, if it's None, it gets out of the loop.
Be careful with the keyword next. I would avoid using it as a class attribute. Also the literal next would just give you the built-in command.
At the bare minimum, you would want to iterate over the lista argument in __init__, creating a Node for each one, saving the previous node for the next operation.
if lista is None:
self.head = None
return
prev = None
for data in lista:
node = Node(data)
if prev is None:
self.head = node
else:
prev.next = node
prev = node
But again, I believe that is what the instructor wanted you to figure out. Hope this helped.
--B

Assigning an (OOP) object to another

I was trying to assign a Python object to another in-place using a member function such as replace_object() below. However, as you can see, object_A remains unchanged and the only way to copy object_B is to create an entirely new object object_C, which defeats the purpose of in-place assignment.
What is going on here and how can I make the assignment in-place?
class some_class():
def __init__(self, attribute):
self.attribute = attribute
def replace_object(self, new_object):
self = new_object
# Does this line even have any effect?
self.attribute = new_object.attribute
self.new_attribute = 'triangle'
return self
object_A = some_class('yellow')
print(object_A.attribute) # yellow
object_B = some_class('green')
object_C = object_A.replace_object(object_B)
print(object_A.attribute) # yellow
print(object_C.attribute) # green
#print(object_A.new_attribute) # AttributeError!
print(object_B.new_attribute) # triangle
print(object_C.new_attribute) # triangle
I also tried to play around with deep copies using copy.copy(), but to no avail.
An interesting twist to this is that if I replace
object_C = object_A.replace_object(object_B)
with
object_A = object_A.replace_object(object_B)
then I get what I want. But why can't the same result be achieved by the statement self = new_object statement within replace_object()?
PS: I have a very good reason to do this in-place assignment, so although it may not be best practice in general, just go along with me here.
You can't 'assign an object to another'. You can assign new and existing objects to new and existing names.
self = new_object only says 'from now on the name self will refer to new_object', and does nothing to the old object. (Note self is just a variable name like any other and only by convention refers to an object within a class definition.)
The subsequent command self.attribute = new_object.attribute has no effect because self has already become a duplicate label for the new_object.
You could copy all the properties of a new object to the old object. You would end up with two distinct objects with different names and identical properties. A test of equality (a == b) would return false unless you overrode the equality operator for these objects.
To copy all the properties inline you could do something like this:
def replace_object(self, new_object):
self.__dict__ = new_object.__dict__.copy() # just a shallow copy of the attributes
There are very likely better ways to do whatever it is you want to do.

Why does .append() not work on this list?

I have an object scene which is an instance of class Scene and has a list children which returns:
[<pythreejs.pythreejs.Mesh object at 0x000000002E836A90>, <pythreejs.pythreejs.SurfaceGrid object at 0x000000002DBF9F60>, <pythreejs.pythreejs.Mesh object at 0x000000002E8362E8>, <pythreejs.pythreejs.AmbientLight object at 0x000000002E8366D8>, <pythreejs.pythreejs.DirectionalLight object at 0x000000002E836630>]
If i want to update this list with a point which has type:
<class 'pythreejs.pythreejs.Mesh'>
I need to execute:
scene.children = list(scene.children) + [point]
Usually, I would execute:
scene.children.append(point)
However, while these two approaches both append point, only the first actually updates the list and produce the expected output (that is; voxels on a grid). Why?
The full code can be found here.
I am guessing your issue is due to children being a property (or other descriptor) rather than a simple attribute of the Scene instance you're interacting with. You can get a list of the children, or assign a new list of children to the attribute, but the lists you're dealing with are not really how the class keeps track of its children internally. If you modify the list you get from scene.children, the modifications are not reflected in the class.
One way to test this would be to save the list from scene.children several times in different variables and see if they are all the same list or not. Try:
a = scene.children
b = scene.children
c = scene.children
print(id(a), id(b), id(c))
I suspect you'll get different ids for each list.
Here's a class that demonstrates the same issue you are seeing:
class Test(object):
def __init__(self, values=()):
self._values = list(values)
#property
def values(self):
return list(self._values)
#values.setter
def values(self, new_values):
self._values = list(new_values)
Each time you check the values property, you'll get a new (copied) list.
I don't think there's a fix that is fundamentally different than what you've found to work. You might streamline things a little by by using:
scene.children += [point]
Because of how the += operator in Python works, this extends the list and then reassigns it back to scene.children (a += b is equivalent to a = a.__iadd__(b) if the __iadd__ method exists).
Per this issue, it turns out this is a traitlets issue. Modifying elements of self.children does not trigger an event notification unless a new list is defined.

Nodes of a graph in Python

I am trying to figure out the best way of coding a Node Class (to be used in a binary tree) that would contain the attributes key, left and right.
I thought I would do something like
class Node:
def __init__(self, key):
self.key= key
self.left = None
self.right = None
and then do
a = Node('A')
b = Node('B')
c = Node('C')
a.left = b
a.right = c
Here I am a little bit confused: are (under the hood) left and right pointers? Or is a containing a copy of the whole tree?
If I add
d = Node('B') # same key as before, but an entirely different node
c.right = d
then are b and d two different objects even if they have the same attributes? I would think so because they don't share any memory.
Finally, if I want to do a deep copy of one of my nodes, is
e = Node(a.key))
sufficient?
Python is dynamically typed, so you can't say left and right are references. One can also store an integer, or float in them. You can even first store an integer then a reference to an object and later a float in them, so the type might vary over time. But if you perform an assignment to an object. That will indeed result in a pointer (this is a huge semantical difference with your question).
For your second question, it depends on how you see deep copying. If your node contains references to other nodes, do you want to copy these nodes as well?
If you are interested only in generating a new node with the same value but with references to the same other nodes, then use: copy.copy, otherwise use copy.deepcopy.
The difference is:
B <- A -> C B' <- D -> C'
^ ^
| |
\-- S --/
With S a shallow copy and D a deep copy. Note that a deep copy thus results in new nodes B' and C'. You can imagine that if you deep copy a huge tree this can result in a large memory and CPU footprint.
Your code
e = Node(a.key))
Is not completely correct since you don't copy (potential) references to your left and right node, and furthermore it's not good design since you can attach more items to the node and you need to modify your copy function(s) each time. Using the copy.copy-functions is thus more safe.
Yes b and d have the same attributes, but they are two independent instances. To see this:
print id(b) # one number
print id(d) # new number
This proves that they are two different objects in memory. To see that a.right is the same object as c use the same technique, checking for equivalent id values.
print id(a.right)
print id(c) # same
Yes these are just references to the left or right object.
Everytime you do Node("some_str), a new object is created. So b & d will be different, and a new object gets created for e = Node(a.key)).
Doing a e = Node('E') and doing f = e will be the same, with f and e referring to the same object.

Categories

Resources