package com.jaivox.interpreter;
import java.util.*;
import java.awt.Point;
import com.jaivox.util.Log;
/**
* 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.
*/
public class Script {
Interact Act;
Info Info;
String data [][];
String fields [];
int nr, nf;
Vector <Qapair> qa;
TreeMap <String, String> qspecs;
Answer adata;
String intro [];
String yesanswers [];
String noanswers [];
String confused [];
String followup [];
String topics [];
String oneitem [];
String twoitems [];
String manyitems [];
String forinstance [];
String askanother [];
String dontknow [];
TreeMap <String, Point> quants;
TreeMap <String, String []> qwords;
static int nSpecials = 2; // top values to show
/**
* Create a script
@param inter
@param inf
*/
public Script (Interact inter, Info inf, String answerdata) {
Act = inter;
Info = inf;
initializeQuants ();
adata = new Answer (answerdata);
intro = adata.intro;
yesanswers = adata.yesanswers;
noanswers = adata.noanswers;
confused = adata.confused;
followup = adata.followup;
topics = adata.topics;
oneitem = adata.oneitem;
twoitems = adata.twoitems;
manyitems = adata.manyitems;
forinstance = adata.forinstance;
askanother = adata.askanother;
dontknow = adata.dontknow;
}
/**
* Determine the range of values that correspond to various adjectives.
*/
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));
*/
// ranges extended since a superlative is a comparative and
// both superlative and comparative apply to the adjective
quants.put ("jjs-n", new Point (0, 10));
quants.put ("jjr-n", new Point (0, 30));
quants.put ("jj-n", new Point (0, 50));
quants.put ("jj-p", new Point (50, 99));
quants.put ("jjr-p", new Point (70, 99));
quants.put ("jjs-p", new Point (90, 99));
refreshData ();
}
void refreshData () {
data = Info.data;
fields = Info.fields;
nr = Info.nr;
nf = Info.nf;
}
void loadhistory () {
qspecs = Act.qspecs;
qa = Act.qa;
}
/**
* Create an answer to a question. Usually called from Inter.
@param question
@return
*/
String makeAnswer (String question) {
Log.fine ("makeAnswer: question = "+question);
loadhistory ();
String dummyAnswer = "dummy";
Qapair p = new Qapair (Act, -1, question, dummyAnswer);
String state = currentState (p);
Log.fine ("currentState = "+state);
String answer = null;
// situations with fully specified question
if (state.equals ("command")) {
answer = handleCommand (p);
}
else if (state.equals ("specified")) {
answer = generateSimple (p);
}
else if (state.equals ("nnpSpecified")) {
answer = generateSpecific (p);
}
else if (state.equals ("elseSpecified")) {
// answer = generateSimpleElse (p);
answer = makeWithHistory (state, p);
}
else if (state.equals ("elseNnp")) {
answer = generateSpecificElse (p);
}
// situations where specifications have to be inferred from history
else {
answer = makeWithHistory (state, p);
}
return answer;
}
/**
* Determine the type of question we have, using information from the
* history of the conversation so far.
@param state
@param p
@return
*/
String makeWithHistory (String state, Qapair p) {
Log.fine ("makeWithHistory: state="+state+", Qapair="+p.details ());
String answer = null;
// since the main fields are not specified we have to go back to the history
// find the last fully specified
int lastfull = getLastSpecified ();
// if no such thing, this is a query with wildcards that does not have any
// data available to fill the wildcards
if (lastfull == -1) {
Log.info ("Should have some some nouns specified after stage "+lastfull);
state = "notSpecified";
answer = selectPhrase (confused) + " " + selectPhrase (askanother);
// answer = "I can't figure out the answer, can you ask a new question?";
return answer;
}
Qapair qap = qa.elementAt (lastfull);
if (!p.updateFAQ (qap)) {
Log.warning ("Not able to update FAQ from query at position "+lastfull);
state = "internalError";
}
// resolve the nnp's mentioned since lastfull
Vector <String> nnps = getUsedNouns (lastfull);
if (nnps == null) {
// can't figure out
Log.info ("Can't figure out anything: "+p.question);
state = "internalError";
answer = selectPhrase (confused) + " " + selectPhrase (askanother);
// answer = "I am confused, could you ask the question a different way?";
return answer;
}
// since FAQ are updated, we can answer some questions
int nNouns = nnps.size ();
if (nNouns == 0) {
if (p.els.equals ("")) {
answer = generateSimple (p);
}
else {
String temp = generateSimple (p);
answer = selectPhrase (topics) + " " + temp;
// answer = "Do not know what else. However, " + temp;
}
}
else if (nNouns == 1) {
String nnp = nnps.elementAt (0);
p.nnp = nnp;
if (p.els.equals ("")) {
answer = generateSpecific (p);
}
else {
answer = generateSpecificElse (p);
}
}
else { // more than one noun mentioned
answer = generateElse (p, nnps);
}
return answer;
}
String currentState (Qapair p) {
if (p.command.equals ("command"))
return "command";
if (p.FAQspecified ()) {
if (p.els.equals ("")) {
if (p.nnp.equals ("")) {
return "specified";
}
else {
return "nnpSpecified";
}
}
else {
if (p.nnp.equals ("")) {
return "elseSpecified";
}
else {
return "elseNnp";
}
}
}
else {
return "useHistory";
}
}
int getLastSpecified () {
int n = qa.size ();
int lastfull = -1;
for (int i=n-1; i>=0; i--) {
Qapair qap = qa.elementAt (i);
Log.fine ("qa:"+i+" "+qap.toString ());
String olds = qap.spec;
// ignore commands
if (qap.command.equals ("command")) continue;
int loc = olds.indexOf ("_");
if (loc == -1) {
lastfull = i;
break;
}
}
return lastfull;
}
Vector <String> getUsedNouns (int lastfull) {
int n = qa.size ();
if (lastfull >= n) {
Log.warning ("No full specification available to determine unknown variables.");
return null;
}
Vector <String> nnps = new Vector <String> ();
for (int i=lastfull; i<n; i++) {
Qapair qap = qa.elementAt (i);
// get any nnp from the question specs
String nnp = qap.nnp;
if (!nnp.equals ("") && !nnp.equals ("_")) {
Log.fine ("At step "+n+" adding nnp from question: "+nnp);
nnps.add (nnp);
}
// get all nnp's in answers
String ans = qap.answer;
for (int j=0; j<nr; j++) {
if (ans.indexOf (data [j][0]) != -1) {
nnps.add (data [j][0]);
Log.fine ("At step "+n+" adding nnp from answer: "+data [j][0]);
}
}
}
return nnps;
}
String generateSimple (Qapair p) {
try {
Log.fine ("generateSimple: "+ p.field+" "+p.attribute+" "+p.quant);
Point pt = quants.get (p.quant);
int plow = pt.x;
int phigh = pt.y;
int f = findColumn (p.attribute);
if (f == -1) {
Log.warning ("Could not find atrribute "+p.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++) {
Log.finest ("selection:"+i+" "+selection [i]);
}
}
if (p.command.equals ("ask")) {
if (count == 0) {
result = selectPhrase (intro) + " " + selectPhrase (noanswers) + stop;
}
else {
result = selectPhrase (intro) + " " + selectPhrase (yesanswers) + stop;
}
return result;
}
if (count == 0) {
result = selectPhrase (intro) + " " + selectPhrase (noanswers) + stop;
}
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 (Qapair p) {
try {
Log.fine ("generateSpecific: "+ p.field+" "+p.attribute+" "+p.quant+" "+p.nnp);
boolean up = true;
if (p.quant.toLowerCase ().endsWith ("-n")) up = false;
int f = findColumn (p.attribute);
if (f == -1) {
Log.warning ("Could not find atrribute "+p.attribute);
return "error";
}
// String stop = ".";
String stop = " ";
String selection [] = selectComparative (f, up, p.nnp);
if (selection == null) {
String problem = selectPhrase (intro) + " " + selectPhrase (noanswers) + stop;
return problem;
}
int count = selection.length;
String result = "";
if (p.command.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++) {
Log.finest ("selection:"+i+" "+selection [i]);
}
}
if (count == 0) {
result = selectPhrase (intro) + " " + selectPhrase (noanswers) + stop;
}
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 (Qapair p) {
try {
Log.fine ("generateSpecificElse: "+ p.field+" "+p.attribute+" "+p.quant+" "+p.nnp+" "+p.els);
boolean up = true;
if (p.quant.toLowerCase ().endsWith ("-n")) up = false;
int f = findColumn (p.attribute);
if (f == -1) {
Log.warning ("Could not find atrribute "+p.attribute);
return "error";
}
// String stop = ".";
String stop = " ";
// select the relevant data
// String orig [] = selectOrdering (f, plow, phigh);
String orig [] = selectComparative (f, up, p.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 (p.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 (p.command.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++) {
Log.finest ("selection:"+i+" "+selection [i]);
}
}
if (count == 0) {
result = selectPhrase (intro) + " " + selectPhrase (noanswers) + stop;
}
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";
}
}
// need to avoid previous answers here
String generateElse (Qapair p, Vector <String> nnps) {
try {
Log.fine ("generateElse: "+p.field+" "+p.attribute+" "+p.quant);
boolean up = true;
if (p.quant.toLowerCase ().endsWith ("-n")) up = false;
int f = findColumn (p.attribute);
if (f == -1) {
Log.warning ("Could not find atrribute "+p.attribute);
return "error";
}
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 (p.command.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++) {
Log.finest ("selection:"+i+" "+selection [i]);
}
}
if (count == 0) {
result = selectPhrase (intro) + " " + selectPhrase (noanswers) + stop;
}
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) {
// Log.fine ("Select ordering from "+plow+" to "+phigh);
if (f == 0 || f >=nf) {
Log.warning ("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];
double xnr = (double)nr;
double low = (double)plow;
double high = (double)phigh;
double per = xnr/100.0;
// Log.fine ("nr="+nr+" xnr="+xnr+" per="+per+" low="+low+" high="+high);
int start = (int)(per*low);
int end = (int)(per*high);
Log.fine ("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) {
Log.warning ("selectOrdering based on a column > 0 and < nf");
return null;
}
if (nnp.indexOf ("nnp:") != -1) {
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) {
Log.warning ("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
// currently it does very little, but this is the place to use both
// the command and the args to do various actions
public String handleCommand (Qapair p) {
String command = p.command;
if (command.equals ("back") || command.equals ("clear") || command.equals ("reset")) {
Act.clearhistory ();
return selectPhrase (askanother);
}
else if (command.equals ("end")) {
Act.qstack.push ("terminate");
return "terminate";
}
else {
String result = Act.command.handleCommand (p);
Act.qstack.push (result);
return result;
}
}
}