source: src/main/java/weka/core/AttributeExpression.java @ 18

Last change on this file since 18 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 15.5 KB
Line 
1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 *    AttributeExpression.java
19 *    Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.core;
24
25import java.io.Serializable;
26import java.util.Stack;
27import java.util.StringTokenizer;
28import java.util.Vector;
29
30/**
31 * A general purpose class for parsing mathematical expressions
32 * involving attribute values. Values can be provided in an array
33 * or in an Instance. Values are accessed in the expression by
34 * prefixing their index (starting at 1) with the character 'a'.
35 *
36 * <pre> Example expression: a1^2*a5/log(a7*4.0) </pre>
37 *
38 * Supported opperators: +, -, *, /, ^, log, abs, cos, exp, sqrt,
39 * floor, ceil, rint, tan, sin, (, ).
40 *
41 * @author Mark Hall
42 * @version $Revision: 5988 $
43 */
44public class AttributeExpression
45  implements Serializable, RevisionHandler {
46
47  /** for serialization */
48  static final long serialVersionUID = 402130123261736245L;
49 
50  /**
51   * Interface implemented by operators and operants.
52   */
53  private interface ExpressionComponent {};
54
55  /**
56   * Inner class handling an attribute index as an operand
57   */
58  private class AttributeOperand 
59    implements ExpressionComponent, Serializable, RevisionHandler {
60   
61    /** for serialization */
62    static final long serialVersionUID = -7674280127286031105L;
63
64    /** the index of the attribute */
65    protected int m_attributeIndex;
66
67    /** true if the value of the attribute are to be multiplied by -1 */
68    protected boolean m_negative;
69
70    /**
71     * Constructor
72     *
73     * @param operand
74     * @param sign
75     * @throws Exception
76     */
77    public AttributeOperand(String operand, boolean sign) throws Exception {
78      // strip the leading 'a' and set the index
79      m_attributeIndex = (Integer.parseInt(operand.substring(1)))-1;
80      m_negative = sign;
81    }
82
83    /**
84     * Return a string describing this object
85     * @return a string descibing the attribute operand
86     */
87    public String toString() {
88      String result = "";
89      if (m_negative) {
90        result += '-';
91      }
92      return result+"a"+(m_attributeIndex+1);
93    }
94   
95    /**
96     * Returns the revision string.
97     *
98     * @return          the revision
99     */
100    public String getRevision() {
101      return RevisionUtils.extract("$Revision: 5988 $");
102    }
103  }
104
105  /**
106   * Inner class for storing numeric constant opperands
107   */
108  private class NumericOperand 
109    implements ExpressionComponent, Serializable, RevisionHandler {
110   
111    /** for serialization */
112    static final long serialVersionUID = 9037007836243662859L;
113
114    /** numeric constant */
115    protected double m_numericConst;
116
117    /**
118     * Constructor
119     *
120     * @param operand
121     * @param sign
122     * @throws Exception
123     */
124    public NumericOperand(String operand, boolean sign) throws Exception {
125      m_numericConst = Double.valueOf(operand).doubleValue();
126      if (sign) {
127        m_numericConst *= -1.0;
128      }
129    }
130   
131    /**
132     * Return a string describing this object
133     * @return a string descibing the numeric operand
134     */
135    public String toString() {
136      return ""+m_numericConst;
137    }
138   
139    /**
140     * Returns the revision string.
141     *
142     * @return          the revision
143     */
144    public String getRevision() {
145      return RevisionUtils.extract("$Revision: 5988 $");
146    }
147  }
148
149  /**
150   * Inner class for storing operators
151   */
152  private class Operator 
153    implements ExpressionComponent, Serializable, RevisionHandler {
154   
155    /** for serialization */
156    static final long serialVersionUID = -2760353522666004638L;
157
158    /** the operator */
159    protected char m_operator;
160
161    /**
162     * Constructor
163     *
164     * @param opp the operator
165     */
166    public Operator(char opp) {
167      if (!isOperator(opp)) {
168        throw new IllegalArgumentException("Unrecognized operator:" + opp);
169      }
170      m_operator = opp;
171    }
172
173    /**
174     * Apply this operator to the supplied arguments
175     * @param first the first argument
176     * @param second the second argument
177     * @return the result
178     */
179    protected double applyOperator(double first, double second) {
180      switch (m_operator) {
181        case '+' :
182          return (first+second);
183        case '-' :
184          return (first-second);
185        case '*' :
186          return (first*second);
187        case '/' :
188          return (first/second);
189        case '^' :
190          return Math.pow(first,second);
191      }
192      return Double.NaN;
193    }
194
195    /**
196     * Apply this operator (function) to the supplied argument
197     * @param value the argument
198     * @return the result
199     */
200    protected double applyFunction(double value) {
201      switch (m_operator) {
202        case 'l' :
203          return Math.log(value);
204        case 'b' :
205          return Math.abs(value);
206        case 'c' :
207          return Math.cos(value);
208        case 'e' :
209          return Math.exp(value);
210        case 's' :
211          return Math.sqrt(value);
212        case 'f' :
213          return Math.floor(value);
214        case 'h' :
215          return Math.ceil(value);
216        case 'r' :
217          return Math.rint(value);
218        case 't' :
219          return Math.tan(value);
220        case 'n' :
221          return Math.sin(value);
222      }
223      return Double.NaN;
224    }
225
226    /**
227     * Return a string describing this object
228     * @return a string descibing the operator
229     */
230    public String toString() {
231      return ""+m_operator;
232    }
233   
234    /**
235     * Returns the revision string.
236     *
237     * @return          the revision
238     */
239    public String getRevision() {
240      return RevisionUtils.extract("$Revision: 5988 $");
241    }
242  }
243
244  /** Operator stack */
245  private Stack<String> m_operatorStack = new Stack<String>();
246
247  /** Supported operators. l = log, b = abs, c = cos, e = exp, s = sqrt,
248      f = floor, h = ceil, r = rint, t = tan, n = sin */
249  private static final String OPERATORS = "+-*/()^lbcesfhrtn";
250  /** Unary functions. l = log, b = abs, c = cos, e = exp, s = sqrt,
251      f = floor, h = ceil, r = rint, t = tan, n = sin */
252  private static final String UNARY_FUNCTIONS = "lbcesfhrtn";
253
254  /** Holds the original infix expression */
255  private String m_originalInfix;
256 
257  /** Holds the expression in postfix form */
258  private Vector<ExpressionComponent> m_postFixExpVector;
259
260  /** True if the next numeric constant or attribute index is negative */
261  private boolean m_signMod = false;
262
263  /** Holds the previous token */
264  private String m_previousTok = "";
265
266    /**
267   * Handles the processing of an infix operand to postfix
268   * @param tok the infix operand
269   * @throws Exception if there is difficulty parsing the operand
270   */
271  private void handleOperand(String tok) throws Exception {
272    // if it contains an 'a' then its an attribute index
273    if (tok.indexOf('a') != -1) {
274      m_postFixExpVector.addElement(new AttributeOperand(tok,m_signMod));
275    } else {
276      try {
277        // should be a numeric constant
278        m_postFixExpVector.addElement(new NumericOperand(tok, m_signMod));
279      } catch (NumberFormatException ne) {
280        throw new Exception("Trouble parsing numeric constant");
281      }
282    }
283    m_signMod = false;
284  }
285
286  /**
287   * Handles the processing of an infix operator to postfix
288   * @param tok the infix operator
289   * @throws Exception if there is difficulty parsing the operator
290   */
291  private void handleOperator(String tok) throws Exception {
292    boolean push = true;
293
294    char tokchar = tok.charAt(0);
295    if (tokchar == ')') {
296      String popop = " ";
297      do {
298        popop = (String)(m_operatorStack.pop());
299        if (popop.charAt(0) != '(') {
300          m_postFixExpVector.addElement(new Operator(popop.charAt(0)));
301        }
302      } while (popop.charAt(0) != '(');
303    } else {
304      int infixToc = infixPriority(tok.charAt(0));
305      while (!m_operatorStack.empty() && 
306             stackPriority(((String)(m_operatorStack.peek())).charAt(0)) 
307             >= infixToc) {
308       
309        // try an catch double operators and see if the current one can
310        // be interpreted as the sign of an upcoming number
311        if (m_previousTok.length() == 1 && 
312            isOperator(m_previousTok.charAt(0)) &&
313            m_previousTok.charAt(0) != ')') {
314          if (tok.charAt(0) == '-') {
315            m_signMod = true;
316          } else {
317            m_signMod = false;
318          }
319          push = false;
320          break;
321        } else {
322          String popop = (String)(m_operatorStack.pop());
323          m_postFixExpVector.addElement(new Operator(popop.charAt(0)));
324        }
325      }
326      if (m_postFixExpVector.size() == 0) {
327        if (tok.charAt(0) == '-') {
328          m_signMod = true;
329          push = false;
330        }
331      }
332
333      if (push) {
334        m_operatorStack.push(tok);
335      }
336    }
337  }
338
339  /**
340   * Converts a string containing a mathematical expression in infix form
341   * to postfix form. The result is stored in the vector m_postfixExpVector
342   *
343   * @param infixExp the infix expression to convert
344   * @throws Exception if something goes wrong during the conversion
345   */
346  public void convertInfixToPostfix(String infixExp) throws Exception {
347    m_originalInfix = infixExp;
348
349    infixExp = Utils.removeSubstring(infixExp, " ");
350    infixExp = Utils.replaceSubstring(infixExp,"log","l");
351    infixExp = Utils.replaceSubstring(infixExp,"abs","b");
352    infixExp = Utils.replaceSubstring(infixExp,"cos","c");
353    infixExp = Utils.replaceSubstring(infixExp,"exp","e");
354    infixExp = Utils.replaceSubstring(infixExp,"sqrt","s");
355    infixExp = Utils.replaceSubstring(infixExp,"floor","f");
356    infixExp = Utils.replaceSubstring(infixExp,"ceil","h");
357    infixExp = Utils.replaceSubstring(infixExp,"rint","r");
358    infixExp = Utils.replaceSubstring(infixExp,"tan","t");
359    infixExp = Utils.replaceSubstring(infixExp,"sin","n");
360
361    StringTokenizer tokenizer = new StringTokenizer(infixExp, OPERATORS, true);
362    m_postFixExpVector = new Vector<ExpressionComponent>();
363
364    while (tokenizer.hasMoreTokens()) {
365      String tok = tokenizer.nextToken();
366     
367      if (tok.length() > 1) {
368        handleOperand(tok);
369      } else {
370        // probably an operator, but could be a single char operand
371        if (isOperator(tok.charAt(0))) {
372          handleOperator(tok);
373        } else {
374          // should be a numeric constant
375          handleOperand(tok);
376        }
377      }
378      m_previousTok = tok;
379    }
380    while (!m_operatorStack.empty()) {
381      String popop = (String)(m_operatorStack.pop());
382      if (popop.charAt(0) == '(' || popop.charAt(0) == ')') {
383        throw new Exception("Mis-matched parenthesis!");
384      }
385      m_postFixExpVector.addElement(new Operator(popop.charAt(0)));
386    }
387  }
388
389  /**
390   * Evaluate the expression using the supplied Instance.
391   * Assumes that the infix expression has been converted to
392   * postfix and stored in m_postFixExpVector
393   *
394   * @param instance the Instance containing values to apply
395   * the expression to
396   * @throws Exception if something goes wrong
397   */
398  public double evaluateExpression(Instance instance)
399    throws Exception {
400    double [] vals = new double [instance.numAttributes()+1];
401    for(int i = 0; i < instance.numAttributes(); i++) {
402      if (instance.isMissing(i)) {
403        vals[i] = Utils.missingValue();
404      } else {
405        vals[i] = instance.value(i);
406      }
407    }
408   
409    evaluateExpression(vals);
410    return vals[vals.length - 1];
411  }
412
413  /**
414   * Evaluate the expression using the supplied array of attribute values.
415   * The result is stored in the last element of the array. Assumes that
416   * the infix expression has been converted to postfix and stored in
417   * m_postFixExpVector
418   * @param vals the values to apply the expression to
419   * @throws Exception if something goes wrong
420   */
421  public void evaluateExpression(double [] vals) throws Exception {
422    Stack<Double> operands = new Stack<Double>();
423
424    for (int i=0;i<m_postFixExpVector.size();i++) {
425      Object nextob = m_postFixExpVector.elementAt(i);
426      if (nextob instanceof NumericOperand) {
427        operands.push(new Double(((NumericOperand)nextob).m_numericConst));
428      } else if (nextob instanceof AttributeOperand) {
429        double value = vals[((AttributeOperand)nextob).m_attributeIndex];
430        /*if (Utils.isMissingValue(value)) {
431          vals[vals.length-1] = Utils.missingValue();
432          break;
433        }*/
434        if (((AttributeOperand)nextob).m_negative) {
435          value = -value;
436        }
437        operands.push(new Double(value));
438      } else if (nextob instanceof Operator) {
439        char op = ((Operator)nextob).m_operator;
440        if (isUnaryFunction(op)) {
441          double operand = ((Double)operands.pop()).doubleValue();
442          double result = ((Operator)nextob).applyFunction(operand);
443          operands.push(new Double(result));
444        } else {
445          double second = ((Double)operands.pop()).doubleValue();
446          double first = ((Double)operands.pop()).doubleValue();
447          double result = ((Operator)nextob).applyOperator(first,second);
448          operands.push(new Double(result));
449        }
450      } else {
451        throw new Exception("Unknown object in postfix vector!");
452      }
453    }
454
455    if (operands.size() != 1) {
456      throw new Exception("Problem applying function");
457    }
458
459    Double result = ((Double)operands.pop());
460    if (result.isNaN() || result.isInfinite()) {
461      vals[vals.length-1] = Utils.missingValue();
462    } else {
463      vals[vals.length-1] = result.doubleValue();
464    }
465  }
466
467  /**
468   * Returns true if a token is an operator
469   * @param tok the token to check
470   * @return true if the supplied token is an operator
471   */
472  private boolean isOperator(char tok) {
473    if (OPERATORS.indexOf(tok) == -1) {
474      return false;
475    }
476
477    return true;
478  }
479
480  /**
481   * Returns true if a token is a unary function
482   * @param tok the token to check
483   * @return true if the supplied token is a unary function
484   */
485  private boolean isUnaryFunction(char tok) {
486    if (UNARY_FUNCTIONS.indexOf(tok) == -1) {
487      return false;
488    }
489
490    return true;
491  }
492
493  /**
494   * Return the infix priority of an operator
495   * @param opp the operator
496   * @return the infix priority
497   */
498  private int infixPriority(char opp) {
499    switch (opp) {
500      case 'l' : 
501      case 'b' :
502      case 'c' :
503      case 'e' :
504      case 's' :
505      case 'f' :
506      case 'h' :
507      case 'r' :
508      case 't' :
509      case 'n' :
510        return 3;
511      case '^' :
512        return 2;
513      case '*' : 
514        return 2;
515      case '/' : 
516        return 2;
517      case '+' :
518        return 1;
519      case '-' :
520        return 1;
521      case '(' :
522        return 4;
523      case ')' :
524        return 0;
525      default :
526        throw new IllegalArgumentException("Unrecognized operator:" + opp);
527    }
528  }
529
530  /**
531   * Return the stack priority of an operator
532   * @param opp the operator
533   * @return the stack priority
534   */
535  private int stackPriority(char opp) {
536     switch (opp) {
537       case 'l' :
538       case 'b' :
539       case 'c' :
540       case 'e' :
541       case 's' :
542       case 'f' :
543       case 'h' :
544       case 'r' :
545       case 't' :
546       case 'n' :
547         return 3;
548       case '^' :
549         return 2;
550       case '*' : 
551         return 2;
552       case '/' : 
553         return 2;
554       case '+' :
555         return 1;
556       case '-' :
557         return 1;
558       case '(' :
559         return 0;
560       case ')' :
561         return -1;
562       default :
563         throw new IllegalArgumentException("Unrecognized operator:" + opp);
564    }
565  }
566
567  /**
568   * Return the postfix expression
569   *
570   * @return the postfix expression as a String
571   */
572  public String getPostFixExpression() {
573    return m_postFixExpVector.toString();
574  }
575
576  public String toString() {
577    return m_originalInfix;
578  }
579 
580  /**
581   * Returns the revision string.
582   *
583   * @return            the revision
584   */
585  public String getRevision() {
586    return RevisionUtils.extract("$Revision: 5988 $");
587  }
588}
Note: See TracBrowser for help on using the repository browser.