Advertisement
EgzonIseini

[AI] Graph Explorer

May 27th, 2019
445
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 27.93 KB | None | 0 0
  1. import bisect
  2. import sys
  3. import math
  4. import random
  5. from sys import maxsize as infinity
  6.  
  7. """
  8. Defining a class for the problem structure that we will solve with a search.
  9. The Problem class is an abstract class from which we make inheritance to define the basic
  10. characteristics of every problem we want to solve
  11. """
  12.  
  13.  
  14. class Problem:
  15.     def __init__(self, initial, goal=None):
  16.         self.initial = initial
  17.         self.goal = goal
  18.  
  19.     def successor(self, state):
  20.         """Given a state, return a dictionary of {action : state} pairs reachable
  21.        from this state. If there are many successors, consider an iterator
  22.        that yields the successors one at a time, rather than building them
  23.        all at once.
  24.  
  25.        :param state: given state
  26.        :return:  dictionary of {action : state} pairs reachable
  27.                  from this state
  28.        :rtype: dict
  29.        """
  30.         raise NotImplementedError
  31.  
  32.     def actions(self, state):
  33.         """Given a state, return a list of all actions possible
  34.        from that state
  35.  
  36.        :param state: given state
  37.        :return: list of actions
  38.        :rtype: list
  39.        """
  40.         raise NotImplementedError
  41.  
  42.     def result(self, state, action):
  43.         """Given a state and action, return the resulting state
  44.  
  45.        :param state: given state
  46.        :param action: given action
  47.        :return: resulting state
  48.        """
  49.         raise NotImplementedError
  50.  
  51.     def goal_test(self, state):
  52.         """Return True if the state is a goal. The default method compares
  53.        the state to self.goal, as specified in the constructor. Implement
  54.        this method if checking against a single self.goal is not enough.
  55.  
  56.        :param state: given state
  57.        :return: is the given state a goal state
  58.        :rtype: bool
  59.        """
  60.         return state == self.goal
  61.  
  62.     def path_cost(self, c, state1, action, state2):
  63.         """Return the cost of a solution path that arrives at state2 from state1
  64.        via action, assuming cost c to get up to state1. If the problem is such
  65.        that the path doesn't matter, this function will only look at state2. 
  66.        If the path does matter, it will consider c and maybe state1 and action.
  67.        The default method costs 1 for every step in the path.
  68.  
  69.        :param c: cost of the path to get up to state1
  70.        :param state1: given current state
  71.        :param action: action that needs to be done
  72.        :param state2: state to arrive to
  73.        :return: cost of the path after executing the action
  74.        :rtype: float
  75.        """
  76.         return c + 1
  77.  
  78.     def value(self):
  79.         """For optimization problems, each state has a value.
  80.        Hill-climbing and related algorithms try to maximize this value.
  81.  
  82.        :return: state value
  83.        :rtype: float
  84.        """
  85.         raise NotImplementedError
  86.  
  87.  
  88. """
  89. Definition of the class for node structure of the search.
  90. The class Node is not inherited
  91. """
  92.  
  93.  
  94. class Node:
  95.     def __init__(self, state, parent=None, action=None, path_cost=0):
  96.         """Create node from the search tree,  obtained from the parent by
  97.        taking the action
  98.  
  99.        :param state: current state
  100.        :param parent: parent state
  101.        :param action: action
  102.        :param path_cost: path cost
  103.        """
  104.         self.state = state
  105.         self.parent = parent
  106.         self.action = action
  107.         self.path_cost = path_cost
  108.         self.depth = 0  # search depth
  109.         if parent:
  110.             self.depth = parent.depth + 1
  111.  
  112.     def __repr__(self):
  113.         return "<Node %s>" % (self.state,)
  114.  
  115.     def __lt__(self, node):
  116.         return self.state < node.state
  117.  
  118.     def expand(self, problem):
  119.         """List the nodes reachable in one step from this node.
  120.  
  121.        :param problem: given problem
  122.        :return: list of available nodes in one step
  123.        :rtype: list(Node)
  124.        """
  125.         return [self.child_node(problem, action)
  126.                 for action in problem.actions(self.state)]
  127.  
  128.     def child_node(self, problem, action):
  129.         """Return a child node from this node
  130.  
  131.        :param problem: given problem
  132.        :param action: given action
  133.        :return: available node  according to the given action
  134.        :rtype: Node
  135.        """
  136.         next_state = problem.result(self.state, action)
  137.         return Node(next_state, self, action,
  138.                     problem.path_cost(self.path_cost, self.state,
  139.                                       action, next_state))
  140.  
  141.     def solution(self):
  142.         """Return the sequence of actions to go from the root to this node.
  143.  
  144.        :return: sequence of actions
  145.        :rtype: list
  146.        """
  147.         return [node.action for node in self.path()[1:]]
  148.  
  149.     def solve(self):
  150.         """Return the sequence of states to go from the root to this node.
  151.  
  152.        :return: list of states
  153.        :rtype: list
  154.        """
  155.         return [node.state for node in self.path()[0:]]
  156.  
  157.     def path(self):
  158.         """Return a list of nodes forming the path from the root to this node.
  159.  
  160.        :return: list of states from the path
  161.        :rtype: list(Node)
  162.        """
  163.         x, result = self, []
  164.         while x:
  165.             result.append(x)
  166.             x = x.parent
  167.         result.reverse()
  168.         return result
  169.  
  170.     """We want the queue of nodes at breadth_first_search or
  171.     astar_search to not contain states-duplicates, so the nodes that
  172.     contain the same condition we treat as the same. [Problem: this can
  173.     not be desirable in other situations.]"""
  174.  
  175.     def __eq__(self, other):
  176.         return isinstance(other, Node) and self.state == other.state
  177.  
  178.     def __hash__(self):
  179.         return hash(self.state)
  180.  
  181.  
  182. """
  183. Definitions of helper structures for storing the list of generated, but not checked nodes
  184. """
  185.  
  186.  
  187. class Queue:
  188.     """Queue is an abstract class/interface. There are three types:
  189.         Stack(): Last In First Out Queue (stack).
  190.         FIFOQueue(): First In First Out Queue.
  191.         PriorityQueue(order, f): QQueue in sorted order (default min-first).
  192.     """
  193.  
  194.     def __init__(self):
  195.         raise NotImplementedError
  196.  
  197.     def append(self, item):
  198.         """Adds the item into the queue
  199.  
  200.        :param item: given element
  201.        :return: None
  202.        """
  203.         raise NotImplementedError
  204.  
  205.     def extend(self, items):
  206.         """Adds the items into the queue
  207.  
  208.        :param items: given elements
  209.        :return: None
  210.        """
  211.         raise NotImplementedError
  212.  
  213.     def pop(self):
  214.         """Returns the first element of the queue
  215.  
  216.        :return: first element
  217.        """
  218.         raise NotImplementedError
  219.  
  220.     def __len__(self):
  221.         """Returns the number of elements in the queue
  222.  
  223.        :return: number of elements in the queue
  224.        :rtype: int
  225.        """
  226.         raise NotImplementedError
  227.  
  228.     def __contains__(self, item):
  229.         """Check if the queue contains the element item
  230.  
  231.        :param item: given element
  232.        :return: whether the queue contains the item
  233.        :rtype: bool
  234.        """
  235.         raise NotImplementedError
  236.  
  237.  
  238. class Stack(Queue):
  239.     """Last-In-First-Out Queue."""
  240.  
  241.     def __init__(self):
  242.         self.data = []
  243.  
  244.     def append(self, item):
  245.         self.data.append(item)
  246.  
  247.     def extend(self, items):
  248.         self.data.extend(items)
  249.  
  250.     def pop(self):
  251.         return self.data.pop()
  252.  
  253.     def __len__(self):
  254.         return len(self.data)
  255.  
  256.     def __contains__(self, item):
  257.         return item in self.data
  258.  
  259.  
  260. class FIFOQueue(Queue):
  261.     """First-In-First-Out Queue."""
  262.  
  263.     def __init__(self):
  264.         self.data = []
  265.  
  266.     def append(self, item):
  267.         self.data.append(item)
  268.  
  269.     def extend(self, items):
  270.         self.data.extend(items)
  271.  
  272.     def pop(self):
  273.         return self.data.pop(0)
  274.  
  275.     def __len__(self):
  276.         return len(self.data)
  277.  
  278.     def __contains__(self, item):
  279.         return item in self.data
  280.  
  281.  
  282. class PriorityQueue(Queue):
  283.     """A queue in which the minimum (or maximum) element is returned first
  284.      (as determined by f and order). This structure is used in
  285.      informed search"""
  286.  
  287.     def __init__(self, order=min, f=lambda x: x):
  288.         """
  289.        :param order: sorting function, if order is min, returns the element
  290.                       with minimal f (x); if the order is max, then returns the
  291.                      element with maximum f (x).
  292.        :param f: function f(x)
  293.        """
  294.         assert order in [min, max]
  295.         self.data = []
  296.         self.order = order
  297.         self.f = f
  298.  
  299.     def append(self, item):
  300.         bisect.insort_right(self.data, (self.f(item), item))
  301.  
  302.     def extend(self, items):
  303.         for item in items:
  304.             bisect.insort_right(self.data, (self.f(item), item))
  305.  
  306.     def pop(self):
  307.         if self.order == min:
  308.             return self.data.pop(0)[1]
  309.         return self.data.pop()[1]
  310.  
  311.     def __len__(self):
  312.         return len(self.data)
  313.  
  314.     def __contains__(self, item):
  315.         return any(item == pair[1] for pair in self.data)
  316.  
  317.     def __getitem__(self, key):
  318.         for _, item in self.data:
  319.             if item == key:
  320.                 return item
  321.  
  322.     def __delitem__(self, key):
  323.         for i, (value, item) in enumerate(self.data):
  324.             if item == key:
  325.                 self.data.pop(i)
  326.  
  327. """
  328. Defining a class for the problem structure that we will solve with a search.
  329. The Problem class is an abstract class from which we make inheritance to define the basic
  330. characteristics of every problem we want to solve
  331. """
  332.  
  333.  
  334. class Problem:
  335.     def __init__(self, initial, goal=None):
  336.         self.initial = initial
  337.         self.goal = goal
  338.  
  339.     def successor(self, state):
  340.         """Given a state, return a dictionary of {action : state} pairs reachable
  341.        from this state. If there are many successors, consider an iterator
  342.        that yields the successors one at a time, rather than building them
  343.        all at once.
  344.  
  345.        :param state: given state
  346.        :return:  dictionary of {action : state} pairs reachable
  347.                  from this state
  348.        :rtype: dict
  349.        """
  350.         raise NotImplementedError
  351.  
  352.     def actions(self, state):
  353.         """Given a state, return a list of all actions possible
  354.        from that state
  355.  
  356.        :param state: given state
  357.        :return: list of actions
  358.        :rtype: list
  359.        """
  360.         raise NotImplementedError
  361.  
  362.     def result(self, state, action):
  363.         """Given a state and action, return the resulting state
  364.  
  365.        :param state: given state
  366.        :param action: given action
  367.        :return: resulting state
  368.        """
  369.         raise NotImplementedError
  370.  
  371.     def goal_test(self, state):
  372.         """Return True if the state is a goal. The default method compares
  373.        the state to self.goal, as specified in the constructor. Implement
  374.        this method if checking against a single self.goal is not enough.
  375.  
  376.        :param state: given state
  377.        :return: is the given state a goal state
  378.        :rtype: bool
  379.        """
  380.         return state == self.goal
  381.  
  382.     def path_cost(self, c, state1, action, state2):
  383.         """Return the cost of a solution path that arrives at state2 from state1
  384.        via action, assuming cost c to get up to state1. If the problem is such
  385.        that the path doesn't matter, this function will only look at state2.
  386.        If the path does matter, it will consider c and maybe state1 and action.
  387.        The default method costs 1 for every step in the path.
  388.  
  389.        :param c: cost of the path to get up to state1
  390.        :param state1: given current state
  391.        :param action: action that needs to be done
  392.        :param state2: state to arrive to
  393.        :return: cost of the path after executing the action
  394.        :rtype: float
  395.        """
  396.         return c + 1
  397.  
  398.     def value(self):
  399.         """For optimization problems, each state has a value.
  400.        Hill-climbing and related algorithms try to maximize this value.
  401.  
  402.        :return: state value
  403.        :rtype: float
  404.        """
  405.         raise NotImplementedError
  406.  
  407.  
  408. """
  409. Definition of the class for node structure of the search.
  410. The class Node is not inherited
  411. """
  412.  
  413.  
  414. class Node:
  415.     def __init__(self, state, parent=None, action=None, path_cost=0):
  416.         """Create node from the search tree,  obtained from the parent by
  417.        taking the action
  418.  
  419.        :param state: current state
  420.        :param parent: parent state
  421.        :param action: action
  422.        :param path_cost: path cost
  423.        """
  424.         self.state = state
  425.         self.parent = parent
  426.         self.action = action
  427.         self.path_cost = path_cost
  428.         self.depth = 0  # search depth
  429.         if parent:
  430.             self.depth = parent.depth + 1
  431.  
  432.     def __repr__(self):
  433.         return "<Node %s>" % (self.state,)
  434.  
  435.     def __lt__(self, node):
  436.         return self.state < node.state
  437.  
  438.     def expand(self, problem):
  439.         """List the nodes reachable in one step from this node.
  440.  
  441.        :param problem: given problem
  442.        :return: list of available nodes in one step
  443.        :rtype: list(Node)
  444.        """
  445.         return [self.child_node(problem, action)
  446.                 for action in problem.actions(self.state)]
  447.  
  448.     def child_node(self, problem, action):
  449.         """Return a child node from this node
  450.  
  451.        :param problem: given problem
  452.        :param action: given action
  453.        :return: available node  according to the given action
  454.        :rtype: Node
  455.        """
  456.         next_state = problem.result(self.state, action)
  457.         return Node(next_state, self, action,
  458.                     problem.path_cost(self.path_cost, self.state,
  459.                                       action, next_state))
  460.  
  461.     def solution(self):
  462.         """Return the sequence of actions to go from the root to this node.
  463.  
  464.        :return: sequence of actions
  465.        :rtype: list
  466.        """
  467.         return [node.action for node in self.path()[1:]]
  468.  
  469.     def solve(self):
  470.         """Return the sequence of states to go from the root to this node.
  471.  
  472.        :return: list of states
  473.        :rtype: list
  474.        """
  475.         return [node.state for node in self.path()[0:]]
  476.  
  477.     def path(self):
  478.         """Return a list of nodes forming the path from the root to this node.
  479.  
  480.        :return: list of states from the path
  481.        :rtype: list(Node)
  482.        """
  483.         x, result = self, []
  484.         while x:
  485.             result.append(x)
  486.             x = x.parent
  487.         result.reverse()
  488.         return result
  489.  
  490.     """We want the queue of nodes at breadth_first_search or
  491.    astar_search to not contain states-duplicates, so the nodes that
  492.    contain the same condition we treat as the same. [Problem: this can
  493.    not be desirable in other situations.]"""
  494.  
  495.     def __eq__(self, other):
  496.         return isinstance(other, Node) and self.state == other.state
  497.  
  498.     def __hash__(self):
  499.         return hash(self.state)
  500.  
  501.  
  502. """
  503. Definitions of helper structures for storing the list of generated, but not checked nodes
  504. """
  505.  
  506.  
  507. class Queue:
  508.     """Queue is an abstract class/interface. There are three types:
  509.        Stack(): Last In First Out Queue (stack).
  510.        FIFOQueue(): First In First Out Queue.
  511.        PriorityQueue(order, f): Queue in sorted order (default min-first).
  512.    """
  513.  
  514.     def __init__(self):
  515.         raise NotImplementedError
  516.  
  517.     def append(self, item):
  518.         """Adds the item into the queue
  519.  
  520.        :param item: given element
  521.        :return: None
  522.        """
  523.         raise NotImplementedError
  524.  
  525.     def extend(self, items):
  526.         """Adds the items into the queue
  527.  
  528.        :param items: given elements
  529.        :return: None
  530.        """
  531.         raise NotImplementedError
  532.  
  533.     def pop(self):
  534.         """Returns the first element of the queue
  535.  
  536.        :return: first element
  537.        """
  538.         raise NotImplementedError
  539.  
  540.     def __len__(self):
  541.         """Returns the number of elements in the queue
  542.  
  543.        :return: number of elements in the queue
  544.        :rtype: int
  545.        """
  546.         raise NotImplementedError
  547.  
  548.     def __contains__(self, item):
  549.         """Check if the queue contains the element item
  550.  
  551.        :param item: given element
  552.        :return: whether the queue contains the item
  553.        :rtype: bool
  554.        """
  555.         raise NotImplementedError
  556.  
  557.  
  558. class Stack(Queue):
  559.     """Last-In-First-Out Queue."""
  560.  
  561.     def __init__(self):
  562.         self.data = []
  563.  
  564.     def append(self, item):
  565.         self.data.append(item)
  566.  
  567.     def extend(self, items):
  568.         self.data.extend(items)
  569.  
  570.     def pop(self):
  571.         return self.data.pop()
  572.  
  573.     def __len__(self):
  574.         return len(self.data)
  575.  
  576.     def __contains__(self, item):
  577.         return item in self.data
  578.  
  579.  
  580. class FIFOQueue(Queue):
  581.     """First-In-First-Out Queue."""
  582.  
  583.     def __init__(self):
  584.         self.data = []
  585.  
  586.     def append(self, item):
  587.         self.data.append(item)
  588.  
  589.     def extend(self, items):
  590.         self.data.extend(items)
  591.  
  592.     def pop(self):
  593.         return self.data.pop(0)
  594.  
  595.     def __len__(self):
  596.         return len(self.data)
  597.  
  598.     def __contains__(self, item):
  599.         return item in self.data
  600.  
  601.  
  602. class PriorityQueue(Queue):
  603.     """A queue in which the minimum (or maximum) element is returned first
  604.     (as determined by f and order). This structure is used in
  605.     informed search"""
  606.  
  607.     def __init__(self, order=min, f=lambda x: x):
  608.         """
  609.        :param order: sorting function, if order is min, returns the element
  610.                      with minimal f (x); if the order is max, then returns the
  611.                      element with maximum f (x).
  612.        :param f: function f(x)
  613.        """
  614.         assert order in [min, max]
  615.         self.data = []
  616.         self.order = order
  617.         self.f = f
  618.  
  619.     def append(self, item):
  620.         bisect.insort_right(self.data, (self.f(item), item))
  621.  
  622.     def extend(self, items):
  623.         for item in items:
  624.             bisect.insort_right(self.data, (self.f(item), item))
  625.  
  626.     def pop(self):
  627.         if self.order == min:
  628.             return self.data.pop(0)[1]
  629.         return self.data.pop()[1]
  630.  
  631.     def __len__(self):
  632.         return len(self.data)
  633.  
  634.     def __contains__(self, item):
  635.         return any(item == pair[1] for pair in self.data)
  636.  
  637.     def __getitem__(self, key):
  638.         for _, item in self.data:
  639.             if item == key:
  640.                 return item
  641.  
  642.     def __delitem__(self, key):
  643.         for i, (value, item) in enumerate(self.data):
  644.             if item == key:
  645.                 self.data.pop(i)
  646.  
  647.  
  648. """
  649. Uninformed tree search.
  650. Within the tree we do not solve the loops.
  651. """
  652.  
  653.  
  654. def tree_search(problem, fringe):
  655.     """Search through the successors of a problem to find a goal.
  656.  
  657.    :param problem: given problem
  658.    :param fringe:  empty queue
  659.    :return: Node
  660.    """
  661.     fringe.append(Node(problem.initial))
  662.     while fringe:
  663.         node = fringe.pop()
  664.         print(node.state)
  665.         if problem.goal_test(node.state):
  666.             return node
  667.         fringe.extend(node.expand(problem))
  668.     return None
  669.  
  670.  
  671. def breadth_first_tree_search(problem):
  672.     """Search the shallowest nodes in the search tree first.
  673.  
  674.    :param problem: given problem
  675.    :return: Node
  676.    """
  677.     return tree_search(problem, FIFOQueue())
  678.  
  679.  
  680. def depth_first_tree_search(problem):
  681.     """Search the deepest nodes in the search tree first.
  682.  
  683.    :param problem: given problem
  684.    :return: Node
  685.    """
  686.     return tree_search(problem, Stack())
  687.  
  688.  
  689. """
  690. Uninformed graph search
  691. The main difference is that here we do not allow loops,
  692. i.e. repetition of states
  693. """
  694.  
  695.  
  696. def graph_search(problem, fringe):
  697.     """Search through the successors of a problem to find a goal.
  698.      If two paths reach a state, only use the best one.
  699.  
  700.    :param problem: given problem
  701.    :param fringe: empty queue
  702.    :return: Node
  703.    """
  704.     closed = set()
  705.     fringe.append(Node(problem.initial))
  706.     while fringe:
  707.         node = fringe.pop()
  708.         if problem.goal_test(node.state):
  709.             return node
  710.         if node.state not in closed:
  711.             closed.add(node.state)
  712.             fringe.extend(node.expand(problem))
  713.     return None
  714.  
  715.  
  716. def breadth_first_graph_search(problem):
  717.     """Search the shallowest nodes in the search tree first.
  718.  
  719.    :param problem: given problem
  720.    :return: Node
  721.    """
  722.     return graph_search(problem, FIFOQueue())
  723.  
  724.  
  725. def depth_first_graph_search(problem):
  726.     """Search the deepest nodes in the search tree first.
  727.  
  728.    :param problem: given problem
  729.    :return: Node
  730.    """
  731.     return graph_search(problem, Stack())
  732.  
  733.  
  734. def depth_limited_search(problem, limit=50):
  735.     def recursive_dls(node, problem, limit):
  736.         """Helper function for depth limited"""
  737.         cutoff_occurred = False
  738.         if problem.goal_test(node.state):
  739.             return node
  740.         elif node.depth == limit:
  741.             return 'cutoff'
  742.         else:
  743.             for successor in node.expand(problem):
  744.                 result = recursive_dls(successor, problem, limit)
  745.                 if result == 'cutoff':
  746.                     cutoff_occurred = True
  747.                 elif result is not None:
  748.                     return result
  749.         if cutoff_occurred:
  750.             return 'cutoff'
  751.         return None
  752.  
  753.     return recursive_dls(Node(problem.initial), problem, limit)
  754.  
  755.  
  756. def iterative_deepening_search(problem):
  757.     for depth in range(sys.maxsize):
  758.         result = depth_limited_search(problem, depth)
  759.         if result is not 'cutoff':
  760.             return result
  761.  
  762.  
  763. def uniform_cost_search(problem):
  764.     """Search the nodes in the search tree with lowest cost first."""
  765.     return graph_search(problem, PriorityQueue(min, lambda a: a.path_cost))
  766.  
  767.  
  768. """
  769. Informed graph search.
  770. """
  771.  
  772.  
  773. def memoize(fn, slot=None):
  774.     """ Store the calculated value for any arguments list. If a
  775.    slot is specified, store the result in that slot in the first
  776.    argument. If slot is false, store the results in a dictionary.
  777.  
  778.    :param fn: given function
  779.    :param slot: name of the attribute for saving the function results
  780.    :return: modified function for storing the results
  781.    """
  782.     if slot:
  783.         def memoized_fn(obj, *args):
  784.             if hasattr(obj, slot):
  785.                 return getattr(obj, slot)
  786.             else:
  787.                 val = fn(obj, *args)
  788.                 setattr(obj, slot, val)
  789.                 return val
  790.     else:
  791.         def memoized_fn(*args):
  792.             if args not in memoized_fn.cache:
  793.                 memoized_fn.cache[args] = fn(*args)
  794.             return memoized_fn.cache[args]
  795.  
  796.         memoized_fn.cache = {}
  797.     return memoized_fn
  798.  
  799.  
  800. def best_first_graph_search(problem, f):
  801.     """The idea of Best First Search is to use an evaluation function
  802.    to decide which adjacent is most promising and then explore.
  803.  
  804.    :param problem: given problem
  805.    :param f: given heuristic function
  806.    :return: Node or None
  807.    """
  808.     f = memoize(f, 'f')
  809.     node = Node(problem.initial)
  810.     if problem.goal_test(node.state):
  811.         return node
  812.     frontier = PriorityQueue(min, f)
  813.     frontier.append(node)
  814.     explored = set()
  815.     while frontier:
  816.         node = frontier.pop()
  817.         if problem.goal_test(node.state):
  818.             return node
  819.         explored.add(node.state)
  820.         for child in node.expand(problem):
  821.             if child.state not in explored and child not in frontier:
  822.                 frontier.append(child)
  823.             elif child in frontier:
  824.                 incumbent = frontier[child]
  825.                 if f(child) < f(incumbent):
  826.                     del frontier[incumbent]
  827.                     frontier.append(child)
  828.     return None
  829.  
  830.  
  831. def greedy_best_first_graph_search(problem, h=None):
  832.     """ Greedy best-first search is implemented with f(n) = h(n).
  833.  
  834.    :param problem: given problem
  835.    :param h: given heuristic function
  836.    :return: Node or None
  837.    """
  838.     h = memoize(h or problem.h, 'h')
  839.     return best_first_graph_search(problem, h)
  840.  
  841.  
  842. def astar_search(problem, h=None):
  843.     """ A* search is best-first graph search where f(n) = g(n) + h(n).
  844.  
  845.    :param problem: given problem
  846.    :param h: given heuristic function
  847.    :return: Node or None
  848.    """
  849.     h = memoize(h or problem.h, 'h')
  850.     return best_first_graph_search(problem, lambda n: n.path_cost + h(n))
  851.  
  852.  
  853. #   ===========
  854.  
  855. class GraphExplorer(Problem):
  856.     def __init__(self, initial, goal=None):
  857.         super().__init__(initial, goal)
  858.        
  859.     def successor(self, state):
  860.         succesors = dict()
  861.        
  862.         player_position = state[0]
  863.         star1 = state[1]
  864.         star2 = state[2]
  865.         edges = state[3]
  866.        
  867.         #Levo
  868.         new_maze = self.moveFigure("Levo", state);
  869.         if new_maze != None: succesors['Levo'] = new_maze
  870.  
  871.         #Desno
  872.         new_maze = self.moveFigure("Desno", state);
  873.         if new_maze != None: succesors['Desno'] = new_maze
  874.  
  875.         #Gore
  876.         new_maze = self.moveFigure("Gore", state);
  877.         if new_maze != None: succesors['Gore'] = new_maze
  878.  
  879.         #Dolu
  880.         new_maze = self.moveFigure("Dolu", state);
  881.         if new_maze != None: succesors['Dolu'] = new_maze
  882.  
  883.         #DoluDesno
  884.         new_maze = self.moveFigure("DoluDesno", state);
  885.         if new_maze != None: succesors['DoluDesno'] = new_maze
  886.  
  887.         #GoreLevo
  888.         new_maze = self.moveFigure("GoreLevo", state);
  889.         if new_maze != None: succesors['GoreLevo'] = new_maze
  890.  
  891.         return succesors
  892.    
  893.     def goal_test(self, state):
  894.         star1 = state[1]
  895.         star2 = state[2]
  896.         return star1 == -1 and star2 == -1
  897.    
  898.     def actions(self, state):
  899.         return self.successor(state).keys()
  900.    
  901.     def result(self, state, action):
  902.         return self.successor(state)[action]
  903.    
  904.     def moveFigure(self, direction, state):
  905.         availableMovements = movements[state[0]]
  906.         oldPosition, newPosition = state[0], None
  907.         new_maze = None
  908.  
  909.         star1, star2 = state[1], state[2]
  910.  
  911.         edgeList = list(state[3])
  912.  
  913.         for movement in availableMovements:
  914.             if movement[1] != direction: continue
  915.             else:
  916.                 edge = oldPosition, movement[0]
  917.                 reverseEdge = movement[0], oldPosition
  918.                 if edge not in edgeList and reverseEdge not in edgeList: return None
  919.                
  920.                 if edge in edgeList:
  921.                     edgeList.remove(edge)
  922.                 if reverseEdge in edgeList:
  923.                     edgeList.remove(reverseEdge)
  924.                 newPosition = movement[0]
  925.  
  926.                 if newPosition == star1:
  927.                     star1 = -1
  928.                 if newPosition == star2:
  929.                     star2 = -1
  930.  
  931.                 new_maze = (newPosition, star1, star2, tuple(edgeList))
  932.         return new_maze
  933.    
  934. movements = {
  935.     1: ((2, "Desno"), (5, "Dolu")), 2: ((1, "Levo"), (6, "Dolu")), 3: ((4, "Desno"), (7, "Dolu")),
  936.     4: ((3, "Levo"), (8, "Dolu")), 5: ((1, "Gore"), (6, "Desno")),
  937.     6: ((2, "Gore"), (5, "Levo"), (7, "Desno"), (10, "Dolu"), (11, "DoluDesno")),
  938.     7: ((3, "Gore"), (6, "Levo"), (8, "Desno"), (11, "Dolu")), 8: ((4, "Gore"), (7, "Levo")),
  939.     9: ((10, "Desno"), (13, "Dolu")), 10: ((6, "Gore"), (9, "Levo"), (11, "Desno"), (14, "Dolu")),
  940.     11: ((7, "Gore"), (10, "Levo"), (12, "Desno"), (15, "Dolu"), (6, "GoreLevo")),
  941.     12: ((11, "Levo"), (16, "Dolu")), 13: ((14, "Desno"), (9, "Gore")), 14: ((13, "Levo"), (10, "Gore")),
  942.     15: ((16, "Desno"), (11, "Gore")), 16: ((15, "Levo"), (12, "Gore"))
  943. }
  944.  
  945. if __name__ == '__main__':
  946.     player_position = int(input())
  947.     s1_position = int(input())
  948.     s2_position = int(input())
  949.  
  950.     edges = (
  951.         (1,2), (3,4), (5,6), (6,7), (7,8), (9,10), (10,11), (11,12), (13,14), (15,16),
  952.         (1,5), (9,13), (2,6), (6,10), (10,14), (3,7), (7,11), (11,15), (4,8), (12,16), (6,11)
  953.     )
  954.  
  955.     problem = GraphExplorer((player_position, s1_position, s2_position, edges))
  956.  
  957.     solution = breadth_first_graph_search(problem)
  958.  
  959.     print(solution.solution())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement