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    }