Why is this instance variable not incrementing (Python)? - python

I have implemented a Depth-first search algorithm, and using an instance variable called "call-count", I am trying to track how many recursions/comparisons are executed inside the code, i.e. how many times "searchDFS" is ran. Inside "searchDFS", the call_count variable defaults to the instance variable the first time searchDFS is ran, then it is passed down through every recursion. All of the other instance variables work fine, I am able to append to the top-level instance array "final_path" just fine, but it refuses to increment call_count.
What am I doing wrong, and is there a better way to do this?
The code:
class Node:
def __init__(self, id, connections):
self.id = id
self.connections = connections
class DFS:
def __init__(self):
self.visited = {}
self.nodes = []
self.final_path = []
self.call_count = 0
def searchDFS(self, nodes, target, current, call_count=None, final_path=None, visited=None):
if(visited == None): visited = self.visited
if(final_path == None): final_path = self.final_path
if(call_count == None): call_count = self.call_count
call_count +=1 # <<< to increment every time searchDFS is ran
if current in visited.keys():
print(f'already visited node {current}')
return 0
else:
print(f'current -> {current}')
visited[current] = 1
if(current == target):
print('found!')
final_path.append(current)
return True
if(nodes[current].connections):
for node in nodes[current].connections:
if(self.searchDFS(nodes, target, node.id, call_count, final_path, visited)):
final_path.append(current)
if(current == 0):
print(final_path[::-1])
print(f'call count: {call_count}') # not incrementing !!! :(
return True
return False
def setup_graph(self):
for x in range(0, 5):
self.nodes.append(Node(x, []))
self.nodes[0].connections = [self.nodes[1], self.nodes[2]]
self.nodes[1].connections = [self.nodes[0], self.nodes[3]]
self.nodes[2].connections = [self.nodes[0], self.nodes[4]]
self.nodes[3].connections = [self.nodes[1]]
self.nodes[4].connections = [self.nodes[2]]
def print_graph(self):
for node in self.nodes:
print(f"node id: {node.id}")
print("children: ")
for childs in node.connections:
print(childs.id)
def main(self):
print('main() init')
self.setup_graph()
self.print_graph()
self.searchDFS(self.nodes, 4, 0)
if __name__ == "__main__":
DFS().main()

Solved thanks to #slothrop (in-depth explanation)
The difference comes down to "rebinding the name" versus "mutating the value"
Primitives are immutable in Python - They cannot be changed, only new ones created (and the variable name is re-bound to the new value):
x = 5
x = x + 1
The above creates a new block of memory with the new value, 6. Then, x rebinds to that new value. It is similar to how pointers work in C.
Unassigned values are automatically discarded by Python's garbage collector. The memory cell that was holding the '5' will be cleared, since it is no longer assigned to any variable.
Assignment never copies data, so assigning one variable to another won't make a second copy of the first, it just gives it 'directions' on where to look to find the value.
x = 5 # creates a value 5, with 'x' pointing to it
y = x # y is now pointing/bound to that same value, 5, as well
x = x + 1 # a new value, 6, is created, and x is "bound" to it
print(x) # outputs 6
print(y) # outputs 5
'y' is still "bound/looking" at the original '5'. There are also mutable values, like Lists. Lists contain a built-in function to append to themselves.
x = [1, 2]
y = x
x.append(3)
print(y) # outputs [1, 2, 3]
Since List is a class, it can have methods to change in-place, without having to create a completely new value like primitives.
In my code, when I assigned "call_count = self.call_count", I only pointed the value of "call_count" to the same value of instance variable "self.call_count". Incrementing "call_count" only assigns a new value for the local variable "call_count" without re-assigning the original instance variable "self.call_count". To fix this, I just made a function to update the instance's value, and bound a variable to that method inside my search function:
class DFS:
def __init__(self):
self.call_count = 0
def increment_call_count(self):
self.call_count += 1
def searchDFS(self, counter=0, inc=None):
if(inc == None): inc = self.increment_call_count # set once
inc() # execute the function which "inc" is pointing at
if(counter<5):
self.searchDFS(counter + 1, inc)
if(counter == 0): print(self.call_count) # prints 6 correctly
def main(self):
self.searchDFS()
if __name__ == "__main__":
DFS().main()
Since inc is only bound once to a single function, the 'value' of inc never changes, using it will just run that same function, which re-binds the original instance's variable to the new value.

Related

I got problem a about to call func from class

I got this code from a book name 'Data Structures and Algorithms in Python from Michael T. Goodrich ' chapter 5.5.1 Storing High Scores for a Game.
Can some one can explain to me what this self._board[-1].get_score( ) means ??
I try to print it to see what happens but I got:
print(self._board[-1].get_score( ))
AttributeError: 'NoneType' object has no attribute 'get_score'
class GameEntry:
def __init__(self, name, score):
self._name = name
self._score = score
def get_name(self):
return self._name
def get_score(self):
return self._score
def __str__(self):
return ('({0}, {1})'.format(self._name, self._score)) # e.g., (Bob, 98)
class Scoreboard():
def __init__(self, capacity=10):
self._board = [None]*capacity # reserve space for future scores
self._n=0 # number of actual entries
def __getitem__(self, k):
return self._board[k]
def add(self, entry):
score = entry.get_score()
print(score)
if self._n < len(self._board) or score > self._board[-1].get_score(): # what is it " self._board[-1].get_score()"
if self._n < len(self._board): # no score drops from list
self._n += 1 # so overall number increases
j = self._n - 1
while j > 0 and self._board[j-1].get_score( ) < score:
self._board[j] = self._board[j-1] # shift entry from j-1 to j
j -= 1 # and decrement j
self._board[j] = entry
def __str__(self):
return '\n' .join(str(self._board[j]) for j in range(self._n))
a_ = Scoreboard()
a = ('a','b','c','d')
b = (5,4,8,4)
c = dict(zip(a,b))
print(c)
for i,j in c.items():
x = GameEntry(i,j)
print(x)
y=a_.add(x)
print(y)
Inside your class Scoreboard you keep a list of game entries:
self._board = [None]*capacity # reserve space for future scores
This list is used to keep GameEntry entries:
self._board[j] = entry
The logic in your application uses ._n to track the number of entries added, but only up to total number of score slots available (self._n < len(self._board)).
If this is False (i.e. the number of entries added is the same as the capacity of the entry list when it was initialized), the other part of the or statement gets executed:
score > self._board[-1].get_score()
This looks at the last element in self._board - i.e. what is supposed to be the slot entry with the lowest score to see if this score deserves to be entered into the list instead of the previous one. Any negative list index starts from the end of the list instead of the wrong, so -1 points to the last entry, -2 the second to last entry, and so on.
If you get a None entry while running your code (and not just printing it out before you've added ten entries) is another question, and is probably what you need to debug further (but it doesn't seem to be the case from your code). If you attempt to print that entry before inserting all ten entries (i.e. filling up the entry list), there won't be an entry in last place, and the None placeholder will still be there.

How to generate a new instance of a class in recursion

Recently I was learning the sequence alignment algorithm. After I got the alignment matrix, I could find an optimal path, but I was in trouble when I was looking for multiple optimal paths (backtracking)!
My idea is to store the results of multiple paths with multiple instances, and finally loop through all instances of the base class to get the answer.
I know the following conditions:
What conditions to exit recursion
When do I need to create a new instance and when I don't create it?
But the problem is in the second condition. I don't know how many optimal results there are, and I don't know how many new instances will be created.
So I want to be able to dynamically generate an instance name with a variable.
I don't know how to do this:
# those equivalent to new_instance_name = ResultSeq()
a="new_instance_name"
create_new_instance(a,ResultSeq)
My result base class is ResultSeq:
class KeepRefs(object):
"""
reference:https://stackoverflow.com/questions/328851/printing-all-instances-of-a-class#comment167339_328851
"""
__refs__ = defaultdict(list)
def __init__(self):
self.__refs__[self.__class__].append(weakref.ref(self))
#classmethod
def get_instances(cls):
for inst_ref in cls.__refs__[cls]:
inst = inst_ref()
if inst is not None:
yield inst
class ResultSeq(KeepRefs):
"""
save two
"""
def __init__(self, seq1="", seq2=""):
super(ResultSeq, self).__init__()
self.seq1 = seq1
self.seq2 = seq2
Below is my recursive code:
def multi_backtracking(self, array, i, j, result_seq):
"""
:param array: V, E, F
:param i: row
:param j: col
:param result_seq: new instance of the class ResultSeq
:return: Multiple alignment results
"""
def create_new_obj(name, obj):
"""
I don't know how to do this.
"""
pass
if i == 0 and j == 0:
pass
else:
if array is self.array_V:
if sum(pass_judgement) == 1:
"""
An optimal path without creating a new instance.
"""
self.multi_backtracking(self.array_V, i, j, result_seq)
else:
"""
Multiple paths, need to create a new instance
"""
new_instance_name = "xxx"
create_new_obj(new_instance_name, ResultSeq)
...
if pass_judgement[0]:
result_seq.seq1 = self.origin_seq.seq1[i - 1] + result_seq.seq1
result_seq.seq2 = self.origin_seq.seq2[j - 1] + result_seq.seq2
self.multi_backtracking(self.array_V, i - 1, j - 1, new_instance_name)
if pass_judgement[1]:
self.multi_backtracking(self.array_E, i, j, new_instance_name)
if pass_judgement[2]:
self.multi_backtracking(self.array_F, i, j, new_instance_name)
This is just one of my solutions. If there are better suggestions, I will be happy to accept them, thank you!
You do not need names to store variables - you can use a simple list to store your instances:
class A:
def __init__(self,value):
self.value = value
def __repr__(self):
return f" _{self.value}_ "
def rec(i):
"""Recursive function, returns a list of instances of class A with decreasing
value i"""
if i < 0:
return []
return [A(i)] + rec(i-1)
k = rec(5)
print(k)
Output:
[ _5_ , _4_ , _3_ , _2_ , _1_ , _0_ ]
You can acccess your instances inside your list by indexing:
print(k[2]) # _3_
print(k[2].value + k[3].value) # 5
If you really need names, you can use a dictionary to store them - that is about the same as your existing baseclass KeepRefs does (*):
data = { "Instance1" : A(42), "Instance2" : A(3.141)}
print(data)
print( data["Instance1"].value + data["Instance2"].value )
Output:
{'Instance1': _42_ , 'Instance2': _3.141_ }
45.141
Most of the time when you need user generated "names" for variables you should very strongly reconsider your options.
(*) Your baseclass does not keep non-referenced instances around, a real dict will prevent garbage collecting:
k1 = ResultSeq("A","B")
k2 = ResultSeq("C","D")
k3 = ResultSeq("E","F")
for g in ResultSeq.get_instances():
print(g.seq1, g.seq2)
k2 = None # no instance of k2 anywhere
k3 = None # no instance of k3 anywhere
for g in ResultSeq.get_instances():
print(g.seq1, g.seq2)
A B
C D
E F
A B # 2.print loop after removing instances k2,k3
Documentation:
https://docs.python.org/3/library/weakref.html

NameError: global name is not defined, why am I getting that error?

I am trying to implement some functions but I'm getting an error with len_link function:
NameError: global name 'len_link' is not defined
While the other function is working perfectly fine, any clue why this error is occurring in the first place?
class Solution:
# #param A : head node of linked list
# #param B : head node of linked list
def len_link(A):
temp=A.head
count=0
while(temp):
count+=1
temp=temp.next
return count
def longerlist(B,n):
for i in range(n):
B = B.next
return B
def getIntersectionNode(self, A, B):
m = len_link(A)
n = len_link(B)
d = abs(m-n)
if m>n :
A = longerlist(A,n)
elif m<n:
B = longerlist(B,n)
while A!= None and B!= None:
if A.val == B.val:
return A
A = A.next
B = B.next
You need to call Solution.len_link not just len_link. Otherwise Python expects it to be a name at global scope.
Also, since len_link does not take a self parameter, it needs to be decorated with #staticmethod:
#staticmethod
def len_link(A):
Or create a function outside of that class, by this you will able to call it globally or go with classname.functionname()

Python - Why is my locally defined linked list updated when I call other functions that doesn't return anything [duplicate]

This question already has answers here:
Function changes list values and not variable values in Python [duplicate]
(7 answers)
Closed 7 years ago.
The code below imports a linked list from LinkedQfile and creates a list object with some node objects.
If I run this code the output from check_something() becomes CD .
I thought linked_list in check_something() would become a local object inside the function and since I'm not assigning whatever I'm returning to anything it wouldn't change, that is I would expect the output ABCD. This is obviously not the case so I'm wondering if someone could explain to me what is going on here?
If linked_list was a global variable I would expect this outcome, my guess is that the return statements in each function returns some information to the object but I have no idea how and why! (I got the code from a lecture note and it works just like I want it to, I just want to know why!)
from LinkedQFile import LinkedQ
def check_something(linked_list):
check_first_element(linked_list)
check_second_element(linked_list)
print(linked_list)
def check_first_element(linked_list):
word = linked_list.dequeue()
if word == "A":
return
def check_second_element(linked_list):
word = linked_list.dequeue()
if word == "B":
return
def main():
list = LinkedQ()
list.enqueue("A")
list.enqueue("B")
list.enqueue("C")
list.enqueue("D")
check_something(list)
main()
And if needed, the LinkedQFile:
class Node:
def __init__(self, x, next= None):
self._data = x
self._next = next
def getNext(self):
return self._next
def setNext(self, next):
self._next = next
def getValue(self):
return self._data
def setValue(self, data):
self._data = data
class LinkedQ:
def __init__(self):
self._first = None
self._last = None
self._length = 0
def __str__(self):
s = ""
p = self._first
while p != None:
s = s + str(p.getValue())
p = p.getNext()
return s
def enqueue(self, kort):
ny = Node(kort)
if self._first == None:
self._first = ny
else:
self._last = self._first
while self._last.getNext():
self._last = self._last.getNext()
self._last.setNext(ny)
self._length += 1
def dequeue(self):
data = self._first.getValue()
self._first = self._first.getNext()
self._length = self._length - 1
return data
You're right about linked_list being a local variable, but just because a variable is local doesn't mean it can't reference something that isn't. In order for it to do what you expected, it would need to copy your entire linked list every time you pass it to a function, which wouldn't make sense.
Here's a simple example that illustrates the idea of a shared object. In this example, an empty list is created and assigned to a. Then a is assigned to b. This does not copy the list. Instead, there is a single list, referenced by both a and b. When it is modified, through either a or b, both a and b reflect the change:
>>> a = []
>>> b = a
>>> a.append("x")
>>> a
['x']
>>> b
['x']
>>>
The same thing is happening with your class objects. In fact, your linked lists wouldn't work at all if it didn't.

Behavior of del operator in Python

I am wondering why the following doesn't work.
class Node(object):
def __init__(self, data, next=None):
self.data = data
self.next = next
def remove(self, value):
if self is None:
return False
if self.data == value:
if self.next:
self.data = self.next.data
self.next = self.next.next
else:
del self
else:
self.next.remove(value)
node = Node(4)
node.append(Node(3))
node.remove(3)
print node.next.data
#prints 3
del doesn't delete the element from the linked list. I had to modify the delete() function so that I have a pointer to the parent of the target element.
class Node(object):
def __init__(self, data, next=None):
self.data = data
self.next = next
def remove(self, value):
if self is None:
return False
if self.data == value:
if self.next:
self.data = self.next.data
self.next = self.next.next
else:
del self
else:
current = self
while current.next:
if current.next.data == value:
if current.next.next:
current.next = current.next.next
else:
current.next = None
From the console,
node = Node(4)
current = node
del current #node is not deleted because I am only deleting the pointer
del node #node is deleted
This seems logical to me. However, I am not sure why the first code block doesn't work as I expect.
I ll explain why it doesn't work. But first you need to know that you rarely need to create linked lists in Python since the list type already offer you almost everything.
>>> [2*i for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> list_1=[2*i for i in range(10)]
>>> i=0
>>> list_1[i]#first
0
>>> i=i+1
>>> list_1[i]#next
2
>>> list_1[-1]#last
18
>>> len(list_1)#size
10
>>> del list_1[-1]# del the last
#delete all (list_1 becomes empty)
for i in range(len(list_1)):
del list_1[0]
at the end loop we only delete the first n times but when you del the first in a list the other object go 1 place back.That way you can easily manipulate lists as if they were linked list and delete any elements without worrying about an empty slot. In addition to this, list have several very usefull methods like append, remove, sort, shuffle and more. Look at the docs https://docs.python.org/3.5/library/stdtypes.html?highlight=list#list
Now back to you question:
Let s take a look to your remove method
if self is None:
return False
this is useless, you can only call .remove with an object that has this method.
None.remove() never works. If self is None it will throw an error before any (impossible) call.
else:
del self
When you see self in an Object's method it is just a reference to the object itself, del sel will just delete the local reference. If you really want to destroy an object you must destroy it with del on every reference. Python will forget about your object if no living variable is looking at it. You can also overwrite the reference.
>>> node = Node(4)
>>> node = 3# the Node object is erased because you can t have access to it
Overall, I didn't really understand the goal of your Node class except to create linked lists, but as I said, you can do that with list(). In general you don't need to care about what is deleted or not in Python since Python will overwritte the memory if there is no reference left to an object.

Categories

Resources