# Name: Rushil Umaretiya # Date: 10/11/2020 import random, time, math class HeapPriorityQueue(): def __init__(self): self.queue = ["dummy"] # we do not use index 0 for easy index calulation self.current = 1 # to make this object iterable def next(self): # define what __next__ does if self.current >=len(self.queue): self.current = 1 # to restart iteration later raise StopIteration out = self.queue[self.current] self.current += 1 return out def __iter__(self): return self __next__ = next def isEmpty(self): return len(self.queue) <= 1 # b/c index 0 is dummy def swap(self, a, b): self.queue[a], self.queue[b] = self.queue[b], self.queue[a] # Add a value to the heap_pq def push(self, value): self.queue.append(value) # write more code here to keep the min-heap property self.heapUp(len(self.queue)-1) # helper method for push def heapUp(self, k): if k <= 1: return if len(self.queue) % 2 == 1 and k == len(self.queue) - 1: # no sibling if self.queue[k//2][1] > self.queue[k][1]: self.swap(k, k//2) self.heapUp(k//2) return if k % 2 == 0: parent, sibling = k//2, k+1 else: parent, sibling = k//2, k-1 if self.queue[k][1] > self.queue[sibling][1]: child = sibling else: child = k if self.queue[parent][1] > self.queue[child][1]: self.swap(child, parent) self.heapUp(parent) # helper method for reheap and pop def heapDown(self, k, size): left, right = 2*k, 2*k+1 if left == size and self.queue[k][1] > self.queue[size][1]: # One child self.swap(k, left) elif right <= size: child = (left if self.queue[left][1] < self.queue[right][1] else right) if self.queue[k][1] > self.queue[child][1]: self.swap(k, child) self.heapDown(child, size) # make the queue as a min-heap def reheap(self): if self.isEmpty(): return -1 for k in range((len(self.queue)-1)//2, 0, -1): self.heapUp(k) # remove the min value (root of the heap) # return the removed value def pop(self): if self.isEmpty(): return -1 self.swap (1, len(self.queue) - 1) val = self.queue.pop() self.heapDown(1, len(self.queue) - 1) return val # remove a value at the given index (assume index 0 is the root) # return the removed value def remove(self, index): # Your code goes here if self.isEmpty(): return -1 if len (self.queue) == 2: val = self.queue.pop() self.queue = [] return val self.swap (index + 1, len(self.queue) - 1) val = self.queue.pop() self.heapDown(index + 1, len(self.queue) - 1) return val def inversion_count(new_state, width = 4, N = 4): ''' Depends on the size(width, N) of the puzzle, we can decide if the puzzle is solvable or not by counting inversions. If N is odd, then puzzle instance is solvable if number of inversions is even in the input state. If N is even, puzzle instance is solvable if the blank is on an even row counting from the bottom (second-last, fourth-last, etc.) and number of inversions is even. the blank is on an odd row counting from the bottom (last, third-last, fifth-last, etc.) and number of inversions is odd. ''' # Your code goes here inversion_count = 0 for i in range(len(new_state)): for j in range(i, len(new_state)): if new_state[i] != '_': if new_state[i] > new_state[j]: inversion_count += 1 if N % 2 == 0: blank = new_state.find('_') if (blank // width) % 2 == 0: return inversion_count % 2 == 0 else: return inversion_count % 2 != 0 else: return inversion_count % 2 == 0 def check_inversion(): t1 = inversion_count("_42135678", 3, 3) # N=3 f1 = inversion_count("21345678_", 3, 3) t2 = inversion_count("4123C98BDA765_EF", 4) # N is default, N=4 f2 = inversion_count("4123C98BDA765_FE", 4) return t1 and t2 and not (f1 or f2) def getInitialState(sample, size): sample_list = list(sample) random.shuffle(sample_list) new_state = ''.join(sample_list) while not inversion_count(new_state, size, size): random.shuffle(sample_list) new_state = ''.join(sample_list) return new_state '''precondition: i -1: children.append(swap(state, empty, up)) if down < len(state): children.append(swap(state, empty, down)) return children def display_path(path_list, size): for n in range(size): for path in path_list: print (path[n*size:(n+1)*size], end = " "*size) print () print ("\nThe shortest path length is :", len(path_list)) return "" ''' You can make multiple heuristic functions ''' def dist_heuristic(state, goal = "_123456789ABCDEF", size=4): # Your code goes here md = 0 for i in range(len(state)): if state[i] != '_': md += abs(goal.find(state[i]) % size - i % size) + abs(goal.find(state[i]) // size - i // size) return md def check_heuristic(): a = dist_heuristic("152349678_ABCDEF", "_123456789ABCDEF", 4) b = dist_heuristic("8936C_24A71FDB5E", "_123456789ABCDEF", 4) return (a < b) def a_star(start, goal="_123456789ABCDEF", heuristic=dist_heuristic, size = 4): frontier = HeapPriorityQueue() explored = {} if start == goal: return [] # We are pushing tuples of the (current_node, path_cost+heuristic, path) frontier.push((start, heuristic(start, goal, size), [start])) explored[start] = heuristic(start, goal, size) while not frontier.isEmpty(): # Pop off the heapq state = frontier.pop() # Goal Test if state[0] == goal: return state[2] # Push children onto priority queue for i in generate_children(state[0], size): cost = heuristic(i, goal, size) + len(state[2]) + 1 print(cost) if i not in explored.keys() or explored[i] > cost: explored[i] = cost frontier.push((i, cost, state[2] + [i])) return None def main(): # A star print ("Inversion works?:", check_inversion()) print ("Heuristic works?:", check_heuristic()) #initial_state = getInitialState("_123456789ABCDEF", 4) initial_state = input("Type initial state: ") if inversion_count(initial_state): cur_time = time.time() path = (a_star(initial_state)) if path != None: display_path(path, 4) else: print ("No Path Found.") print ("Duration: ", (time.time() - cur_time)) else: print ("{} did not pass inversion test.".format(initial_state)) if __name__ == '__main__': main() ''' Sample output 1 Inversion works?: True Heuristic works?: True Type initial state: 152349678_ABCDEF 1523 1523 1_23 _123 4967 4_67 4567 4567 8_AB 89AB 89AB 89AB CDEF CDEF CDEF CDEF The shortest path length is : 4 Duration: 0.0 Sample output 2 Inversion works?: True Heuristic works?: True Type initial state: 2_63514B897ACDEF 2_63 _263 5263 5263 5263 5263 5263 5263 5263 52_3 5_23 _523 1523 1523 1_23 _123 514B 514B _14B 1_4B 14_B 147B 147B 147_ 14_7 1467 1467 1467 _467 4_67 4567 4567 897A 897A 897A 897A 897A 89_A 89A_ 89AB 89AB 89AB 89AB 89AB 89AB 89AB 89AB 89AB CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF The shortest path length is : 16 Duration: 0.005014657974243164 Sample output 3 Inversion works?: True Heuristic works?: True Type initial state: 8936C_24A71FDB5E 8936 8936 8936 893_ 89_3 8943 8943 8_43 84_3 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 _423 4_23 4123 4123 4123 4123 _123 C_24 C2_4 C24_ C246 C246 C2_6 C_26 C926 C926 C9_6 C916 C916 C916 C916 C916 C916 C916 C916 C916 _916 9_16 91_6 916_ 9167 9167 9167 9167 9167 9167 _167 8167 8167 8_67 8567 8567 _567 4567 A71F A71F A71F A71F A71F A71F A71F A71F A71F A71F A7_F A_7F AB7F AB7F AB7F AB7_ AB_7 A_B7 _AB7 CAB7 CAB7 CAB7 CAB7 CAB_ CA_B C_AB C5AB C5AB _5AB 95AB 95AB 95AB 95AB 9_AB _9AB 89AB 89AB DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E D_5E D5_E D5E_ D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D_EF _DEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF The shortest path length is : 37 Duration: 0.27825474739074707 Sample output 4 Inversion works?: True Heuristic works?: True Type initial state: 8293AC4671FEDB5_ 8293 8293 8293 8293 8293 8293 8293 8293 82_3 8_23 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 8423 _423 4_23 4123 4123 4123 4123 _123 AC46 AC46 AC46 AC46 AC46 _C46 C_46 C4_6 C496 C496 C_96 C9_6 C916 C916 C916 C916 C916 C916 C916 C916 C916 _916 9_16 91_6 916_ 9167 9167 9167 9167 9167 9167 _167 8167 8167 8_67 8567 8567 _567 4567 71FE 71F_ 71_F 7_1F _71F A71F A71F A71F A71F A71F A71F A71F A7_F A_7F AB7F AB7F AB7F AB7_ AB_7 A_B7 _AB7 CAB7 CAB7 CAB7 CAB7 CAB_ CA_B C_AB C5AB C5AB _5AB 95AB 95AB 95AB 95AB 9_AB _9AB 89AB 89AB DB5_ DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E DB5E D_5E D5_E D5E_ D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D5EF D_EF _DEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF CDEF The shortest path length is : 39 Duration: 0.7709157466888428 '''