A* search with multiple-goals (Python) - python

I am trying to write a function that completes an A* search with multiple goals. Basically it is searching a grid like structure of the form:
%%%%%%%%%%%%%%%%%%%%
%. ...P .%
%.%%.%%.%%.%%.%% %.%
% %% %..... %.%
%%%%%%%%%%%%%%%%%%%%
for a path from P that goes through all the dots (basically Pacman).
However I have run into a problem with my algorithm (which I attempted to adapt from my A* search for a single goal) as the path it returns does not go through all the dots. This is the path it returns for the above maze:
Path = [(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15), (1, 16)]
while a print statement shows that the visited variable has a value at return of:
[(1, 16), (1, 15), (2, 16), (1, 17), (1, 14), (3, 16), (1, 18), (1, 13), (3, 15), (2, 18), (1, 12), (2, 13), (3, 18), (3, 14), (1, 11), (3, 13), (3, 12), (1, 10), (1, 9), (3, 11), (2, 10), (1, 8), (3, 10), (1, 7), (3, 9), (1, 6), (3, 8), (2, 7), (1, 5), (3, 7), (1, 4), (3, 6), (2, 4), (1, 3), (3, 4), (1, 2), (1, 1), (2, 1)]
I think that that problem is how I am storing the current path (where each node stores its parent node, and then I return the end node and go backwards recursively to get the path). Does anyone have any advice for what I should change? I attached my current code below. Thanks!

What your algorithm is currently doing is trying to find the goal by expending its area around the starting point and finding the best path for every node its visiting.
In a single-goal situation, it works well and you can get the path to this goal.
However how you have adapted it to a multi-goal purpose is that only the stop condition changes (when all goals as been visited once), meaning that you found the shortest path from the start point to each goal but not a single path visiting all nodes.
In the case, you just want the paths from the start point to each goal, just get the path (via parents) from each goal point.
If you really want to implement a pacman-like search, this is NP-Hard problem (see this answer).
As one of the comment proposes, if you have a small list of goals, you can find a solution with brute-force:
Let's say you have 3 goals: A,B,C (which were dots):
%%%%%%%%%%%%%%%%%%%%
%A P %
% %% %% %% %%C%% % %
% %% % B % %
%%%%%%%%%%%%%%%%%%%%
Using your algorithm, you can find the shortest path from P to A, then A to B then B to C. Do the same for other permutations ((P,A,C,B),(P,B,A,C) ...): see itertools.combinations(goals, len(goals))
You can then use your algorithm to find the path from one point to the other:
def A_multiple_goals(maze, start, goals):
paths = []
for itinerary in itertools.combinations(goals, len(goals)):
path = get_path(A_search_multiple(maze, start, itinerary[0])) # First go to first goal from start
for i in range(1 , len(itinerary)): # Then from each goal, goto the next one
path += get_path(A_search_multiple(maze, itinerary[i-1], itinerary[i]))
paths.append(paths)
return min(paths, key=len)
This is a brute-force approach, if you have a lot of goals, you would need a better algorithm based around the Traveling Salesman Problem.

Related

Paths in Python/Sage

I've been working on this problem (https://imgur.com/a/nJEMfM9) asking me to plot all lattice paths in a nxn grid for the last week, and I have no idea how to proceed.
This is about as far as I've been able to get
def NE_lattice_paths(x,y):
Vn= vector([0,1])
Ve= vector([1,0])
plot(Vn) + plot(Ve, start=Vn)
I know I have to use vectors, and I have to use the "def" command to make a function, but how would I make a function that can plot every path and know to take a different one each time? What I wrote doesn't really make sense, but I could use some guidance on how to proceed. Thank you!
You can get all the paths with a nested for loop (or list comprehension).
So this will give all the paths.
def NE_lattice_paths(x,y):
paths = []
for i in range(x):
path = []
for j in range(y):
path.append((i,j))
paths.append(path)
return paths
result = NE_lattice_paths(5,3)
print(result)
result
[[(0, 0), (0, 1), (0, 2)], [(1, 0), (1, 1), (1, 2)], [(2, 0), (2, 1), (2, 2)], [(3, 0), (3, 1), (3, 2)], [(4, 0), (4, 1), (4, 2)]]
I will leave it as an excersize for the OP to do the animation...

How Compare and Extract equal values from Nested Dictionary and Append the equal values to a List

I have the following function responsible to generate a nested dictionary with integer keys that works inside a for loop, and I want to create a updated list when equal values are found:
# The number of times I want to run
n = 2
# Number of Loops
count = 0
# Init the Hash Table
castar_hash = {}
def run_discrete_time(start, path, count):
'''
Create or Update the Nested Dic, look for equal values and append
a new list based on the 'path' input
Args:
start (list)
path (list)
count (int)
Vars:
start = A simple list with one element
path = A list of Tuples
count = The atual Loop Interaction
'''
# Inserted the 0 because the "discrete time" will init at 0
path_list.insert(0, start)
time_run = list(range(0, len(path_list)+1))
hash = {t:p for t,p in zip(time_run,path_list)}
#* Create a new Dic Key
value_list = ['value', str(count)]
value = "".join(value_list)
castar_hash.update({value:hash})
print(f'\nThe time steps is: {time_run}')
print(f'The Hash table is: {castar_hash}\n')
'''
Need the code here to find the equal values
in the castar_hash and append to a updated list
'''
return updated_list
def main():
for _ in range(n):
'''
some code here who picks the start and path from a deque
already implemented (see the inputs bellow)
'''
count += 1
run_discrete_time(start, path, count)
if __name__ == '__main__':
main()
Let me explain how the function works with inputs: Considering that the loop will run 2 times (since the number of times "n" is 2), for the first call, considering the input:
run_discrete_time([4, 6], [(4, 5), (4, 4),(4, 3),(5, 3),(6, 3),
(7, 3), (8, 3), (8, 2), (8, 1),(9, 1)],
count)
The generated nested dic will be:
castar_hash = {'value1': {0:(4, 6), 1:(4, 5), 2:(4, 4), 3:(4, 3),
4:(5, 3), 5:(6, 3), 6:(7, 3), 7:(8, 3),
8:(8, 2), 9:(8, 1), 10:(9, 1)},
For the second loop with inputs:
run_discrete_time([1, 6], [(2, 6), (4, 4), (4, 6),(4, 5), (4, 4),
(4, 3), (5, 3), (6, 3), (8, 1), (8, 3), (9, 3)],
count)
The updated nest dic will now be:
castar_hash = {'value1': {0:(4, 6), 1:(4, 5), 2:(4, 4), 3:(4, 3),
4:(5, 3), 5:(6, 3), 6:(7, 3), 7:(8, 3),
8:(8, 2), 9:(8, 1), 10:(9, 1)},
'value2': {0:(1, 6), 1:(2, 6), 2:(4, 4), 3:(4, 6),
4:(4, 5), 5:(4, 4), 6:(4, 3), 7:(5, 3),
8:(6, 3), 9:(8, 1), 10:(8, 3), 11:(9,3)}}
My question is: What is the best and most efficient way to extract the equal values in the nested dics for every loop (considering that I can have more than two)? I'm struggling a lot to find a solution for that.
For example, the repeated values in the 'value2' dic is 2:(4, 4) and 9:(8, 1) (in relation to the 'value1' dic), and I would like to return a new list updated as (4,4) inserted in the index 2, and (8,1) at index 9, for example:
#The Path List of Tuples inputed at the second loop
path = [(2, 6), (4, 4), (4, 6),(4, 5), (4, 4),
(4, 3), (5, 3), (6, 3), (8, 1), (8, 3), (9, 3)]
#The New Updated Path List that I want to return since
#the method finded equals values compared to the 'value1' dic:
new_path = [(2, 6), (4, 4), (4, 4) (4, 6),(4, 5), (4, 4),
(4, 3), (5, 3), (6, 3), (8, 1), (8, 1), (8, 3),
(9, 3)]

How to get all maximal non-overlapping sets of spans from a list of spans

I can't seem to find a way to write the algorithm in the title without needing to curate the results in some way.
To illustrate what I want:
all_spans = [(0, 5), (2, 7), (5, 8), (6, 10), (9, 10), (11, 15)]
possible_sets = [
{(0, 5), (5, 8), (9, 10), (11, 15)},
{(2, 7), (9, 10), (11, 15)},
{(0, 5), (6, 10), (11, 15)}
]
not_possible = [
{(0, 5), (5, 8), (6, 10), (11, 15)}, # has overlaps
{(5, 8), (9, 10), (11, 15)} # not maximal w.r.t possible_sets[0]
]
My current implementation is more or less this:
def has_overlap(a, b):
return a[1] > b[0] and b[1] > a[0]
def combine(spans, current, idx=0):
for i in range(idx, len(spans)):
overlaps = {e for e in current if has_overlap(e, spans[i])}
if overlaps:
yield from combine(spans, current-overlaps, i)
else:
current.add(spans[i])
yield current
But it produces non-maximal spans that I'd rather just not create in the first place.
>>> for s in combine(all_spans, set()):
... print(sorted(s))
[(9, 10), (11, 15)]
[(6, 10), (11, 15)]
[(5, 8), (9, 10), (11, 15)]
[(9, 10), (11, 15)]
[(6, 10), (11, 15)]
[(2, 7), (9, 10), (11, 15)]
[(0, 5), (9, 10), (11, 15)]
[(0, 5), (6, 10), (11, 15)]
[(0, 5), (5, 8), (9, 10), (11, 15)]
Is there a different approach that avoids this behavior? I found similar problems under the keywords "interval overlaps" and "activity scheduling", but none of them seemed to refer to this particular problem.
It depends on what you mean by not wanting to curate the results.
You can filter out the non-maximal results after using your generator with:
all_results = [s for s in combine(all_spans, set())]
for first_result in list(all_results):
for second_result in list(all_results):
if first_result.issubset(second_result) and first_result != second_result:
all_results.remove(first_result)
break
To not produce them in the first place, you could do a check before yielding to see whether an answer is maximal. Something like:
def combine(spans, current, idx=0):
for i in range(idx, len(spans)):
overlaps = {e for e in current if has_overlap(e, spans[i])}
if overlaps:
yield from combine(spans, current-overlaps, i)
else:
current.add(spans[i])
# Check whether the current set is maximal.
possible_additions = set(spans)
for item_to_consider in set(possible_additions):
if any([has_overlap(item_in_current, item_to_consider) for item_in_current in current]):
possible_additions.remove(item_to_consider)
if len(possible_additions) == 0:
yield current
This is a simple (?) graph problem. Make a directed graph where each span is a node. There is an edge AB (from node A to node B) iff A[1] <= B[0] -- in prose, if span B doesn't start until span A finishes. Your graph would look like
Node => Successors
(0, 5) => (5, 8), (6, 10), (9, 10), (11, 15)
(2, 7) => (9, 10), (11, 15)
(5, 8) => (9, 10), (11, 15)
(6, 10) => (11, 15)
(9, 10) => (11, 15)
Now, the problem reduces to simply finding the longest path through the graph, including ties.
Given the linearity of the problem, finding one maximal solution is easier: at each step, pick the successor node with the soonest ending time. In steps:
To start, all nodes are available. The one with the soonest ending time is (0,5).
The successor to (0,5) with the earliest end is (5, 8).
The successor to (5,8) ... is (9, 10)
... and finally add (11, 15)
Note that this much doesn't require a graph; merely a structure you're willing to reference by either first or second sub-element.
The solution length is 4, as you already know.
Can you take it form here?
Assuming ranges are sorted by lower bound, we'd like to append the current range to the longest paths it can be appended to, or create a new path (append to an empty path). If it's called for, we could consider making the search for the longest prefixes more efficient. (The code below just updates that search in a slightly optimised linear method.)
(I'm not sure how to use the yield functionality, perhaps you could make this code more elegant.)
# Assumes spans are sorted by lower bound
# and each tuple is a valid range
def f(spans):
# Append the current span to the longest
# paths it can be appended to.
paths = [[spans.pop(0)]]
for l,r in spans:
to_extend = []
longest = 0
print "\nCandidate: %s" % ((l,r),)
for path in paths:
lp, rp = path[-1]
print "Testing on %s" % ((lp,rp),)
if lp <= l < rp:
prefix = path[:-1]
if len(prefix) >= longest:
to_extend.append(prefix + [(l,r)])
longest = len(prefix)
# Otherwise, it's after so append it.
else:
print "Appending to path: %s" % path
path.append((l, r))
longest = len(path)
for path in to_extend:
print "Candidate extensions: %s" % to_extend
if len(path) == longest + 1:
print "Adding to total paths: %s" % path
paths.append(path)
print "\nResult: %s" % paths
return paths
all_spans = [(0, 5), (2, 7), (5, 8), (6, 10), (9, 10), (11, 15)]
f(all_spans)
Output:
"""
Candidate: (2, 7)
Testing on (0, 5)
Candidate extensions: [[(2, 7)]]
Adding to total paths: [(2, 7)]
Candidate: (5, 8)
Testing on (0, 5)
Appending to path: [(0, 5)]
Testing on (2, 7)
Candidate: (6, 10)
Testing on (5, 8)
Testing on (2, 7)
Candidate extensions: [[(0, 5), (6, 10)]]
Adding to total paths: [(0, 5), (6, 10)]
Candidate: (9, 10)
Testing on (5, 8)
Appending to path: [(0, 5), (5, 8)]
Testing on (2, 7)
Appending to path: [(2, 7)]
Testing on (6, 10)
Candidate: (11, 15)
Testing on (9, 10)
Appending to path: [(0, 5), (5, 8), (9, 10)]
Testing on (9, 10)
Appending to path: [(2, 7), (9, 10)]
Testing on (6, 10)
Appending to path: [(0, 5), (6, 10)]
Result: [[(0, 5), (5, 8), (9, 10), (11, 15)],
[(2, 7), (9, 10), (11, 15)],
[(0, 5), (6, 10), (11, 15)]]
"""

Python Chess Implementation

I'm beginning work on a chess implementation and before going too far down the rabbit hole, I wanted to get the community's input if you wouldn't mind since I'm already at a dead end ha. I'm struggling to figure out the best way to associate the pieces with the coordinates.
Right now, I have a list of list with the various pieces where each list represents a board.
For the coordinates, I used this list comprehension
coordinates = [[(i,j) for i in range(0,8)] for j in range(0,8)]
which gives me a table like this
[(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0)]
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)]
[(0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2), (7, 2)]
[(0, 3), (1, 3), (2, 3), (3, 3), (4, 3), (5, 3), (6, 3), (7, 3)]
[(0, 4), (1, 4), (2, 4), (3, 4), (4, 4), (5, 4), (6, 4), (7, 4)]
[(0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5), (7, 5)]
[(0, 6), (1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6), (7, 6)]
[(0, 7), (1, 7), (2, 7), (3, 7), (4, 7), (5, 7), (6, 7), (7, 7)]
Any strong thoughts on how I can associate the piece with their coordinate to find potential moves? I was thinking dictionary at first, but you have multiple pieces of the same type (eg. two knights) and don't think this would be ideal as the board evolved.
Thanks as always.
Funnily enough, I have just been working on exactly this! Previously, I wrote a chess AI but in javascript, however today I have been converting that code into Python for use with a bigger project so the knowledge is fresh in my mind.
Originally, in the JS version, I stored the board effectively as an 8x8 array of strings for each piece (in reality this was inside an object with other data such as castling but that is not important).
However, this method of using an array (list in Python) led to problems due to the way they are passed by reference. The issue was that passing the board state through the negamax algorithm meant that for each move to be considered, the whole array (in JS) would have to be copied to stop the move being made to the original board state.
I got around this by storing the board states as strings which are immutable in Python. I would advise you to start off using lists though as they are much simpler to access and change values even though they will probably end up leading to slowness (from making copies of them) later down the line when you come to optimising.
The actual trick to storing the board state is to use one character for each piece and use upper and lowercase to represent the white and black sides. I stole this technique from the widely used FEN notation and it turns out to be really useful for both displaying and doing operations on the board state!
To see what I mean, you could initialise the starting state with:
state = ["RNBQKBNR", "PPPPPPPP", " ", " ", " ", " ", "pppppppp", "rnbqkbnr"]
state = [list(r) for r in state]
and then you can easily create a display function with:
def display(state):
for r in reversed(state):
print(''.join(r))
then whenever you want to display a given state, you can call display(state) which gives:
rnbqkbnr
pppppppp
PPPPPPPP
RNBQKBNR
Hopefully this helps you out! You can look at the code for my full implementation of a chess AI on github: in Python and in javascript :)
OK it goes like this:
You got 64 cells, traditionally coordinated with letter and digit.
Name each cell numeric so that cell will coordinated: "a1" will be 11, h5 will be 85 etc.
Now for the moves:
Up: (cell value) + 1, Down: (cell value) - 1, Right: (cell value) +
10, Left: (cell value) - 10,
Diagnose: Up-Left: (cell value) - 9, Up-Right: (cell value) +
11, Down-Left: (cell value) - 11, Down-Right: ((cell value) + 9,
And for the Knight: (cell value) + 21, (cell value) - 21, (cell
value) + 12, (cell value) - 12, (cell value) + 8, (cell value) - 8,
(cell value) + 19, (cell value) – 19.
As you can understand, I recently build one by myself ( based on JS if you mind) ha ha.
Good Luck!
As someone mentioned, the most obvious simple implementation is a list of lists, for example in my implementation this logic creates the board, and then pieces are added to it using the add() method:
https://github.com/akulakov/pychess/blob/7176b168568000af721e79887981bcd6467cfbc0/chess.py#L141

Find maximum equidistant points on a line

I need an algorithm to find maximum no of equidistant points on the same line.
Input: List of collinear points
For example: My points could be
[(1, 1), (1, 2), (1, 3)]
In this case what I could do is sort the points based on their distance from origin and find the distance sequentially. However, in a scenario such as below the condition is failing. All the points are on the same line y=-x+6, and are equidistant from each other.
[(3, 3), (2, 4), (4, 2), (5, 1), (1, 5)]
because all the points are equidistant from origin, and sorting order could be anything so sequential traversal is not possible.
For example, if final dictionary become this [(3, 3), (5, 1), (4, 2), (2, 4), (1,5)] we would end up calculating distance between (3,3) and (5,1), which is not correct. Ideally, I would want to calculate the distance between closest points so the order should be (1,5), (2,4).
To overcome this problem I created a O(n*n) solution by iterating using 2 loops, and finding frequency of minimum distance between any 2 points:
import sys
distance_list=[]
lop=[(1, 3), (2, 4), (3, 5), (4, 6), (10, 12), (11, 13), (12, 14), (13, 15), (14, 16)]
lop.sort(key=lambda x: x[0]*x[0] + x[1]*x[1])
for k in range(0, len(lop)):
min_dist=sys.maxint
for l in range(0, len(lop)):
if k!=l:
temp_dist = ( (lop[k][0] - lop[l][0])*(lop[k][0] - lop[l][0]) + (lop[k][1] - lop[l][1])*(lop[k][1] - lop[l][1]) )
min_dist= min(min_dist, temp_dist)
distance_list.append(min_dist)
print distance_list.count (max(distance_list,key=distance_list.count))
However, above solution failed for below test case:
[(1, 3), (2, 4), (3, 5), (4, 6), (10, 12), (11, 13), (12, 14), (13, 15), (14, 16)]
Expected answer should be: 5
However, I'm getting: 9
Essentially, I am not able to make sure, how do I do distinction between 2 cluster of points which contain equidistant points; In the above example that would be
[(1, 3), (2, 4), (3, 5), (4, 6)] AND [(10, 12), (11, 13), (12, 14), (13, 15), (14, 16)]
If you want to put the points in order, you don't need to sort them by distance from anything. You can just sort them by the default lexicographic order, which is consistent with the order along the line:
lop.sort()
Now you just need to figure out how to find the largest set of equidistant points. That could be tricky, especially if you're allowed to skip points.
because you want the distance of consecutive points, there is no need to calculate all combinations, you just need to calculate the distance of (p0,p1), (p1,p2), (p2,p3), and so on, and group those pairs in that order by the value of their distance, once you have done that, you just need the longest sequence among those, to do that the itertools module come in handy
from itertools import groupby, tee, izip
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return izip(a, b)
def distance(a,b):
ax,ay = a
bx,by = b
return (ax-bx)**2 + (ay-by)**2
def longest_seq(points):
groups = [ list(g) for k,g in groupby(pairwise(points), lambda p:distance(*p)) ]
max_s = max(groups,key=len) # this is a list of pairs [(p0,p1), (p1,p2), (p2,p3),..., (pn-1,pn)]
ans = [ p[0] for p in max_s ]
ans.append( max_s[-1][-1] ) # we need to include the last point manually
return ans
here the goupby function group together consecutive pairs of points that have the same distance, pairwise is a recipe to do the desire pairing, and the rest is self explanatory.
here is a test
>>> test = [(1, 3), (2, 4), (3, 5), (4, 6), (10, 12), (11, 13), (12, 14), (13, 15), (14, 16)]
>>> longest_seq(test)
[(10, 12), (11, 13), (12, 14), (13, 15), (14, 16)]
>>>

Categories