001 package aima.search.online; 002 003 import java.util.ArrayList; 004 import java.util.Hashtable; 005 import java.util.List; 006 007 import aima.basic.Agent; 008 import aima.basic.Percept; 009 010 import aima.search.framework.Problem; 011 import aima.search.framework.Successor; 012 013 /** 014 * Artificial Intelligence A Modern Approach (2nd Edition): Figure 4.20, page 126. 015 * <code> 016 * function ONLINE-DFS-AGENT(s') returns an action 017 * inputs: s', a percept that identifies the current state 018 * static: result, a table, indexed by action and state, initially empty 019 * unexplored, a table that lists, for each visited state, the actions not yet tried 020 * unbacktracked, a table that lists, for each visited state, the backtracks not yet tried 021 * s, a, the previous state and action, initially null 022 * 023 * if GOAL-TEST(s') then return stop 024 * if s' is a new state then unexplored[s'] <- ACTIONS(s') 025 * if s is not null then do 026 * result[a, s] <- s' 027 * add s to the front of the unbacktracked[s'] 028 * if unexplored[s'] is empty then 029 * if unbacktracked[s'] is empty then return stop 030 * else a <- an action b such that result[b, s'] = POP(unbacktracked[s']) 031 * else a <- POP(unexplored[s']) 032 * s <- s' 033 * return a 034 * </code> 035 * Figure 4.20 An online search agent that uses depth-first exploration. The agent is 036 * applicable only in bidirectional search spaces.<br> 037 * <br> 038 * Note: This algorithm fails to exit if the goal does not exist (e.g. A<->B Goal=X), 039 * this could be an issue with the implementation. Comments welcome. 040 */ 041 042 /** 043 * @author Ciaran O'Reilly 044 * 045 */ 046 public class OnlineDFSAgent extends Agent { 047 048 private Problem problem; 049 // static: result, a table, indexed by action and state, initially empty 050 private final Hashtable<ActionState, Percept> result = new Hashtable<ActionState, Percept>(); 051 // unexplored, a table that lists, for each visited state, the actions not 052 // yet tried 053 private final Hashtable<Percept, List<Object>> unexplored = new Hashtable<Percept, List<Object>>(); 054 // unbacktracked, a table that lists, 055 // for each visited state, the backtracks not yet tried 056 private final Hashtable<Percept, List<Percept>> unbacktracked = new Hashtable<Percept, List<Percept>>(); 057 // s, a, the previous state and action, initially null 058 private Percept s = null; 059 private Object a = null; 060 061 public OnlineDFSAgent(Problem problem) { 062 setProblem(problem); 063 } 064 065 public Problem getProblem() { 066 return problem; 067 } 068 069 public void setProblem(Problem problem) { 070 this.problem = problem; 071 init(); 072 } 073 074 // function ONLINE-DFS-AGENT(s') returns an action 075 // inputs: s', a percept that identifies the current state 076 @Override 077 public String execute(Percept sComma) { 078 // if GOAL-TEST(s') then return stop 079 if (!goalTest(sComma)) { 080 // if s' is a new state then unexplored[s'] <- ACTIONS(s') 081 if (!unexplored.containsKey(sComma)) { 082 unexplored.put(sComma, actions(sComma)); 083 } 084 085 // if s is not null then do 086 if (null != s) { 087 // result[a, s] <- s' 088 result.put(new ActionState(a, s), sComma); 089 090 // Ensure the unbacktracked always has a list for s' 091 if (!unbacktracked.containsKey(sComma)) { 092 unbacktracked.put(sComma, new ArrayList<Percept>()); 093 } 094 095 // add s to the front of the unbacktracked[s'] 096 unbacktracked.get(sComma).add(s); 097 } 098 // if unexplored[s'] is empty then 099 if (unexplored.get(sComma).size() == 0) { 100 // if unbacktracked[s'] is empty then return stop 101 if (unbacktracked.get(sComma).size() == 0) { 102 a = Agent.NO_OP; 103 } else { 104 // else a <- an action b such that result[b, s'] = 105 // POP(unbacktracked[s']) 106 Percept popped = unbacktracked.get(sComma).remove( 107 unbacktracked.get(sComma).size() - 1); 108 for (ActionState as : result.keySet()) { 109 if (as.getState().equals(sComma) 110 && result.get(as).equals(popped)) { 111 a = as.getAction(); 112 break; 113 } 114 } 115 } 116 } else { 117 // else a <- POP(unexplored[s']) 118 a = unexplored.get(sComma).remove( 119 unexplored.get(sComma).size() - 1); 120 } 121 } else { 122 a = Agent.NO_OP; 123 } 124 125 if (Agent.NO_OP.equals(a)) { 126 // I'm either at the Goal or can't get to it, 127 // which in either case I'm finished so just die. 128 die(); 129 } 130 131 // s <- s' 132 s = sComma; 133 // return a 134 return a.toString(); 135 } 136 137 // 138 // PRIVATE METHODS 139 // 140 141 private void init() { 142 live(); 143 result.clear(); 144 unexplored.clear(); 145 unbacktracked.clear(); 146 s = null; 147 a = null; 148 } 149 150 private boolean goalTest(Percept state) { 151 return getProblem().isGoalState(state); 152 } 153 154 private List<Object> actions(Percept state) { 155 List<Object> actions = new ArrayList<Object>(); 156 157 List<Successor> successors = getProblem().getSuccessorFunction() 158 .getSuccessors(state); 159 160 for (Successor s : successors) { 161 actions.add(s.getAction()); 162 } 163 164 return actions; 165 } 166 }