@@ -532,6 +532,119 @@ def h(self, node):
532532
533533# ______________________________________________________________________________
534534
535+ class TravelingSalesman (Problem ):
536+ """ The problem of finding the shortest Hamiltonian cycle (tour) that visits
537+ every city exactly once and returns to the start."""
538+
539+ def __init__ (self , all_cities , initial , goal = None ):
540+ # initial = (0), goal = some state that its first and last values are 0
541+ """ Define goal state and initialize a problem """
542+ super ().__init__ (initial , goal )
543+ self .cities_matrix = self .init_distance_matrix (all_cities )
544+ self .cities_identifiers = set (all_cities .keys ())
545+ self .cities_amount = len (self .cities_identifiers )
546+ self .trees_evaluation_hash = {}
547+
548+ def value (self , state ):
549+ """ Define state value """
550+ overall_distance = 0.0
551+ state_max_ind = len (state ) - 1
552+ for ind , city in enumerate (state ):
553+ if ind != state_max_ind :
554+ overall_distance += self .cities_matrix [state [ind ]][state [ind + 1 ]]
555+ return overall_distance
556+
557+ def actions (self , state ):
558+ """ Return the actions that can be executed in the given state.
559+ The result would be a list"""
560+ if len (state ) == self .cities_amount :
561+ return [0 ]
562+ return self .get_unvisited_cities (state )
563+
564+ def result (self , state , action ):
565+ """ Given state and action, return a new state that is the result of the action.
566+ Action is assumed to be a valid action in the state """
567+
568+ # blank is the index of the blank square
569+ new_state = list (state )
570+ new_state .append (action )
571+ return tuple (new_state )
572+
573+ def goal_test (self , state ):
574+ """ Given a state, return True if state is a goal state or False, otherwise"""
575+ return len (state ) == (self .cities_amount + 1 ) and state [0 ] == state [self .cities_amount ]
576+
577+ def get_unvisited_cities (self , state ):
578+ """ Returns unused actions(cities) """
579+ return self .cities_identifiers .difference (state )
580+
581+ def path_cost (self , c , state1 , action , state2 ):
582+ """ Return next state cost """
583+ return c + self .cities_matrix [state1 [- 1 ]][state2 [- 1 ]]
584+
585+ def h (self , node ):
586+ h = 0.0
587+ unvisited_cities = self .get_unvisited_cities (node .state )
588+ mst_evaluation = self .eval_unvisited_mst_edges_sum (unvisited_cities )
589+ h += mst_evaluation
590+ return h
591+
592+ def eval_unvisited_mst_edges_sum (self , unvisited_nodes ):
593+ if not unvisited_nodes :
594+ return 0.0
595+ return self .tsp_kruskal (unvisited_nodes )
596+
597+ def tsp_kruskal (self , unvisited_nodes ):
598+ """ Generate MST of the graph and return sum of edges """
599+ forest_value = 0.0
600+ disjoint_sets = DisjointSets ()
601+ non_sorted_edges_list = []
602+ for vertex_x in unvisited_nodes :
603+ disjoint_sets .make_set (vertex_x )
604+ for vertex_y in unvisited_nodes :
605+ if vertex_x < vertex_y :
606+ non_sorted_edges_list .append ((vertex_x , vertex_y ))
607+ edges_list = sorted (non_sorted_edges_list , key = lambda edge : self .cities_matrix [edge [0 ]][edge [1 ]], reverse = False )
608+ for vertex_u , vertex_v in edges_list :
609+ if disjoint_sets .find (vertex_v ) != disjoint_sets .find (vertex_u ):
610+ forest_value += self .cities_matrix [vertex_u ][vertex_v ]
611+ disjoint_sets .union (vertex_v , vertex_u )
612+ return forest_value
613+
614+ def init_distance_matrix (self , all_cities ):
615+ """ Generate matrix of distance between all cities """
616+ return {row_ind : {col_ind : distance (row_coords , col_coords ) for col_ind , col_coords in all_cities .items ()}
617+ for row_ind , row_coords in all_cities .items ()}
618+
619+
620+ class DisjointSets :
621+ def __init__ (self ):
622+ self .parent = {}
623+ self .rank = {}
624+
625+ def make_set (self , vertex ):
626+ self .parent [vertex ] = vertex
627+ self .rank [vertex ] = 0
628+
629+ def find (self , vertex ):
630+ if self .parent [vertex ] == vertex :
631+ return vertex
632+ return self .find (self .parent [vertex ])
633+
634+ def union (self , vertex_u , vertex_v ):
635+ vertex_u_root = self .find (vertex_u )
636+ vertex_v_root = self .find (vertex_v )
637+
638+ if self .rank [vertex_u_root ] > self .rank [vertex_v_root ]:
639+ self .parent [vertex_v_root ] = vertex_u_root
640+ elif self .rank [vertex_u_root ] < self .rank [vertex_v_root ]:
641+ self .parent [vertex_u_root ] = vertex_v_root
642+ else :
643+ self .parent [vertex_u_root ] = vertex_v_root
644+ self .rank [vertex_v_root ] += 1
645+
646+
647+ # ______________________________________________________________________________
535648
536649class PlanRoute (Problem ):
537650 """ The problem of moving the Hybrid Wumpus Agent from one place to other """
0 commit comments