package aima.core.search.framework.qsearch; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; import aima.core.search.framework.Node; import aima.core.search.framework.NodeExpander; import aima.core.search.framework.problem.Problem; /** * Artificial Intelligence A Modern Approach (3rd Edition): Figure 3.7, page 77. * <br> * * <pre> * function GRAPH-SEARCH(problem) returns a solution, or failure * initialize the frontier using the initial state of problem * initialize the explored set to be empty * loop do * if the frontier is empty then return failure * choose a leaf node and remove it from the frontier * if the node contains a goal state then return the corresponding solution * add the node to the explored set * expand the chosen node, adding the resulting nodes to the frontier * only if not in the frontier or explored set * </pre> * * Figure 3.7 An informal description of the general graph-search algorithm. * * <br> * This implementation is based on the template method * {@link QueueSearch#findNode(Problem, Queue)} of the superclass and provides * implementations for the needed primitive operations. It implements a special * version of graph search which keeps the frontier short by focusing on the * best node for each state only. It should only be used in combination with * priority queue frontiers. If a node is added to the frontier, this * implementation checks whether another node for the same state already exists * and decides whether to replace it or ignore the new node depending on the * node's costs (comparator of priority queue is used, if available). * * @author Ravi Mohan * @author Ciaran O'Reilly * @author Ruediger Lunde */ public class GraphSearchReducedFrontier extends QueueSearch { private Set<Object> explored = new HashSet<Object>(); private Map<Object, Node> frontierNodeLookup = new HashMap<Object, Node>(); private Comparator<? super Node> nodeComparator = null; public GraphSearchReducedFrontier() { this(new NodeExpander()); } public GraphSearchReducedFrontier(NodeExpander nodeExpander) { super(nodeExpander); } /** * Sets the comparator if a priority queue is used, resets explored list and * state map and calls the inherited version of search. */ @Override public Node findNode(Problem problem, Queue<Node> frontier) { // initialize the explored set to be empty if (frontier instanceof PriorityQueue<?>) nodeComparator = ((PriorityQueue<Node>) frontier).comparator(); explored.clear(); frontierNodeLookup.clear(); return super.findNode(problem, frontier); } public Comparator<? super Node> getNodeComparator() { return nodeComparator; } /** * Inserts the node into the frontier if the node's state is not yet * explored and not present in the frontier. If a second node for the same * state is already part of the frontier, it is checked, which node is * better (with respect to priority). Depending of the result, the existing * node is replaced or the new node is dropped. */ @Override protected void addToFrontier(Node node) { if (!explored.contains(node.getState())) { Node frontierNode = frontierNodeLookup.get(node.getState()); if (frontierNode == null) { // child.STATE is not in frontier and not yet explored frontier.add(node); frontierNodeLookup.put(node.getState(), node); updateMetrics(frontier.size()); } else if (nodeComparator != null && nodeComparator.compare(node, frontierNode) < 0) { // child.STATE is in frontier with higher cost // replace that frontier node with child if (frontier.remove(frontierNode)) frontierNodeLookup.remove(frontierNode.getState()); frontier.add(node); frontierNodeLookup.put(node.getState(), node); } } } /** * Removes the node at the head of the frontier, adds the corresponding * state to the explored set, and returns the node. * * @return the node at the head of the frontier. */ @Override protected Node removeFromFrontier() { Node result = frontier.remove(); frontierNodeLookup.remove(result.getState()); // add the node to the explored set explored.add(result.getState()); updateMetrics(frontier.size()); return result; } /** * Checks whether the frontier contains not yet expanded nodes. */ @Override protected boolean isFrontierEmpty() { return frontier.isEmpty(); } }