Related
So i am learning about search algorithms at the minute, and would appreciate it if someone could provide an explanation of how this implementation of depth first search works, i do understand how depth first search works as a algorithm, but i am struggling to grasp how it has been implemented here.
Thanks for your patience and understanding, Below is the code:
map = {(0, 0): [(1, 0), (0, 1)],
(0, 1): [(1, 1), (0, 2)],
(0, 2): [(1, 2), (0, 3)],
(0, 3): [(1, 3), (0, 4)],
(0, 4): [(1, 4), (0, 5)],
(0, 5): [(1, 5)],
(1, 0): [(2, 0), (1, 1)],
(1, 1): [(2, 1), (1, 2)],
(1, 2): [(2, 2), (1, 3)],
(1, 3): [(2, 3), (1, 4)],
(1, 4): [(2, 4), (1, 5)],
(1, 5): [(2, 5)],
(2, 0): [(3, 0), (2, 1)],
(2, 1): [(3, 1), (2, 2)],
(2, 2): [(3, 2), (2, 3)],
(2, 3): [(3, 3), (2, 4)],
(2, 4): [(3, 4), (2, 5)],
(2, 5): [(3, 5)],
(3, 0): [(4, 0), (3, 1)],
(3, 1): [(4, 1), (3, 2)],
(3, 2): [(4, 2), (3, 3)],
(3, 3): [(4, 3), (3, 4)],
(3, 4): [(4, 4), (3, 5)],
(3, 5): [(4, 5)],
(4, 0): [(5, 0), (4, 1)],
(4, 1): [(5, 1), (4, 2)],
(4, 2): [(5, 2), (4, 3)],
(4, 3): [(5, 3), (4, 4)],
(4, 4): [(5, 4), (4, 5)],
(4, 5): [(5, 5)],
(5, 0): [(5, 1)],
(5, 1): [(5, 2)],
(5, 2): [(5, 3)],
(5, 3): [(5, 4)],
(5, 4): [(5, 5)],
(5, 5): []}
visited = []
path = []
routes = []
def goal_test(node):
if node == (5, 5):
return True
else:
return False
found = False
def dfs(visited, graph, node):
global routes
visited = visited + [node]
if goal_test(node):
routes = routes + [visited]
else:
for neighbour in graph[node]:
dfs(visited, graph, neighbour)
dfs(visited, map, (0, 0))
print(len(routes))
for route in routes:
print(route)
This implementation employs several bad practices:
map is a native Python function, so it is a bad idea to create a variable with that name.
visited should not need to be initialised in the global scope: the caller has no interest in this as it only plays a role in the DFS algorithm itself
routes should not have to be initialised to an empty list either, and it is bad that dfs mutates this global variable. Instead dfs should return that information to the caller. This makes one dfs call self-contained, as it returns the possible routes from the current node to the target. It is up to the caller to extend the routes in this returned collection with an additional node.
The body of goal_test should be written as return node == (5, 5). The if ... else is just translating a boolean value to the same boolean value.
The function goal_test seems overkill when you can just pass an argument to the dfs function that represents the target node. This makes it also more generic, as you don't need to hard-code the target location inside a function.
path and found are initialised but never used.
dfs would run into a stack overflow if the graph would have cycles. It does not happen with the given graph, because that graph is acyclic, but it would be better if you could also rely on this function when giving it cyclic graphs.
dfs will visit the same cell multiple times, as it can be found via different paths (like for instance (2,2)), and so from there it will perform the same DFS search it already did before. This could be made slightly more efficient by storing the result it got from a previous visit to that cell, i.e. we could use memoization. The gain is small, as most time is spent on creating and copying paths. The gain (of using memoization) would be significant if the function would only count the number of paths, and not build them.
Here is an implementation that deals with the above mentioned points. It uses a wrapper function to hide the use of memoization to the caller, and to reduce the number of arguments that need to be passed to dfs:
def search(graph, source, target):
# Use memoization to avoid repetitive DFS from same node,
# Also used to mark a node as visited, to avoid runnning in cycles
memo = dict() # has routes that were already collected
def dfs(node):
if node not in memo: # not been here before
if node == target:
memo[node] = [[target]]
else:
# Mark with None that this node is on the current path
# ...avoiding infinite recursion on a cycle
memo[node] = None # temporary value while not yet back from recursion
memo[node] = [
[node] + route
for neighbour in graph[node]
for route in dfs(neighbour)
if route
]
return memo[node]
return dfs(source)
graph = {(0, 0): [(1, 0), (0, 1)],
# ...etc ...
}
routes = search(graph, (0, 0), (5, 5))
print(len(routes))
for route in routes:
print(route)
Starting at (0,0) in the plane, given a positive integer n, I want to generate all paths consisting of n-1 steps away from (0,0). A step can be either one step to the right or one step up. For example, if n=4, then a path would be (0,0), (1,0), (1,1), (1,2). I'm currently using python
I've tried letting some parameter count the number of steps I'm taking and then using a while loop to restrict the number of steps, and for looping through my starting array [[[0,0]]].
def f(n):
A=[[[0,0]]]
s=0
while (int(s+1)<int(n)):
for i in A:
i.append([i[-1][0]+1,i[-1][1]])
A.append(i+[i[-1][0],i[-1][1]+1])
s+=1
return A
print f(2)
I'm getting an error 'int' object has no attribute 'getitem' on line 8. I also have a feeling that there are various other problems with the above code but am not too sure the best way to go about this
Welcome to Stackoverflow. This problem is ideally suited to recursive techniques.
If you have a path of length N at point P = (x, y) then you know it forms two
possible paths of length N+1, one to point (x+1, y) and one to point (x, y+1).
The only other thing you know is that there is one path of length zero at the starting point. Given that, you can compute the paths of length 1, the paths of length 2, and
so on. To separate the logic of path generation from the business of consuming the
paths I'd suggest using a generator function, allowing your logic to yield a
new path whenever is finds one. You can then iterate over this generator
to enumerate the paths. This appears to work:
def paths(in_path, N=1):
if N==1:
yield in_path
else:
x, y = in_path[-1]
yield from paths(in_path+[(x+1, y)], N-1)
yield from paths(in_path+[(x, y+1)], N-1)
for path in paths([(0, 0)], 4):
print(path)
The output I see is
[(0, 0), (1, 0), (2, 0), (3, 0)]
[(0, 0), (1, 0), (2, 0), (2, 1)]
[(0, 0), (1, 0), (1, 1), (2, 1)]
[(0, 0), (1, 0), (1, 1), (1, 2)]
[(0, 0), (0, 1), (1, 1), (2, 1)]
[(0, 0), (0, 1), (1, 1), (1, 2)]
[(0, 0), (0, 1), (0, 2), (1, 2)]
[(0, 0), (0, 1), (0, 2), (0, 3)]
which, gratifyingly, appears to include the example you gave.
In a list of tuples, I want to have just one copy of a tuple where it may be (x, y) or (y, x).
So, in:
# pairs = list(itertools.product(range(3), range(3)))
pairs = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
the result should be:
result = [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)] # updated pairs
This list of tuples is generated using itertools.product() but I want to remove the duplicates.
My working solution:
pairs = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
result = []
for pair in pairs:
a, b = pair
# reordering in increasing order
temp = (a, b) if a < b else (b, a)
result.append(temp)
print(list(set(result))) # I could use sorted() but the order doesn't matter
How can this be improved?
You could use combinations_with_replacement
The code for combinations_with_replacement() can be also expressed as a subsequence of product() after filtering entries where the elements are not in sorted order (according to their position in the input pool)
import itertools
pairs = list(itertools.combinations_with_replacement(range(3), 2))
print(pairs)
>>> [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)]
edit I just realized, your solution matches my solution. What you are doing is just fine. If you need to do this for a very large list, then there are some other options you may want to look into, like a key value store.
If you need to remove dupes more programatically, then you can use a function like this:
def set_reduce(pairs):
new_pairs = set([])
for x,y in pairs:
if x < y:
new_pairs.add((x,y))
else:
new_pairs.add((y,x))
return new_pairs
running this results in
>>>set_reduce(pairs)
set([(0, 1), (1, 2), (0, 0), (0, 2), (2, 2), (1, 1)])
This is one solution which relies on sparse matrices. This works for the following reasons:
An entry in a matrix cannot contain two values. Therefore, uniqueness is guaranteed.
Selecting the upper triangle ensures that (0, 1) is preferred above (1, 0), and inclusion of both is not possible.
import numpy as np
from scipy.sparse import csr_matrix, triu
lst = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1),
(1, 2), (2, 0), (2, 1), (2, 2)]
# get row coords & col coords
d1, d2 = list(zip(*lst))
# set up sparse matrix inputs
row, col, data = np.array(d1), np.array(d2), np.array([1]*len(lst))
# get upper triangle of matrix including diagonal
m = triu(csr_matrix((data, (row, col))), 0)
# output coordinates
result = list(zip(*(m.row, m.col)))
# [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)]
I have a function that generates a list of tuples like:
[(0, 0), (1, 1), (1, 2), (1,3), (2, 4), (3, 5), (4, 5)]
which are used to represent a path of tiles (row, column) in a game I'm making.
The function that I use to generate these paths isn't perfect, since it often produces "loops", as shown below:
[(2, 0), (2, 1), (1, 2), (0, 3), (0, 4), (1, 5), (2, 5), (3, 4), (3, 3),
(3, 2), (4, 1)]
The path above should instead look like:
[(2, 0), (2, 1), (3, 2), (4, 1)]
These paths can contain any number of loops, which can be of any size and shape.
So my question is, how do I write a function in python that cuts the loopy list and returns a new, shorter list that does not have these loops.
My attempt below:
def Cut_Out_Loops(Path):
NewList = list(Path)
Cutting = True
a = 0
for Cords in Path:
a += 1
try:
for i in range(a + 2, len(Path)):
if (Path[i][0] == Cords[0] and abs(Path[i][1] - Cords[1]) == 1:
NewList = NewList[0:a] + NewList[i:]
Path = list(NewList)
elif Path[i][1] == Cords[1] and abs(Path[i][0] - Cords[0]) == 1:
NewList = NewList[0:a] + NewList[i:]
Path = list(NewList)
elif abs(Path[i][0] - Cords[0]) == 1 and abs(Path[i][1] - Cords[1]) == 1:
NewList = NewList[0:a] + NewList[i:]
Path = list(NewList)
elif abs(Path[i][1] - Cords[1]) == 1 and abs(Path[i][0] - Cords[0]) == 1:
NewList = NewList[0:a] + NewList[i:]
Path = list(NewList)
Cutting = False
except IndexError:
Cutting = True
Although your definition of a "loop" isn't too clear, try this
def clean(path):
path1 = []
for (x1,y1) in path:
for (i,(x2,y2)) in enumerate(path1[:-1]):
if abs(x1-x2) <= 1 and abs(y1-y2) <= 1:
path1 = path1[:i+1]
break
path1.append((x1,y1))
return path1
It definitely works for your example:
>>> path = [(2, 0), (2, 1), (1, 2), (0, 3), (0, 4), (1, 5), (2, 5), (3, 4), (3, 3), (3, 2), (4, 1)]
>>> clean(path)
[(2, 0), (2, 1), (3, 2), (4, 1)]
That said, it is just the most straightforward of brute force solutions. The complexity is quadratic.
How long are your paths? If they're all under 1000 elements, even a naive brute-force algorithm would work:
path = [
(2, 0),
(2, 1),
(1, 2),
(0, 3),
(0, 4),
(1, 5),
(2, 5),
(3, 4),
(3, 3),
(3, 2),
(4, 1)
]
def adjacent(elem, next_elem):
return (abs(elem[0] - next_elem[0]) <= 1 and
abs(elem[1] - next_elem[1]) <= 1)
new_path = []
i = 0
while True:
elem = path[i]
new_path.append(elem)
if i + 1 == len(path):
break
j = len(path) - 1
while True:
future_elem = path[j]
if adjacent(elem, future_elem):
break
j -= 1
i = j
print new_path
i have one list with some coordinates in it, when i am printing it like that:
for i in range(0,len(List)):
print("".join(["(%d, %d) \n" % (y[i], y[i+1]) for y in (List)]))
the output is this:
(0, 3)
(0, 2)
(0, 1)
(1, 1)
(1, 2)
(2, 2)
(2, 1)
(3, 1)
(3, 0)
(2, 0)
(1, 0)
(0, 0)
i want to save the output in a .txt, but that is not a problem, my problem is that the .txt must be formmated like this:
(0, 3), (0, 2)
(0, 2), (0, 1)
(0, 1),(1, 1)
(1, 1),(1, 2)
(1, 2),(2, 2)
.....
i've tried many things but nothing worked..
it must be easy, but i am new to python
thank you in advance
This does the trick:
l = [(0, 3), (0, 2), (0, 1), (1, 1), (1, 2), (2, 2)]
for i in range(0, len(l), 2):
print(', '.join([str(l[i]), str(l[i+1])]))
# (0, 3), (0, 2)
# (0, 1), (1, 1)
# (1, 2), (2, 2)
You can use zip.
mylist = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
# That doesn't have to be the numbers though, you can use your own.
for a, b in zip(mylist, mylist[1:]):
print("{}, {}".format(a, b), file = myfile) # Will print to myfile.
That's assuming that you are printing to an open file. Leave the file argument out if you don't want to print anywhere else, but the default screen.
in myfile.txt:
(0, 0), (0, 1)
(0, 1), (0, 2)
(0, 2), (1, 0)
(1, 0), (1, 1)
(1, 1), (1, 2)
(1, 2), (2, 0)
(2, 0), (2, 1)
(2, 1), (2, 2)
The output is in tuples, not lists.
Well, besides other answers playing with indices, you can also use zip:
for a, b, c, d in zip(List, List[1:], List[2:], List[3:]):
print('({}, {}), ({}, {})'.format(a, b, c, d))
Regarding expected output:
l = [(0, 3), (0, 2), (0, 1), (1, 1), (1, 2), (2, 2)]
for i,j in zip(l,l[1:]):
print str(i) + "," + str(j) # or print ",".join([str(i),str(j)])
#output
(0, 3),(0, 2)
(0, 2),(0, 1)
(0, 1),(1, 1)
(1, 1),(1, 2)
(1, 2),(2, 2)
If you want list chunking with 2 elements,Try this
def chunks(l, n):
for i in xrange(0, len(l), n):
yield l[i:i+n]
for i,j in list(chunks(l,2)):
print ",".join([str(i),str(j)])
#output
(0, 3),(0, 2)
(0, 1),(1, 1)
(1, 2),(2, 2)
Your code:
for i in range(0,len(List)):
print("".join(["(%d, %d) \n" % (y[i], y[i+1]) for y in (List)]))
Turning this into a list comp.
t = ["".join(["(%d, %d) \n" % (y[i], y[i+1]) for y in (List)]) for i in range(0,len(List))]
(equivalent to)
s = []
for i in range(0,len(List)):
s.append("".join(["(%d, %d) \n" % (y[i], y[i+1]) for y in (List)]))
Then:
first = True
other = None
r = ""
for i in t:
if not first:
r += other+", "+i+"\n"
first = True
else:
other = i
first = False
f = open("out.txt","w")
f.write(t)
f.close() #go to notepad