5

I've got a home automation system working in Java, and I want to add simple math capabilities such as addition, subtraction, multiplication, division, roots, and powers.

At the system current state, it can convert a phrase into tags, as shown in the following examples:

example:

Phrase: "what is one hundred twenty two to the power of seven"
Tagged: {QUESTION/math} {NUMBER/122} {MATH/pwr} {NUMBER/7}

example:

Phrase: "twenty seven plus pi 3 squared"
Tagged: {NUMBER/27} {MATH/add} {NUMBER/3.14159} {MATH/multiply} {MATH/pwr} {NUMBER/2}

This example could be just as easily converted to something like this:

27 + 3.14159 * 3^2

Each tag is an object that can be queried for it value.

Edit: Specific question:

So now I need a way to read that group of tags as an equation, and return a numerical result. As a last resort I could use google or wolfram alpha, but that will be slower, and I'm trying to keep the automation system completely self contained.

If you would like to see the entire source, here it is in github. Note that I have not committed the last few few changes, so some of the math related things I gave examples will not work.

Perception
  • 79,279
  • 19
  • 185
  • 195
Ben Cracknell
  • 271
  • 3
  • 11
  • 3
    What is the specific question here? – Oliver Charlesworth Apr 07 '13 at 23:37
  • There is no specific file, I just thought I would add a link in case anyone was interested. Edit: this might be useful: https://github.com/Sprakle/HomeAutomation/blob/master/HomeAutomation/src/net/sprakle/homeAutomation/interpretation/tagger/tags/Tag.java – Ben Cracknell Apr 07 '13 at 23:42
  • @OliCharlesworth I think he needs a way to turn the tags assigned to each phrase into an expression Java can understand to return a number. It'd be more helpful if we had the exact file(s) in question. – jocopa3 Apr 07 '13 at 23:42
  • I don't understand; if you've managed to write code that converts natural language to this tag stuff, surely it's trivial (in comparison) to convert the tags to a mathematical expression? What is the specific issue here? – Oliver Charlesworth Apr 07 '13 at 23:43
  • @ Oli Charlesworth You would think it would be trivial, but I have no idea how to get started. – Ben Cracknell Apr 07 '13 at 23:45
  • 1
    For a start, check out the [Shunting Yard Algorithm](http://en.wikipedia.org/wiki/Shunting-yard_algorithm). Wikipedia mostly discusses using it to convert to [RPN](http://en.wikipedia.org/wiki/Reverse_Polish_notation) or an [AST](http://en.wikipedia.org/wiki/Abstract_syntax_tree), but it is fairly trivial to extend it to actually compute a result. – Mac Apr 07 '13 at 23:49
  • You are digging your self in a deep deep hole. Anyhow have a dictionary of strings: one, two, tree... ten, twenty, eighty, hundred, thousand. Once you have the dictionary constructed you can split the given input read the array until you meed the first word that is contained in dictionary in your case "one" than read to first word that is not contained in the dictionary "to". Now you will need a method that composes an actual integer number from those strings. Once you know how to read numbers. – Sterling Duchess Apr 07 '13 at 23:51
  • You will need another dictionary where to keep your operations like, "pow", "multiply" etc. As you will continue to read the input'ed string. Then simply match the numbers you got with the operation and print out the result. – Sterling Duchess Apr 07 '13 at 23:52

4 Answers4

3

After some more googleing (I didn't know the name for what I was doing at first) I found someone who has done something similar already:

http://www.objecthunter.net/exp4j/

Edit: Finished: https://github.com/Sprakle/HomeAutomation/blob/master/src/net/sprakle/homeAutomation/interpretation/module/modules/math/Math.java

izilotti
  • 4,757
  • 1
  • 48
  • 55
Ben Cracknell
  • 271
  • 3
  • 11
1

Ben, he's right. The parsing action that takes natural language is much more difficult. What you need to do is add math precedence to the expression. The way you do that is to put the expression in some expected form, like post/pre/in-fix and provide an evaluation algorithm(post-fix ends up being pop(), pop(), evaluate(), push();. This requires that you check the individual tokens against a table that investigates the intersection of the operators and operands. It isn't anything you can do quickly or easily.

ChiefTwoPencils
  • 13,548
  • 8
  • 49
  • 75
  • Sounds like quite the task, but since this is meant to be a learning project (for me), I might as well try it. Edit: Nevermind, I found a library that does what I want. – Ben Cracknell Apr 07 '13 at 23:59
1

What you need is a parsing method that will build an equation and return an answer from your text. I'll take a single line and walk through making such a method which you will then need to work out for yourself. Note that this is just a general idea and that some languages other than Java may be more suitable for this kind of operation.

{QUESTION/math} {NUMBER/122} {MATH/pwr} {NUMBER/7}

//let's assume that the preprocessor reduces the input to an array that is something like this:
// [122, pwr, 7] (this is a very common construction in a language like LISP).

public static int math(string[] question){

   while(i < question.length){
      if(question[i] == "pwr"){
           return pow(Integer.parseInt(question[i-1]), Integer.parseInt(question[i+1]);
       }
       i++;
   }
  return 0;
}

Basically what you'll need is a nice way of going from infix to prefix notation with a little bit of string to whatever conversions.

There are likely nicer structures for doing this than what I've produced above, but something like this should get you going.

Peter Geiger
  • 93
  • 1
  • 4
0

The code I wrote relies that the given order of tags is "NUMBER" "MATH" "NUMBER" "MATH" "NUMBER" and it completely ignores operational rules in math. It is just an outline of what you could do, so you may have to fix it up a bit to do what you want.

I have not tested this file due to lack of time, so debug it if necessary!

import net.sprakle.homeAutomation.interpretation.tagger.tags.*;
import java.util.List;
import java.util.Arrays;

public class Math {

    private Tag[] tags; //Stores the converted tags as a "MathTag"

    //Requires an array of tags
    public Math(Tag[] inputTags) {
        tags = new MathTag[inputTags.length]; //create a new MathTag array
        System.arraycopy(inputTags, 0, tags, 0, tags.length); //Convert Tag array to MathTag array
    }

    //returns a value based on the tags
    //TODO: ADD MATHEMATICAL ORDER OF OPERATIONS!!!
    public double performMath() {
        double value = 0;//initial value to return

        for (int i = 0; i < tags.length; i++) { //perform initial check of the phrase given
            if (tags[i].getType() = TagType.NUMBER && !tags[i].getWasChecked() && i + 1 < tags.length) {
                value = performOperation(i, value, Double.parseDouble(tags[i].getValue()), tags[i+1].getType());
            } else if (tags[i].getType() = TagType.MATH && !tags[i].getWasChecked() && i + 1 < tags.length) {
                value = performOperation(i, value, Double.parseDouble(tags[i + 1].getValue()), tag.getType()); //I'm not positive if this would not cause issues
            }
        }
    }

    //Perform individual operations given the index of the operation, value1, value2, and the operation type
    //The order of the array "operations" must match the order of the operations in the switch statement.
    public double peformOperation(int i, double value1, double value2, String operation) {
        String[] operations = {"add", "subtract", "multiply", "divide", "pwr", "root"}; //Array of operations
        List list = Arrays.asList(operations); //Not exactly effecient, used to check what operation to perform

        switch (list.indexOf(operation)) { //Perform a task based on the operation
            case 0: //Addition
                tags[i].setChecked(true); //Number
                tags[i + 1].setChecked(true); //Operation
                return value1 + value2;
            case 1: //Subtraction
                tags[i].setChecked(true); //Number
                tags[i + 1].setChecked(true); //Operation
                return value1 - value2;
            case 2: //Multiplication
                tags[i].setChecked(true); //Number
                tags[i + 1].setChecked(true); //Operation
                return value1 * value2;
            case 3: //Division
                tags[i].setChecked(true); //Number
                tags[i + 1].setChecked(true); //Operation
                return value1 / value2;
            case 4: //Power
                tags[i].setChecked(true); //Number
                tags[i + 1].setChecked(true); //Operation
                return value1 * value1;
            case 5: //Square Root
                tags[i].setChecked(true); //Number
                tags[i + 1].setChecked(true); //Operation
                return Math.sqrt(value1);
        }

        return error(); //Non-valid operation found
    }

    //Need some way to detect an error
    public double error() {
        return 0;
    }

    //Need some way to check if a tag was looked at before
    class MathTag extends Tag {

        protected static boolean wasChecked = false;

        public void setChecked(boolean checked) {
            wasChecked = true;
        }

        public boolean getWasChecked() {
            return wasChecked;
        }
    }
}
jocopa3
  • 796
  • 1
  • 10
  • 29