i am trying to count paths with a recursive function
if for example i have the given instructions
instructions = {
1: (('bot', 3), ('bot', 4)),
2: (('bot', 4), ('output', 0)),
3: (('output', 5), ('bot', 5)),
4: (('bot', 5), ('bot', 6)),
5: (('output', 1), ('bot', 7)),
6: (('bot', 7), ('output', 4)),
7: (('output', 2), ('output', 3))
}
`
represented by this picture
in this in example there are 3 paths from bot 5 (5-1, 5-7-2, 5-7-3). There are 6 paths from bot 4 (4-5-1, 4-5-7-2, 4-5-7-3, 4-6-7-2, 4-6-7-3, 4-6-4).
this is what i have tried so far but i have no success
def count_path(bot, instructions):
counter = 0
b = "bot"
outp = "output"
while True:
for x, y in instructions[bot]:
if x == b:
count_path(y, instructions)
elif x == outp:
counter += 1
return counter
For the sake of clarity, I changed the names from bot to node - representing a point that connects forward to other points, and output to end - representing a point that does not connect forward
instructions = {1: (('node', 3), ('node', 4)),
2: (('node', 4), ('end', 0)),
3: (('end', 5), ('node', 5)),
4: (('node', 5), ('node', 6)),
5: (('end', 1), ('node', 7)),
6: (('node', 7), ('end', 4)),
7: (('end', 2), ('end', 3))}
def count_path(current_node, instructions):
connections = instructions[current_node]
paths = 0
for connection in connections:
if connection[0] == "node":
paths += count_path(connection[1])
elif connection[0] == "end":
paths += 1
return paths
Basically, the recursion works by checking to see if points that connect to the current point are "node" or "end", if they are nodes, a new stack is created (ie. the function is called again) but starting from the nodes that connect to the new current node, it then checks if those points that connect to the new nodes are also nodes.
This continues until a "end" is reached. When this occurs it 1 is added to paths. The number of paths reached in the current stack are this returned to the stack above.
Recursion can be hard to visualise, but essentially as you reach end points and move back up the stacks, you are returning the total number of end points in that direction, and the top stack will therefore return the total number of end points (which is equivalent to the total number of paths).
You need to add the returned value from count_path and remove the while = true.
def count_path(bot, instructions):
counter = 0
b = "bot"
outp = "output"
for x, y in instructions[bot]:
if x == b:
counter += count_path(y, instructions)
else:
counter += 1
return counter
In Recursion you have a base case which in your case is when you meet a dead end i.e. an output element.
Lets suppose you are at bot a, if next element is bot b, then
no. path from bot a += no. of paths from bot b
Otherwise if the next element is a output, then add one because that is a termination of one of the paths you are counting.
This is a simple but working solution. However it only counts paths but not collect them.
instructions = {
1: (('bot', 3), ('bot', 4)),
2: (('bot', 4), ('output', 0)),
3: (('output', 5), ('bot', 5)),
4: (('bot', 5), ('bot', 6)),
5: (('output', 1), ('bot', 7)),
6: (('bot', 7), ('output', 4)),
7: (('output', 2), ('output', 3))
}
def count_paths(bot):
n_paths = 0
entry = instructions[int(bot)]
for path_type, bot_number in entry:
if path_type == 'output':
n_paths += 1
elif path_type == 'bot':
n_paths += count_paths(bot_number)
return n_paths
if __name__ == '__main__':
bot = input("Count path for: ")
paths = count_paths(bot)
print("For bot {bot} there are {paths} paths".format(bot=bot, paths=paths))
Related
I have the following dictionary with the first number being the node, followed by a tuple consisting of a neighbor and then the edge weight (some nodes have multiple neighbors and weights):
dictionary = {1: [(2, 3), (4, 5), (6, 2)], 2: [(3, -4)], 3: [(8, 4)], 4: [(5, 6)], 5: [(4, -3), (8, 8)], 6: [(7, 3)], 7: [(6, -6), (8, 7)]}
How would I use Depth First Search (DFS) to detect if there is a cycle or not? The dictionary will be changed frequently for testing purposes, so some may have cycles and some may not. I am new to python and do not understand how to implement DFS to check for a cycle or not. I have the following DFS code:
visited = set() # Set to keep track of visited nodes of graph.
def dfs(visited, dictionary, node): #function for dfs
if node not in visited:
print (node)
visited.add(node)
for neighbour in dictionary[node]:
dfs(visited, dictionary, neighbour)
and then I call the function: dfs(visited, dictionary, '1').
But I get the error KeyError: '1'. I also am unsure how to use this code to detect for a cycle or not.
First we consider the base cases. In this instance, we wish to evaluate whether the graph has a cycle or not. The function should return true when a cycle is detected, and false if no such cycle exists after all edges have been examined.
dictionary = {1: [(2, 3)], 2: [], 3: [(4, 4)], 4: [(3, 6)]}
def DFS(visited, dictionary, node):
if node in visited:
return True
else:
for neighbour, weight in dictionary[node]:
if DFS(visited | {node}, dictionary, neighbour):
return True
return False
print(f"{DFS(set(), dictionary, 1)=}") # False
Depth first search will examine all paths from a single node, going exploring as far as it can before backtracking. If it encounters a node it has seen before, the search will stop and the function returns true. If all paths from the starting node have been exhausted, it returns false. This will only detect whether node 1 is involved in any cycles.
However, this approach is not enough to detect cycles which does not involve the starting node. In the above example, the DFS will not find the cycle between nodes 3 and 4, as it starts its search from 1.
To fix this, we can write another function which examines all the nodes with the DFS.
def containsCycle(dictionary):
for node in dictionary.keys():
if DFS(set(), dictionary, node):
return True
return False
print(f"{containsCycle(dictionary)=}") # True
dictionary = {1: [(2, 3), (4, 5), (6, 2)], 2: [(3, -4)],
3: [(8, 4)], 4: [(5, 6)], 5: [(4, -3), (8, 8)],
6: [(7, 3)], 7: [(6, -6), (8, 7)]}
The dictionary above has a edges to a node 8, without an entry for that node in the dictionary. This will cause an error. To resolve this, we can add a guard clause to our recursive function.
def DFS(visited, dictionary, node):
if node in visited:
return True
elif node not in dictionary: # Guard clause
return False
else:
for neighbour, weight in dictionary[node]:
if DFS(visited | {node}, dictionary, neighbour):
return True
return False
I am attempting to implement an "all paths" algorithm described in this question's top answer using Python.
So far, I have defined the function as such:
def AllPaths(start, end, edges):
print(start)
if (start == end):
return
for i in range(0, len(edges)):
if edges[i][1] == start:
AllPaths(edges[i][0], end, edges)
I have included print(start) to try give myself an idea of the function's behaviour.
For example, if the initial function call is as such:
edges = [ (1, 2), (1, 3), (2, 3), (3, 4) ]
AllPaths(edges[len(edges) - 1][1], edges[0][0], edges)
With the starting node being 4 and the ending node being 1, the output of the function is:
4
3
1
2
1
Somewhere in that output, it is telling me that all paths between node 4 and node 1 consists of:
[ (4, 3), (3, 1) ]
[ (4, 3), (3, 2), (2, 1) ]
But how do I form these collections during execution and, if I wanted to keep track of how many distinct paths there are, how would this be counted during execution?
If you don't mind that I won't follow the backward traversal route, which I find rather odd, then here's a suggestion on how to modify your function to see what's going on:
def all_paths(start, end, edges):
if start == end:
return [[end]]
paths = []
for edge in edges:
if edge[0] == start:
paths += [[start] + path
for path in all_paths(edge[1], end, edges)]
return paths
Output for edges = [(1, 2), (1, 3), (2, 3), (3, 4)] and start = 1; end = 4:
[[1, 2, 3, 4],
[1, 3, 4]]
If you want a direct edges-result then I'd suggest:
def all_paths(start, end, edges):
paths = []
for edge in edges:
if edge[0] == start:
if edge[1] == end:
return [[edge]]
else:
paths += [[edge] + path
for path in all_paths(edge[1], end, edges)]
return paths
Output for the same input:
[[(1, 2), (2, 3), (3, 4)],
[(1, 3), (3, 4)]]
But a warning: If you have loops in the graph, e.g. edges = [(1, 2), (1, 3), (3, 1), (2, 3), (3, 4)], then these algorithms will crash (recursion won't stop). In these cases something like
def all_paths(start, end, edges):
edges = set(edges)
paths = []
for edge in edges:
if edge[0] == start:
if edge[1] == end:
paths += [[start, end]]
else:
new_edges = edges.difference({edge})
paths += [[start] + path
for path in all_paths(edge[1], end, new_edges)]
return paths
which removes visited edges is better. Result for start = 1; end = 4:
[[1, 2, 3, 1, 3, 4],
[1, 2, 3, 4],
[1, 3, 1, 2, 3, 4],
[1, 3, 4]]
I hope that this is at least a little bit what you were looking for.
The list of tuples is the output from a capacitated vehicle-routing optimization and represents the arcs from one stop to another, where 0 represents the depot of the vehicle (start and end point of vehicle). As the vehicle must drive several laps it may return to the depot before all stops were made. The solver will always return the starting-arcs first, which in the example below means that the first consecutive tuples starting with 0, namely (0, 3), (0, 7), (0, 8) will determine how many laps (= 3) there are.
How can I sort the arcs in consecutive order so that one vehicle could drive the arcs one after another?
Input:
li = [(0, 3), (0, 7), (0, 8), (3, 0), (4, 0), (7, 3), (8, 4), (3, 0)]
Output:
[(0, 3), (3, 0), (0, 7), (7, 3), (3, 0), (0, 8), (8, 4), (4, 0)]
What I tried so far:
laps = 0
for arc in li:
if arc[0] == 0:
laps = laps + 1
new_list = []
for i in range(laps):
value = li.pop(0)
new_list.append([value])
for i in range(laps):
while new_list[i][-1][1] != 0:
arc_end = new_list[i][-1][1]
for j in range(len(li)):
if li[j][0] == arc_end:
value = li.pop(j)
new_list[i].append(value)
break
flat_list = [item for sublist in new_list for item in sublist]
return flat_list
You are trying to solve the problem of finding cycles in a directed graph. The problem itself is not a difficult one to solve, and Python has a very good package for solving such problems - networkx. It would be a good idea to learn a bit about fundamental graph algorithms. There is also another stack overflow question about this algorithm, which you can consult, at Finding all cycles in a directed graph
I have a list of tuples called possible_moves containing possible moves on a board in my game:
[(2, 1), (2, 2), (2, 3), (3, 1), (4, 5), (5, 2), (5, 3), (6, 0), (6, 2), (7, 1)]
Then, I have a dictionary that assigns a value to each cell on the game board:
{(0,0): 10000, (0,1): -3000, (0,2): 1000, (0,3): 800, etc.}
I want to iterate over all possible moves and find the move with the highest value.
my_value = 0
possible_moves = dict(possible_moves)
for move, value in moves_values:
if move in possible_moves and possible_moves[move] > my_value:
my_move = possible_moves[move]
my_value = value
return my_move
The problem is in the part for move, value, because it creates two integer indexes, but I want the index move to be a tuple.
IIUC, you don't even need the list of possible moves. The moves and their scores you care about are already contained in the dictionary.
>>> from operator import itemgetter
>>>
>>> scores = {(0,0): 10000, (0,1): -3000, (0,2): 1000, (0,3): 800}
>>> max_move, max_score = max(scores.items(), key=itemgetter(1))
>>>
>>> max_move
(0, 0)
>>> max_score
10000
edit: turns out I did not understand quite correctly. Assuming that the list of moves, let's call it possible_moves, contains the moves possible right now and that the dictionary scores contains the scores for all moves, even the impossible ones, you can issue:
max_score, max_move = max((scores[move], move) for move in possible_moves)
... or if you don't need the score:
max_move = max(possible_moves, key=scores.get)
You can use max with dict.get:
possible_moves = [(2, 1), (2, 2), (2, 3), (3, 1), (4, 5), (5, 2),
(5, 3), (6, 0), (6, 2), (7, 1), (0, 2), (0, 1)]
scores = {(0,0): 10000, (0,1): -3000, (0,2): 1000, (0,3): 800}
res = max(possible_moves, key=lambda x: scores.get(x, 0)) # (0, 2)
This assumes moves not found in your dictionary have a default score of 0. If you can guarantee that every move is included as a key in your scores dictionary, you can simplify somewhat:
res = max(possible_moves, key=scores.__getitem__)
Note the syntax [] is syntactic sugar for __getitem__: if the key isn't found you'll meet KeyError.
If d is a dict, iterator of d generates keys. d.items() generates key-value pairs. So:
for move, value in moves_values.items():
possibleMoves=[(2, 1), (2, 2), (2, 3), (3, 1), (4, 5), (5, 2),(0, 3),(5, 3), (6, 0), (6, 2), (7, 1),(0,2)]
movevalues={(0,0): 10000, (0,1): -3000, (0,2): 1000, (0,3): 800}
def func():
my_value=0
for i in range(len(possibleMoves)):
for k,v in movevalues.items():
if possibleMoves[i]==k and v>my_value:
my_value=v
return my_value
maxValue=func()
print(maxValue)
I'm following some online courses and I have this function sort but nothing nothing seems to run after the print "here" part:
import unittest
def sort(meetings, indx):
print("call function")
print meetings
firstfirst = meetings[indx][0]
firstsecond = meetings[indx][1]
secondfirst = meetings[indx+1][0]
secondsecond = meetings[indx+1][1]
first = meetings[indx]
second = meetings[indx+1]
print firstfirst
print secondfirst
if firstfirst > secondfirst:
meetings[indx] = second
meetings[indx+1] = first
print "here"
indx = index + 1
print "meetings: "
sort(meetings[indx:len(meetings)-1], indx)
def merge_ranges(meetings):
# Merge meeting range
sort(meetings, 0)
return []
# Tests
class Test(unittest.TestCase):
def test_meetings_overlap(self):
actual = merge_ranges([(1, 3), (2, 4)])
expected = [(1, 4)]
self.assertEqual(actual, expected)
def test_meetings_touch(self):
actual = merge_ranges([(5, 6), (6, 8)])
expected = [(5, 8)]
self.assertEqual(actual, expected)
def test_meeting_contains_other_meeting(self):
actual = merge_ranges([(1, 8), (2, 5)])
expected = [(1, 8)]
self.assertEqual(actual, expected)
def test_meetings_stay_separate(self):
actual = merge_ranges([(1, 3), (4, 8)])
expected = [(1, 3), (4, 8)]
self.assertEqual(actual, expected)
def test_multiple_merged_meetings(self):
actual = merge_ranges([(1, 4), (2, 5), (5, 8)])
expected = [(1, 8)]
self.assertEqual(actual, expected)
def test_meetings_not_sorted(self):
actual = merge_ranges([(5, 8), (1, 4), (6, 8)])
expected = [(1, 4), (5, 8)]
self.assertEqual(actual, expected)
def test_sample_input(self):
actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)])
expected = [(0, 1), (3, 8), (9, 12)]
self.assertEqual(actual, expected)
unittest.main(verbosity=2)
the output shows this and only throws errors for the test cases (which I didn't include) since those are to be expected...
call function
[(1, 8), (2, 5)]
1
2
here
call function
[(5, 8), (1, 4), (6, 8)]
5
1
here
call function
[(1, 3), (2, 4)]
1
2
here
call function
[(1, 3), (4, 8)]
1
4
here
call function
[(5, 6), (6, 8)]
5
6
here
call function
[(1, 4), (2, 5), (5, 8)]
1
2
here
call function
[(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]
0
3
here
"but nothing nothing seems to run after the print "here" part"
Are you basing this on the fact that nothing else prints? If so thats because you have to print the variables you change. Also, none of your functions return anything you have worked on within the function and while sort mutates the meetings variable it has no way of knowing when to stop calling itself, it will just eventually throw an error when trying to index into an empty list held in the meetings variable. Even your use of print is confusing. You use print("call function") up top then print meetings afterwards mixing python 2 & 3 print syntax.
But let's get to the heart of your problem here.
def sort(meetings, indx):
print("call function")
print meetings
# eventually meetings will be an empty list and meetings[indx]
# will throw an IndexError
firstfirst = meetings[indx][0]
firstsecond = meetings[indx][1]
secondfirst = meetings[indx+1][0]
secondsecond = meetings[indx+1][1]
first = meetings[indx]
second = meetings[indx+1]
print firstfirst
print secondfirst
if firstfirst > secondfirst:
meetings[indx] = second
meetings[indx+1] = first
# "here" is printed
print "here"
# you alter the indx variable but do not print it
indx = index + 1
# "meetings:" is printed but nothing else is printed below it
print "meetings: "
# sort calls itself without any condition to stop calling itself
# and which will eventually have the indx variable exceed the
# meetings length in the call:
# meetings[indx:len(meetings)-1]
sort(meetings[indx:len(meetings)-1], indx)
# nothing is returned here and sort does not mutate the object in
# any way that I could see that would cause sort to stop
# calling itself
def merge_ranges(meetings):
# Merge meeting range
sort(meetings, 0)
return [] # <- this empty list is always returned no matter what
sort doesn't return anything, which isn't a huge issue if you are just mutating something
sort calls itself recursively until it exceeds the recursion limit, there is nothing to tell it to stop calling itself
Lets assume meetings is this list
meetings = [(0, 1), (3, 5)]
meetings[5:] # ==> [] will always return an empty list when indx exceed meetings length
This means sort keeps calling itself with an empty list and a higher index number
merge_meetings always returns an empty list
You need to test for the index being larger than len(meetings)
Suggestion:
Assuming python 3
def sort(meetings, indx):
print("call function")
print(meetings)
first = meetings[indx]
second = meetings[indx+1]
firstfirst = first[0]
firstsecond = first[1]
secondfirst = second[0]
secondsecond = second[1]
print(firstfirst)
print(secondfirst)
if firstfirst > secondfirst:
meetings[indx] = second
meetings[indx+1] = first
indx = index + 1
print("meetings: ", meetings)
if len(meetings) - 1 > indx:
sort(meetings[indx:], indx)
Now while this takes care of stopping the recursive calls it still doesn't sort completely, it sorts the 2 elements relative to their position to each other but it will need several passes to acheive a proper sort.
for example:
In [1]: a = [(5,3), (0,2), (4,1), (1,1)]
In [2]: sort(a, 0)
call function
[(0, 2), (5, 3), (4, 1), (1, 1)]
0
5
meetings: [(0, 2), (5, 3), (4, 1), (1, 1)]
call function
[(5, 3), (4, 1), (1, 1)]
4
1
meetings: [(5, 3), (1, 1), (4, 1)]
In [3]: a
Out[3]: [(0, 2), (5, 3), (4, 1), (1, 1)]
I'll leave that up to you to figure out seeing as this was an assignment.