Finding a cycle in a DFS directed graph - cycle
I'm trying to find a cycle in an directed graph using a dfs search. I'm reading in the file from a text file that holds all the neighbors of a vertex. Whenever I call the cycle_exists method I keep getting all false, so the answer never changes.
Vertex.py
"""
__version__ = 'October 2015'
Vertex class reads in our graph and
performs a depth first search on it
and performs the transitive closure operation.
Vertex class also checks for cycles in our graph.
"""
import sys
class Graph:
def __init__(self):
"""
Initialize the variable used in Graph
"""
self.dfsPaths = [] #list for dfsPaths
self.VertexList = {} #list for adjacent vertices
def readInputGraph(self, inputFile):
"""
Reads specified input file and stores in
adjacency list
:param inputFile: file to be rad in
:return: the VertexList
"""
file = open(inputFile, 'r') #open the file and read it
for line in file: #for each element in the file
(vertex,val) = line.split() #vertex gets first value in the line, val gets second
if vertex not in self.VertexList: #if vertex not in VertexList
self.VertexList[vertex] = set([val]) #add adjacent pairs
else: #else
self.VertexList.get(vertex).add(val) #add the values
for i in list(self.VertexList.keys()): #for each element in the list of the vertex keys
for j in self.VertexList[i]: # for each vertex that's in i
if j not in self.VertexList: #if j is not in the vertex list
self.VertexList[j] = set() #we add it to the vertex list
return self.VertexList #return list of adjacent vertices
def dfsSearch(self, graph, start, end, path = []):
"""
Performs a depth first search on
the graph that is read in from the file
:param graph: the graph that we are performing the search on
:param start: the starting vertex
:param end: the target vertex
:param path: a list of the paths
:return: the paths from the search
"""
path = path + [start] #path
if start == end: #if the start element and end element are the same
return [path] #return the list of paths
if start not in graph: #if the start element is not in the graph
print( 'Not Found')#prints out not found
return [] #return an empty list
paths = [] #path list
for node in graph[start]: #for node in the graph
if node not in path: #if not in the path
newpaths = self.dfsSearch(graph, node, end, path) #new paths we found
for newpath in newpaths: #for each new path in the list of new paths
paths.append(newpath) #add the new path to our list of paths
paths.sort() #sort our paths
self.cycle_exists(graph)
#print(self.cycle_exists(graph))
return paths #return our paths
def cycle_exists(self, graph): # -graph is our graph.
color = { node : "white" for node in graph} #color all nodes white to begin with
found_cycle = False # found_cycle set to false
for node in graph: # for each node in graph.
if color[node]:#if the color[node] is white
self.dfs_visit(graph, node, color, found_cycle) #we call the dfs_visit method
if found_cycle:#if a cycle is found
found_cycle = True
break#break
return found_cycle #return the true or false
def dfs_visit(self,graph, node, color, found_cycle):
#print(color)
if found_cycle: # if a cycle is found return to the cycle_exists method
return
color[node] = "gray"#else color the node gray
for neighbor in graph[node]: #for every neighbor in the graph of the node
if color[neighbor] == "gray": #If neighbor is gray
found_cycle = True # then a cycle exists.
return
if color[neighbor] == "white": #if the neighbor is white
#print(color[neighbor])
self.dfs_visit(graph, neighbor, color, found_cycle)# call dfs_visit .
color[node] = "black"# color the original node black
GraphDriver.py
from Vertex import *
import sys
class GraphDriver:
def __init__(self):
self.graph = Graph()
def main():
graph = Graph()
inFile = sys.argv[1]
d = graph.readInputGraph(inFile)
userInput = input("Enter a source and destination:")
dog = userInput.split(" ", -1)
for path in graph.dfsSearch(d, dog[0], dog[1]):
print(path)
if __name__ == '__main__':
main()
Input.txt
0 1
0 6
1 2
1 5
2 3
2 4
4 3
4 0
5 4
6 5
The problem with your code is that it expects the boolean variable found_cycle to be passed by-reference to dfs_visit. However, Python does pass by-value (link, link). Therefore, when dfs_visit sets the parameter found_cycle to True, this modification will not affect the found_cycle variable passed into dfs_visit by the caller.
You can fix this by changing dfs_visit to return whether a cycle was found:
def cycle_exists(self, graph):
color = { node : "white" for node in graph}
for node in graph:
if color[node] == "white":
if self.dfs_visit(graph, node, color):
return True
return False
def dfs_visit(self,graph, node, color):
color[node] = "gray"
for neighbor in graph[node]:
if color[neighbor] == "gray":
return True
if color[neighbor] == "white":
if self.dfs_visit(graph, neighbor, color):
return True
color[node] = "black"
return False
Background information
Regarding the passing of boolean variables, consider the following example:
def f(my_bool):
my_bool = True
my_bool = False
f(my_bool)
print(my_bool)
This code will print False. In the global scope, the variable my_bool is initialized with False and is then passed to f. It is passed by-value, so in f the parameter my_bool receives the value False. However, this variable my_bool is unrelated to the variable my_bool outside f. So modifying the my_bool in f does not affect the my_bool outside f.
Note that this does not mean that you cannot pass references to objects, only that the reference is passed by-value. Consider the following example:
class MyObject:
def __init__(self):
self.x = 13
def f(my_object):
my_object.x = 17
def g(my_object):
my_object = MyObject()
my_object.x = 19
my_object = MyObject()
print(my_object.x)
f(my_object)
print(my_object.x)
g(my_object)
print(my_object.x)
This example prints:
13
17
17
In the global scope, the variable my_object is initialized with an instance of MyObject. The constructor of MyObject initializes the member variable x to 13, which is the first printed value. The my_object variable is then passed to the function f. The variable is passed by-value, so the variable my_object in f is a different variable than the variable my_object in the global scope. However, both point to the same instance of MyObject. So when f sets my_object.x = 17, the next print in the global scope will show this value.
Next, in the global scope the variable my_object is passed to g. Again, the variable is passed by-value, so the variable my_object in g is different from the variable my_object in the global scope, but both point to the same instance of MyObject. In g the my_object variable is then assigned a new instance of MyObject. This does not affect the my_object in the global scope, which still points to the previous instance of MyObject. Therefore, the final print in the global scope will still show the 17 that has been assigned to the x of the first instance of MyObject, not the 19 that was assigned in g to the x in the second instance of MyObject.
So this is why e.g. your color variable is not affected by the same problem as your found_cycle variable. For color, you pass by-value for each call to dfs_visit, as for found_cycle, but you never assign a new value to color in dfs_visit. Therefore, the modifications done to color are done to the same object as pointed to by the original color variable in the cycle_exists function.
Related
Leetcode 126: Word Ladder 2 in Python code optimization
I have the solution for the Word Ladder 2 (Leetcode problem 126: Word Ladder 2 ) in Python 3.6, and I notice that one of the very last testcases times out for me on the platform. Funnily, the test passes when run on PyCharm or as an individual test case on the site, but it takes about 5 seconds for it to complete. My solution uses BFS with some optimizations, but can someone tell me if there is a way to make it faster. Thank you! (P.S: Apologies for the additional test cases included in the commented out section!) import math import queue from typing import List class WordLadder2(object): #staticmethod def is_one_hop_away(s1: str, s2: str) -> int: """ Uses the distance between strings to return True if string s2 is one character away from s1 :param s1: Base string :param s2: Comparison string :return: True if it the difference between the strings is one character """ matrix = [[0] * (len(s1) + 1) for i in range(len(s1) + 1)] for r, row in enumerate(matrix): for c, entry in enumerate(row): if not r: matrix[r][c] = c elif not c: matrix[r][c] = r else: if s1[r - 1] == s2[c - 1]: matrix[r][c] = matrix[r - 1][c - 1] else: matrix[r][c] = 1 + min(matrix[r - 1][c - 1], matrix[r - 1][c], matrix[r][c - 1]) if matrix[-1][-1] == 1: return True else: return False def get_next_words(self, s1: str, wordList: List[str]) -> List[str]: """ For a given string in the list, return a set of strings that are one hop away :param s1: String whose neighbors one hop away are needed :param wordList: Array of words to choose from :return: List of words that are one character away from given string s1 """ words = [] for word in wordList: if self.is_one_hop_away(s1, word): words.append(word) return words def find_ladders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]: """ Main method to determine shortest paths between a beginning word and an ending word, in a given list of words :param beginWord: Word to begin the ladder :param endWord: Word to end the ladder :param wordList: List of words to choose from :return: List of list of word ladders, if they are found. Empty list, if endWord not in wordList or path not found from beginWord to endWord """ q = queue.Queue() paths = list() current = [beginWord] q.put((beginWord, current)) # Set to track words we have already processed visited = set() # Dictionary to keep track of the shortest path lengths to each word from beginWord shortest_paths = {beginWord: 1} min_length = math.inf # Use BFS to find the shortest path in the graph while q.qsize(): word, path = q.get() # If endWord is found, add the current path to the list of paths and compute minimum path # length found so far if word == endWord: paths.append(path) min_length = min(min_length, len(path)) continue for hop in self.get_next_words(word, wordList): # If the hop is already processed or in the queue for processing, skip if hop in visited or hop in q.queue: continue # If the shortest path to the hop has not been determined or the current path length is lesser # than or equal to the known shortest path to the hop, add it to the queue and update the shortest # path to the hop. if (hop not in shortest_paths) or (hop in shortest_paths and len(path + [hop]) <= shortest_paths[hop]): q.put((hop, path + [hop])) shortest_paths[hop] = len(path + [hop]) visited.add(word) return [s for s in paths if len(s) == min_length] if __name__ == "__main__": # beginword = 'qa' # endword = 'sq' # wordlist = ["si","go","se","cm","so","ph","mt","db","mb","sb","kr","ln","tm","le","av","sm","ar","ci","ca","br","ti","ba","to","ra","fa","yo","ow","sn","ya","cr","po","fe","ho","ma","re","or","rn","au","ur","rh","sr","tc","lt","lo","as","fr","nb","yb","if","pb","ge","th","pm","rb","sh","co","ga","li","ha","hz","no","bi","di","hi","qa","pi","os","uh","wm","an","me","mo","na","la","st","er","sc","ne","mn","mi","am","ex","pt","io","be","fm","ta","tb","ni","mr","pa","he","lr","sq","ye"] # beginword = 'hit' # endword = 'cog' # wordlist = ['hot', 'dot', 'dog', 'lot', 'log', 'cog'] # beginword = 'red' # endword = 'tax' # wordlist = ['ted', 'tex', 'red', 'tax', 'tad', 'den', 'rex', 'pee'] beginword = 'cet' endword = 'ism' wordlist = ["kid","tag","pup","ail","tun","woo","erg","luz","brr","gay","sip","kay","per","val","mes","ohs","now","boa","cet","pal","bar","die","war","hay","eco","pub","lob","rue","fry","lit","rex","jan","cot","bid","ali","pay","col","gum","ger","row","won","dan","rum","fad","tut","sag","yip","sui","ark","has","zip","fez","own","ump","dis","ads","max","jaw","out","btu","ana","gap","cry","led","abe","box","ore","pig","fie","toy","fat","cal","lie","noh","sew","ono","tam","flu","mgm","ply","awe","pry","tit","tie","yet","too","tax","jim","san","pan","map","ski","ova","wed","non","wac","nut","why","bye","lye","oct","old","fin","feb","chi","sap","owl","log","tod","dot","bow","fob","for","joe","ivy","fan","age","fax","hip","jib","mel","hus","sob","ifs","tab","ara","dab","jag","jar","arm","lot","tom","sax","tex","yum","pei","wen","wry","ire","irk","far","mew","wit","doe","gas","rte","ian","pot","ask","wag","hag","amy","nag","ron","soy","gin","don","tug","fay","vic","boo","nam","ave","buy","sop","but","orb","fen","paw","his","sub","bob","yea","oft","inn","rod","yam","pew","web","hod","hun","gyp","wei","wis","rob","gad","pie","mon","dog","bib","rub","ere","dig","era","cat","fox","bee","mod","day","apr","vie","nev","jam","pam","new","aye","ani","and","ibm","yap","can","pyx","tar","kin","fog","hum","pip","cup","dye","lyx","jog","nun","par","wan","fey","bus","oak","bad","ats","set","qom","vat","eat","pus","rev","axe","ion","six","ila","lao","mom","mas","pro","few","opt","poe","art","ash","oar","cap","lop","may","shy","rid","bat","sum","rim","fee","bmw","sky","maj","hue","thy","ava","rap","den","fla","auk","cox","ibo","hey","saw","vim","sec","ltd","you","its","tat","dew","eva","tog","ram","let","see","zit","maw","nix","ate","gig","rep","owe","ind","hog","eve","sam","zoo","any","dow","cod","bed","vet","ham","sis","hex","via","fir","nod","mao","aug","mum","hoe","bah","hal","keg","hew","zed","tow","gog","ass","dem","who","bet","gos","son","ear","spy","kit","boy","due","sen","oaf","mix","hep","fur","ada","bin","nil","mia","ewe","hit","fix","sad","rib","eye","hop","haw","wax","mid","tad","ken","wad","rye","pap","bog","gut","ito","woe","our","ado","sin","mad","ray","hon","roy","dip","hen","iva","lug","asp","hui","yak","bay","poi","yep","bun","try","lad","elm","nat","wyo","gym","dug","toe","dee","wig","sly","rip","geo","cog","pas","zen","odd","nan","lay","pod","fit","hem","joy","bum","rio","yon","dec","leg","put","sue","dim","pet","yaw","nub","bit","bur","sid","sun","oil","red","doc","moe","caw","eel","dix","cub","end","gem","off","yew","hug","pop","tub","sgt","lid","pun","ton","sol","din","yup","jab","pea","bug","gag","mil","jig","hub","low","did","tin","get","gte","sox","lei","mig","fig","lon","use","ban","flo","nov","jut","bag","mir","sty","lap","two","ins","con","ant","net","tux","ode","stu","mug","cad","nap","gun","fop","tot","sow","sal","sic","ted","wot","del","imp","cob","way","ann","tan","mci","job","wet","ism","err","him","all","pad","hah","hie","aim"] wl = WordLadder2() # beginword = 'hot' # endword = 'dog' # wordlist = ['hot', 'dog', 'dot'] print(wl.find_ladders(beginword, endword, wordlist))
The part that slows down your solution is is_one_hop_away, which is a costly function. This is called repeatedly during the actual BFS. Instead you should aim to first create a graph structure -- an adjacency list -- so that complexity of calculating which words are neighbors is dealt with before actually peforming the BFS search. Here is one way to do it: from collections import defaultdict class Solution: def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]: def createAdjacencyList(wordList): adj = defaultdict(set) d = defaultdict(set) for word in wordList: for i in range(len(word)): derived = word[:i] + "*" + word[i+1:] for neighbor in d[derived]: adj[word].add(neighbor) adj[neighbor].add(word) d[derived].add(word) return adj def edgesOnShortestPaths(adj, beginWord, endWord): frontier = [beginWord] edges = defaultdict(list) edges[beginWord] = [] while endWord not in frontier: nextfrontier = set(neighbor for word in frontier for neighbor in adj[word] if neighbor not in edges ) if not nextfrontier: # endNode is not reachable return for word in frontier: for neighbor in adj[word]: if neighbor in nextfrontier: edges[neighbor].append(word) frontier = nextfrontier return edges def generatePaths(edges, word): if not edges[word]: yield [word] else: for neighbor in edges[word]: for path in generatePaths(edges, neighbor): yield path + [word] if endWord not in wordList: # shortcut exit return [] adj = createAdjacencyList([beginWord] + wordList) edges = edgesOnShortestPaths(adj, beginWord, endWord) if not edges: # endNode is not reachable return [] return list(generatePaths(edges, endWord))
Numpy , OOP and callables
I'm implementing a Markov Chain Montecarlo with metropolis and barkes alphas for numerical integration. I've created a class called MCMCIntegrator(). I've loaded it with some attributes, one of then is the pdf of the function (a lambda) we're trying to integrate called g. import numpy as np import scipy.stats as st class MCMCIntegrator: def __init__(self): self.g = lambda x: st.gamma.pdf(x, 0, 1, scale=1 / 1.23452676)*np.abs(np.cos(1.123454156)) self.size = 10000 self.std = 0.6 self.real_int = 0.06496359 There are other methods in this class, the size is the size of the sample that the class must generate, std is the standard deviation of the Normal Kernel, which you will see in a few seconds. The real_int is the value of the integral from 1 to 2 of the function we're integrating. I've generated it with a R script. Now, to the problem. def _chain(self, method=None): """ Markov chain heat-up with burn-in :param method: Metrpolis or barker alpha :return: np.array containing the sample """ old = 0 sample = np.zeros(int(self.size * 1.5)) i = 0 if method: def alpha(a, b): return min(1, self.g(b) / self.g(a)) else: def alpha(a, b): return self.g(b) / (self.g(a) + self.g(b)) while i != len(sample): if new < 0: new = st.norm(loc=old, scale=self.std).rvs() alpha = alpha(old, new) u = st.uniform.rvs() if alpha > u: sample[i] = new old = new i += 1 return np.array(sample) When I call the _chain() method, this is the following error: 44 while i != len(sample): 45 new = st.norm(loc=old, scale=self.std).rvs() ---> 46 alpha = alpha(old, new) 47 u = st.uniform.rvs() 48 TypeError: 'numpy.float64' object is not callable alpha returns a nnumpy.float, but I don't know why it's saying it's not callable.
You define a method named alpha based on some condition in an 'early' section of the code: if method: def alpha(a, b): return min(1, self.g(b) / self.g(a)) else: def alpha(a, b): return self.g(b) / (self.g(a) + self.g(b)) and then in the while loop (a 'later' part of the code), you assign the return value of this function to a variable named alpha. Since the names of these two objects are same, and the variable has been declared later in the code, without the function being re-declared anywhere after this variable creation, the variable replaces the function in the namespace and now you can't make calls to alpha anymore, because it has ceased to be a function. If it is not a hindrance to your program logic (doesn't seem to be), renaming the variable to some other nice name would be okay.
Tensorflow - running total
How can I add the number 5 after every iteration of the loop? I want to do something like this: weight = 0.225 for i in range(10): weight += 5 print (weight) Here is how I am trying in tensorflow but it never updates the weight import tensorflow as tf def dummy(x): weights['h0'] = tf.add(weights['h0'], 5) res = tf.add(weights['h0'], x) return res # build computational graph a = tf.placeholder('float', None) d = dummy(a) weights = { 'h0': tf.Variable(tf.random_normal([1])) } # initialize variables init = tf.global_variables_initializer() # create session and run the graph with tf.Session() as sess: sess.run(init) for i in range(10): print (sess.run(d, feed_dict={a: [2]})) # close session sess.close()
There's an operation explicitly created for adding a value and assigning the result back to the input node: tf.assign_add You should use it instead of tf.assing + tf.add. Also, it's more important that you understand why you previous code won't work. weights['h0'] = tf.add(weights['h0'], 5) res = tf.add(weights['h0'], x) At the fist line, you're defining a node add, whose inputs are weights['h0'] and 5 and you're assigning this node to a python variable weights['h0']. Now, thus, weights['h0'] is a python variable holding a tensorflow node. In the next line, you're defining another add node, between the previous node and x, and you return this node. When the graph is evaluated, you evaluate the node pointed by res, that force the evaluation of the previous node (because res is a function of the node holded by weights['h0']). The problem is the that your assignment at line 1 is a python assignment and not a tensorflow assignment. Thus that assign operation is executed only in the python environment but it has no defined an assign node into the tensorflow graph. P.S: when you use with you're defining a context manager that handles the closing operations for you. You can thus remove sess.close() because is executed automatically when you exit from that context
Apparently there is an assign operator https://www.tensorflow.org/api_docs/python/tf/assign weights['h0'] = tf.assign(weights['h0'], tf.add(weights['h0'], 5))
Two Class instances in Python not different
I'm working on another data acquisition project, which has turned into an object oriented programming question. In “main” at the bottom of my code I make two instances of the Object DAQInput. When I wrote this, I thought my method .getData would refer to the taskHandle of the particular instance, but it does not. When I run, the code does the getData task with the first handle twice, so clearly I don’t really understand object oriented programming in Python. I’m sorry this code will not run without PyDAQmx and a National Instruments board attached. from PyDAQmx import * import numpy class DAQInput: # Declare variables passed by reference taskHandle = TaskHandle() read = int32() data = numpy.zeros((10000,),dtype=numpy.float64) sumi = [0,0,0,0,0,0,0,0,0,0] def __init__(self, num_data, num_chan, channel, high, low): """ This is init function that opens the channel""" #Get the passed variables self.num_data = num_data self.channel = channel self.high = high self.low = low self.num_chan = num_chan # Create a task and configure a channel DAQmxCreateTask(b"",byref(self.taskHandle)) DAQmxCreateAIThrmcplChan(self.taskHandle, self.channel, b"", self.low, self.high, DAQmx_Val_DegC, DAQmx_Val_J_Type_TC, DAQmx_Val_BuiltIn, 0, None) # Start the task DAQmxStartTask(self.taskHandle) def getData(self): """ This function gets the data from the board and calculates the average""" print(self.taskHandle) DAQmxReadAnalogF64(self.taskHandle, self.num_data, 10, DAQmx_Val_GroupByChannel, self.data, 10000, byref(self.read), None) # Calculate the average of the values in data (could be several channels) i = self.read.value for j in range(self.num_chan): self.sumi[j] = numpy.sum(self.data[j*i:(j+1)*i])/self.read.value return self.sumi def killTask(self): """ This function kills the tasks""" # If the task is still alive kill it if self.taskHandle != 0: DAQmxStopTask(self.taskHandle) DAQmxClearTask(self.taskHandle) if __name__ == '__main__': myDaq1 = DAQInput(1, 4, b"cDAQ1Mod1/ai0:3", 200.0, 10.0) myDaq2 = DAQInput(1, 4, b"cDAQ1Mod2/ai0:3", 200.0, 10.0) result = myDaq1.getData() print (result[0:4]) result2 = myDaq2.getData() print (result2[0:4]) myDaq1.killTask() myDaq2.killTask()
These variables: class DAQInput: # Declare variables passed by reference taskHandle = TaskHandle() read = int32() data = numpy.zeros((10000,),dtype=numpy.float64) sumi = [0,0,0,0,0,0,0,0,0,0] Are class variables. They belong to the class itself and are shared among instances of the class (i.e. if you modify self.data in Instance1, Instace2's self.data is modified as well). If you want them to be instance variables, define them in __init__.
pygame - python getting variables with the visiter pattern
I'm trying to use the visiter pattern to get self.coin value from the first class and return it to the method in the second class but its not working, its always returning none... can anyone help? class coin_collector(Observer): def __init__(self): super(Observer, self).__init__() self.coin_count = 0 self.run = True self.coin = 0 def acceptVisitor(self, visitor): visitor.visit(self) def update(self, observable, other): me = coin_collector() me.coin_Count(other, True) def coin_Count(self, value, TF): run = TF if run: self.coin = value print self.coin return self.coin def __str__(self): return self.__class__.__name__ #this is part of a different class in a different file def visit(self, location): location.coin_Count(0, False) def update(self): visitee = coin_collector() self.c_Count = self.visit(visitee) # for some reason this always returns none print self.c_Count, "working" # this always prints none...
Ok, so let's start with Coin Collector. His coin attribute is set to 0. You also have a second class with an update and visit function. The update function creates a new coin_collector at every call. Then it assigns the return value of visit function to self.c_Count. Let's now see the visit function. It takes in a brand new coin collector and returns None It would assign its value parameter to the coin field if you would pass True as TF. After coming back from coin_count function, you do not do anything in the visit function so the return value is lost. That is why, when you try to assign the result of self.visit, you get a None.