package aima.core.search.framework; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import aima.core.agent.Action; import aima.core.util.datastructure.Queue; /** * Artificial Intelligence A Modern Approach (3rd Edition): Figure 3.7, page 77. <br> * <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. * * @author Ravi Mohan * @author Ciaran O'Reilly */ public class GraphSearch extends QueueSearch { private Set<Object> explored = new HashSet<Object>(); private Map<Object, Node> frontierState = new HashMap<Object, Node>(); private Comparator<Node> replaceFrontierNodeAtStateCostFunction = null; private List<Node> addToFrontier = new ArrayList<Node>(); public Comparator<Node> getReplaceFrontierNodeAtStateCostFunction() { return replaceFrontierNodeAtStateCostFunction; } public void setReplaceFrontierNodeAtStateCostFunction( Comparator<Node> replaceFrontierNodeAtStateCostFunction) { this.replaceFrontierNodeAtStateCostFunction = replaceFrontierNodeAtStateCostFunction; } // Need to override search() method so that I can re-initialize // the explored set should multiple calls to search be made. @Override public List<Action> search(Problem problem, Queue<Node> frontier) { // initialize the explored set to be empty explored.clear(); frontierState.clear(); return super.search(problem, frontier); } @Override public Node popNodeFromFrontier() { Node toRemove = super.popNodeFromFrontier(); frontierState.remove(toRemove.getState()); return toRemove; } @Override public boolean removeNodeFromFrontier(Node toRemove) { boolean removed = super.removeNodeFromFrontier(toRemove); if (removed) { frontierState.remove(toRemove.getState()); } return removed; } @Override public List<Node> getResultingNodesToAddToFrontier(Node nodeToExpand, Problem problem) { addToFrontier.clear(); // add the node to the explored set explored.add(nodeToExpand.getState()); // expand the chosen node, adding the resulting nodes to the frontier for (Node cfn : expandNode(nodeToExpand, problem)) { Node frontierNode = frontierState.get(cfn.getState()); boolean yesAddToFrontier = false; if (null == frontierNode) { if (!explored.contains(cfn.getState())) { // child.STATE is not in frontier and not yet explored yesAddToFrontier = true; } } else if (null != replaceFrontierNodeAtStateCostFunction && replaceFrontierNodeAtStateCostFunction.compare(cfn, frontierNode) < 0) { // child.STATE is in frontier with higher cost // replace that frontier node with child yesAddToFrontier = true; // Want to replace the current frontier node with the child // node therefore mark the child to be added and remove the // current fontierNode removeNodeFromFrontier(frontierNode); // Ensure removed from add to frontier as well // as 1 or more may reach the same state at the same time addToFrontier.remove(frontierNode); } if (yesAddToFrontier) { addToFrontier.add(cfn); frontierState.put(cfn.getState(), cfn); } } return addToFrontier; } }