/** * The Script class is used to generate responses to questions. The * interpretation of the questions is done in Interact, while Script is * mainly for generating the responses in natural language. */ package com.jaivox.interpreter; import java.util.*; import java.awt.Point; public class Script { Interact Act; Info Info; String data [][]; String fields []; String categories []; int nr, nf; TreeMap <String, String> qspecs; Vector <String> queries; TreeMap <String, String> qa; static String intro [] = { "I guess ", "It could be ", "It looks like ", "It seems like ", "Apparently ", "This may be " }; String yesanswers [] = {"that is the case", "the answer is yes", "that is true"}; String noanswers [] = {"that is not the case", "the answer is no", "that is false"}; String confused [] = {"I cannot understand your question ", "I am unable to figure it out ", "Sorry about being so dense ", "Hmm, may be I am not figuring it out right, ", "Really sorry about this, " }; String followup [] = {"can you ask a different question ", "can you ask this another way ", "is there another way to ask what you need ", "perhaps you can reformulate the question " }; String topics [] = {"I recall we were talking about ", "may be there is something related to ", "is your question about ", "could it be that you are asking about " }; String oneitem [] = {"there is only one such, ", "there is exactly one answer, ", "Only one answer fits, ", "one solution, ", "there is one match, " }; String twoitems [] = {"there are two such, ", "there are exactly two answers, ", "two answers may fit, ", "two solutions, ", "there are two matches, " }; String manyitems [] = {"there are several solutions, ", "there are many answers, ", "many ways to answer this, ", "there are quite a few solutions, ", "there are several matches, "}; String forinstance [] = {"for example ", "for instance ", "such as ", "as an example ", "one of them is ", "one such is ", "one such example is "}; TreeMap <String, Point> quants; TreeMap <String, String []> qwords; static int nSpecials = 2; // top values to show public Script (Interact inter, Info inf) { Act = inter; Info = inf; initializeQuants (); } void Debug (String s) { System.out.println ("[Script]" + s); } void initializeQuants () { quants = new TreeMap <String, Point> (); /* quants.put ("JJS-N", new Point ( 0, 10)); quants.put ("JJR-N", new Point (10, 30)); quants.put ("JJ-N", new Point (30, 50)); quants.put ("JJ-P", new Point (50, 70)); quants.put ("JJR-P", new Point (70, 90)); quants.put ("JJS-P", new Point (90, 99)); */ quants.put ("jjs-n", new Point ( 0, 10)); quants.put ("jjr-n", new Point (10, 30)); quants.put ("jj-n", new Point (30, 50)); quants.put ("jj-p", new Point (50, 70)); quants.put ("jjr-p", new Point (70, 90)); quants.put ("jjs-p", new Point (90, 99)); refreshData (); } void refreshData () { data = Info.data; fields = Info.fields; categories = Info.categories; nr = Info.nr; nf = Info.nf; } void loadhistory () { qspecs = Act.qspecs; queries = Act.queries; qa = Act.qa; } /** * Create an answer in natural language in response to a specific question @param question @return */ public String makeAnswer (String question) { loadhistory (); String spec = qspecs.get (question).toLowerCase (); // Debug ("Getting answer from: "+spec+" for question:"+question); // take the spec apart String command = ""; String arg = ""; String field = ""; String attribute = ""; String quant = ""; String nnp = ""; String els = ""; StringTokenizer st = new StringTokenizer (spec, "(),\r\n"); command = st.nextToken ().trim (); if (command.equals ("command")) { arg = st.nextToken ().trim (); String result = handleCommand (arg); return result; } if (st.hasMoreTokens ()) field = st.nextToken ().trim (); if (st.hasMoreTokens ()) attribute = st.nextToken ().trim (); if (st.hasMoreTokens ()) quant = st.nextToken ().trim (); if (st.hasMoreTokens ()) nnp = st.nextToken ().trim (); if (st.hasMoreTokens ()) els = st.nextToken ().trim (); if (!nnp.equals ("")) { int pos = nnp.indexOf (":"); if (pos != -1) { nnp = nnp.substring (pos+1).trim (); } } // handle various situations // redirects // if (command.equals ("command")) { // } // simple selection if (FAQspecified (field, attribute, quant) && nnp.equals ("") && els.equals ("")) { // Debug ("Simple question: "+spec); // clear the history? Act.clearhistory (); loadhistory (); String answer = generateSimple (command, field, attribute, quant); qa.put (question, answer); queries.add (question); return answer; } // selection with a proper name specified if (FAQspecified (field, attribute, quant) && !nnp.equals ("") && els.equals ("")) { // Debug ("Question with comparison "+spec); // clear the history? Act.clearhistory (); loadhistory (); String answer = generateSpecific (command, field, attribute, quant, nnp); qa.put (question, answer); queries.add (question); return answer; } // use history to fill in field, attribute, quant and nnp if they are // blank int n = queries.size (); int m = Math.max (0, n-3); // don't go back further Vector <String> nnps = new Vector <String> (); for (int i=n-1; i>=m; i--) { String oldq = queries.elementAt (i); String oldspec = qspecs.get (oldq); if (oldspec.startsWith ("(dir")) continue; String oldans = qa.get (oldq); st = new StringTokenizer (oldspec, "(),\r\n"); String oldcmd = st.nextToken (); if (oldcmd.equals ("command")) continue; String oldfield = ""; String oldattr = ""; String oldquant = ""; String oldnnp = ""; String oldels = ""; if (st.hasMoreTokens ()) oldfield = st.nextToken ().trim (); if (st.hasMoreTokens ()) oldattr = st.nextToken ().trim (); if (st.hasMoreTokens ()) oldquant = st.nextToken ().trim (); if (st.hasMoreTokens ()) oldnnp = st.nextToken ().trim (); if (st.hasMoreTokens ()) oldels = st.nextToken ().trim (); // patch field, attr etc if they are blank if (field.equals ("_") && !oldfield.equals ("")) field = oldfield; if (attribute.equals ("_") && !oldattr.equals ("")) attribute = oldattr; if (quant.equals ("_") && !oldquant.equals ("")) quant = oldquant; if (nnp.equals ("_") && !oldnnp.equals ("")) { nnp = oldnnp; // Debug ("Adding nnp from question: "+nnp); nnps.add (nnp); } // if (els.equals ("_") && !oldels.equals ("")) els = oldels; // check the answers to see if any of the data fields were in there String ans = qa.get (oldq); if (ans == null) continue; // Debug ("old question: "+oldq); // Debug ("old answer: "+ans); // nnps should contain all question and answer specifics for (int j=0; j<nr; j++) { if (ans.indexOf (data [j][0]) != -1) { // nnp = data [j][0]; nnps.add (data [j][0]); // Debug ("Adding nnp from answer: "+data [j][0]); } } } if (nnps.size () == 1) nnp = nnps.elementAt (0); if (!nnp.equals ("")) { int pos = nnp.indexOf (":"); if (pos != -1) { nnp = nnp.substring (pos+1).trim (); } } String newspec = "("+command+", "+field+", "+attribute+", "+quant; if (!nnp.equals ("")) newspec = newspec +", "+nnp; if (!els.equals ("")) newspec = newspec +", "+els; newspec = newspec + ")"; // simple selection after using history if (FAQspecified (field, attribute, quant) && nnp.equals ("") && els.equals ("")) { // Debug ("Using history, no nnp: "+newspec); // qspecs.put (question, newspec); String answer = generateSimple (command, field, attribute, quant); qa.put (question, answer); queries.add (question); return answer; } // selection with a proper name specified after using history if (FAQNspecified (field, attribute, quant, nnp) && els.equals ("")) { // Debug ("Using history with nnp: "+newspec); // qspecs.put (question, newspec); String answer = generateSpecific (command, field, attribute, quant, nnp); qa.put (question, answer); queries.add (question); return answer; } // if there is an els clause // even if the what of the else is not mentioned, use history to figure out // the what, then select the else if (FAQNspecified (field, attribute, quant, nnp) && !els.equals ("")) { // Debug ("Figuring else from: "+newspec); // qspecs.put (question, newspec); String answer = ""; if (nnps.size () == 1) { answer = generateSpecificElse (command, field, attribute, quant, nnp); } else { answer = generateElse (command, field, attribute, quant, nnps); } qa.put (question, answer); queries.add (question); return answer; } // can't figure out // Debug ("Can't figure out anything: "+question+" "+newspec); String result = "I can't figure out the answer, can you ask a new question?"; return result; } boolean FAQspecified (String field, String attribute, String quant) { if (field.equals ("")) return false; if (field.equals ("_")) return false; if (attribute.equals ("")) return false; if (attribute.equals ("_")) return false; if (quant.equals ("")) return false; if (quant.equals ("_")) return false; return true; } boolean FAQNspecified (String field, String attribute, String quant, String nnp) { if (!FAQspecified (field, attribute, quant)) return false; if (nnp.equals ("")) return false; if (nnp.equals ("_")) return false; return true; } String generateSimple (String cmd, String field, String attribute, String quant) { try { String dbname = field+"_"+attribute+"_"+quant; Point p = quants.get (quant); int plow = p.x; int phigh = p.y; int f = findColumn (attribute); if (f == -1) { Debug ("Could not find atrribute "+attribute); return "error"; } // String stop = "."; String stop = " "; // select the relevant data String selection [] = selectOrdering (f, plow, phigh); if (selection == null) { String problem = selectPhrase (intro) + selectPhrase (noanswers) + stop; return problem; } int count = selection.length; String result = ""; /* if (count > 0) { for (int i=0; i<count; i++) { System.out.println ("selection:"+i+" "+selection [i]); } } */ if (cmd.equals ("ask")) { if (count == 0) { result = selectPhrase (intro) + selectPhrase (noanswers) + stop; } else { result = selectPhrase (intro) + selectPhrase (yesanswers) + stop; } return result; } if (count == 0) { result ="something hard to determine"; } else if (count == 1) { result = selectPhrase (oneitem) + selection [0] +stop; } else if (count == 2) { result = selectPhrase (twoitems) + selection [0] + " and "+ selection [1] +stop; } else { result = selectPhrase (manyitems) + selectPhrase (forinstance) + selection [0] +stop; } return result; } catch (Exception e) { e.printStackTrace (); return "error"; } } String generateSpecific (String cmd, String field, String attribute, String quant, String nnp) { try { String dbname = field+"_"+attribute+"_"+quant+"_nnp"; boolean up = true; if (quant.endsWith ("-N")) up = false; int f = findColumn (attribute); if (f == -1) { Debug ("Could not find atrribute "+attribute); return "error"; } // String stop = "."; String stop = " "; String selection [] = selectComparative (f, up, nnp); if (selection == null) { String problem = selectPhrase (intro) + selectPhrase (noanswers) + stop; return problem; } int count = selection.length; String result = ""; if (cmd.equals ("ask")) { if (count == 0) { result = selectPhrase (intro) + selectPhrase (noanswers) + stop; } else { result = selectPhrase (intro) + selectPhrase (yesanswers) + stop; } return result; } /* if (count > 0) { for (int i=0; i<count; i++) { System.out.println ("selection:"+i+" "+selection [i]); } } */ if (count == 0) { result ="something hard to determine"; } else if (count == 1) { result = selectPhrase (oneitem) + selection [0] +stop; } else if (count == 2) { result = selectPhrase (twoitems) + selection [0] + " and "+ selection [1] +stop; } else { result = selectPhrase (manyitems) + selectPhrase (forinstance) + selection [0] +stop; } return result; } catch (Exception e) { e.printStackTrace (); return "error"; } } String generateSpecificElse (String cmd, String field, String attribute, String quant, String nnp) { try { String dbname = field+"_"+attribute+"_"+quant; boolean up = true; if (quant.endsWith ("-N")) up = false; int f = findColumn (attribute); if (f == -1) { Debug ("Could not find atrribute "+attribute); return "error"; } // String stop = "."; String stop = " "; // select the relevant data // String orig [] = selectOrdering (f, plow, phigh); String orig [] = selectComparative (f, up, nnp); if (orig == null) { String problem = selectPhrase (intro) + selectPhrase (noanswers) + stop; return problem; } // form selection from the rest int total = orig.length; Vector <String> hold = new Vector <String> (); for (int i=0; i<total; i++) { if (orig [i].equals (nnp)) continue; hold.add (orig [i]); } int count = hold.size (); String selection [] = new String [count]; for (int i=0; i<count; i++) { selection [i] = hold.elementAt (i); } String result = ""; if (cmd.equals ("ask")) { if (count == 0) { result = selectPhrase (intro) + selectPhrase (noanswers) + stop; } else { result = selectPhrase (intro) + selectPhrase (yesanswers) + stop; } return result; } /* if (count > 0) { for (int i=0; i<count; i++) { System.out.println ("selection:"+i+" "+selection [i]); } } */ if (count == 0) { result ="there is no other solution"; } else if (count == 1) { result = selectPhrase (oneitem) + selection [0] +stop; } else if (count == 2) { result = selectPhrase (twoitems) + selection [0] + " and "+ selection [1] + stop; } else { result = selectPhrase (manyitems) + selectPhrase (forinstance) + selection [0] +stop; } return result; } catch (Exception e) { e.printStackTrace (); return "error"; } } String generateElse (String cmd, String field, String attribute, String quant, Vector<String> nnps) { try { String dbname = field+"_"+attribute+"_"+quant; boolean up = true; if (quant.endsWith ("-N")) up = false; int f = findColumn (attribute); if (f == -1) { Debug ("Could not find atrribute "+attribute); return "error"; } // String stop = "."; String stop = " "; // select the relevant data // String orig [] = selectOrdering (f, plow, phigh); String nnp = nnps.elementAt (0); String orig [] = selectComparative (f, up, nnp); if (orig == null) { String problem = selectPhrase (intro) + selectPhrase (noanswers) + stop; return problem; } // form selection from the rest. avoiding everything in nnp int total = orig.length; Vector <String> hold = new Vector <String> (); outer: for (int i=0; i<total; i++) { for (int j=0; j<nnps.size (); j++) { nnp = nnps.elementAt (j); if (orig [i].equals (nnp)) continue outer; } hold.add (orig [i]); } int count = hold.size (); String selection [] = new String [count]; for (int i=0; i<count; i++) { selection [i] = hold.elementAt (i); } String result = ""; if (cmd.equals ("ask")) { if (count == 0) { result = selectPhrase (intro) + selectPhrase (noanswers) + stop; } else { result = selectPhrase (intro) + selectPhrase (yesanswers) + stop; } return result; } /* if (count > 0) { for (int i=0; i<count; i++) { System.out.println ("selection:"+i+" "+selection [i]); } } */ if (count == 0) { result ="there is no other solution"; } else if (count == 1) { result = selectPhrase (oneitem) + selection [0] +stop; } else if (count == 2) { result = selectPhrase (twoitems) + selection [0] + " and "+ selection [1] + stop; } else { result = selectPhrase (manyitems) + selectPhrase (forinstance) + selection [0] +stop; } return result; } catch (Exception e) { e.printStackTrace (); return "error"; } } int findColumn (String field) { for (int i=0; i<fields.length; i++) { if (fields [i].equals (field)) return i; } return -1; } // we assume for these qualitative data tables that the first column is the // identification and the remaining columns are attributes // plow and phigh are between 0 and 99 String [] selectOrdering (int f, int plow, int phigh) { // Debug ("Select ordering from "+plow+" to "+phigh); if (f == 0 || f >=nf) { Debug ("selectOrdering based on a column > 0 and < nf"); return null; } // order the rows Point pp [] = new Point [nr]; for (int i=0; i<nr; i++) { int val = (int)(new Double (data [i][f]).doubleValue ()*100.0); pp [i] = new Point (i, val); } Utils.quicksortpointy (pp, 0, nr-1); // increasing order of data [i][f]; /* Debug ("After sorting rows"); for (int i=0; i<nr; i++) { int j = pp [i].x; System.out.print (""+i+":"+data [j][0]+" "); } System.out.println (); */ double xnr = (double)nr; double low = (double)plow; double high = (double)phigh; double per = xnr/100.0; // Debug ("nr="+nr+" xnr="+xnr+" per="+per+" low="+low+" high="+high); int start = (int)(per*low); int end = (int)(per*high); // Debug ("start at row "+start+" end at row "+end); String selection [] = new String [end - start+1]; if (end -start < 0) return null; for (int i=0; i<=end-start; i++) { Point p = pp [start+i]; int j = p.x; selection [i] = data [j][0]; } return selection; } String [] selectComparative (int f, boolean up, String nnp) { if (f == 0 || f >=nf) { Debug ("selectOrdering based on a column > 0 and < nf"); return null; } if (nnp.indexOf ("nnp:") != -1) { int pos = nnp.indexOf ("nnp:"); nnp = nnp.substring (4).trim (); } // order the rows Point pp [] = new Point [nr]; for (int i=0; i<nr; i++) { int val = (int)(new Double (data [i][f]).doubleValue ()*100.0); pp [i] = new Point (i, val); } Utils.quicksortpointy (pp, 0, nr-1); // increasing order of data [i][f]; // find the one with data [i][0] equaling nnp int mark = -1; for (int i=0; i<nr; i++) { Point p = pp [i]; int j = p.x; if (data [j][0].equals (nnp)) { mark = i; break; } } if (mark == -1) { Debug ("could not find anything to match "+nnp); return null; } String selection []; if (up) { selection = new String [nr - mark - 1]; for (int i=0; i<nr-mark-1; i++) { int j = mark + 1 + i; Point p = pp [j]; int k = p.x; selection [i] = data [k][0]; } } else { selection = new String [mark]; for (int i=0; i<mark; i++) { Point p = pp [i]; int k = p.x; selection [i] = data [k][0]; } } return selection; } String selectPhrase (String [] phrases) { int n = phrases.length; int selected = (int)(Math.random ()*(double)n); if (selected >= n) selected = n-1; return phrases [selected]; } String confusedAnswer (Semnet net) { String start = selectPhrase (confused); String follow = selectPhrase (followup); String topic = selectPhrase (topics); String suggest = net.picktopic (2); String result = start + follow + topic + suggest; return result; } // command handler String handleCommand (String arg) { /* if (arg.equals ("yes")) { Act.qstack.push ("yes"); String result = Act.handleConfirmation (); return result; } else if (arg.equals ("no")) { Act.qstack.push ("no"); String result = Act.handleConfirmation (); return result; } else */ if (arg.equals ("back") || arg.equals ("clear") || arg.equals ("reset")) { Act.clearhistory (); return "Memory cleared, please ask another question."; } else if (arg.equals ("end")) { Act.qstack.push ("terminate"); return "terminate"; } else { String result = "Cannot handle the command "+arg+" Please ask something else."; Act.qstack.push (result); return result; } } }