Raymond Camden's Blog Rss

Friday Puzzler: Math in English

12

Posted in ColdFusion | Posted on 05-05-2006 | 2,523 views

For today's puzzler, I thought of a simple little thing that would make you work your string parsing part of the brain. I started out in Perl and absolutely love mucking around with strings, so I thought, why not share this love with everyone else? (Remind me to tell you about the contract work I did for Netscape back in 98 or so that was - basically - one big perl script to update their web site.)

This puzzle is rather simple. Build a form that will accept math questions in English. So for example:

one plus two

You only need to support numbers from zero to ten. You can report the answer as either a number or a word. You need to support "plus", "minus", "multiply", "divide". Bonus points if you catch a division by zero error. Bonus points if you ignore, and don't get tripped by, stuff at the end, like "is?"

As always, the prize is nothing, which is great since your tax burden on nothing will be roughly 33% of the value of nothing. You have 5 minutes to solve this, but if you spend more, I won't tell anyone. If you get fired doing this, I need someone to help dig up a mail post so I'd be happy to give you a new job.

Enjoy!

Comments

[Add Comment] [Subscribe to Comments]

It did take me more than 5 minutes. It took me 15. I wanted to do the extra credit.

<cffunction name="strEnglishToMath" returntype="numeric" output="true" hint="I return the math evlaution of an english expresss.">
   <cfargument name="strExp" type="string" required="true">

   <cfset numResult = 0>

   <cfscript>
      strExp = ReReplace(strExp,"zero","0","ALL");
      strExp = ReReplace(strExp,"one","1","ALL");
      strExp = ReReplace(strExp,"two","2","ALL");
      strExp = ReReplace(strExp,"three","3","ALL");
      strExp = ReReplace(strExp,"four","4","ALL");
      strExp = ReReplace(strExp,"five","5","ALL");
      strExp = ReReplace(strExp,"six","6","ALL");
      strExp = ReReplace(strExp,"seven","7","ALL");         
      strExp = ReReplace(strExp,"eight","8","ALL");   
      strExp = ReReplace(strExp,"nine","9","ALL");   
      strExp = ReReplace(strExp,"ten","10","ALL");

      strExp = ReReplace(strExp,"plus","+","ALL");
      strExp = ReReplace(strExp,"minus","-","ALL");
      strExp = ReReplace(strExp,"multiply","*","ALL");
      strExp = ReReplace(strExp,"divide","/","ALL");
      
      reScript = "[0-9]+[ ]+[\+\-\/\*]+[ ]+[0-9]+";
      strPos = REFind(reScript, strExp,1,"True");
       strExp = mid(strExp, strPos.pos[1], strPos.len[1]);

      leftNumber = Trim(listgetat(strExp,1,"+-/*"));
      rightNumber = Trim(listgetat(strExp,2,"+-/*"));
      
      reScript = "[\+\-\/\*]";
      strPos = REFind(reScript, strExp,1,"True");
       operator = mid(strExp, strPos.pos[1], strPos.len[1]);
   
      if(not (rightNumber eq 0 and operator eq "/"))
      {
         switch(operator)
         {
            case "+":
            {
               numResult = leftNumber + rightNumber;
               break;
            }         
            case "-":
            {
            numResult = leftNumber - rightNumber;
            break;
            }
            case "/":
            {
            numResult = leftNumber / rightNumber;
            break;
            }
            case "*":
            {
            numResult = leftNumber * rightNumber;
            break;                           
            }
         }
      }

   </cfscript>

   <cfreturn numResult>

</cffunction>

<cfset strExp = "five divide zero is?">

<cfoutput>
#strEnglishToMath(strExp)#
</cfoutput>
<cfscript>
function NaturalToNum(str) {
   switch (str) {
      case "one":
         returnVal = 1;
         break;
      case "two":
         returnVal = 2;
         break;
      case "three":
         returnVal = 3;
         break;
      case "four":
         returnVal = 4;
         break;
      case "five":
         returnVal = 5;
         break;
      case "six":
         returnVal = 6;
         break;
      case "seven":
         returnVal = 7;
         break;
      case "eight":
         returnVal = 8;
         break;
      case "nine":
         returnVal = 9;
         break;
      case "ten":
         returnVal = 10;
         break;
      default:
         returnVal = str;
         break;
   }
   return returnVal;
}

function NaturalToSign(str) {
   switch (str) {
      case "plus":
         returnVal = "+";
         break;
      case "times":
         returnVal = "*";
         break;
      case "minus":
         returnVal = "-";
         break;
      case "divided":
         returnVal = "/";
         break;
      default:
         returnVal = str;
         break;
   }
   return returnVal;
}

function EvalNaturalEquation(str) {
   strArr = ListToArray(str," ");
   for(i = 1; i lte ArrayLen(strArr); i = i + 1) {
      if(NaturalToNum(NaturalToSign(strArr[i])) eq strArr[i]) {
         ArrayDeleteAt(strArr,i);
      }
      else {
         strArr[i] = NaturalToNum(NaturalToSign(strArr[i]));
      }
   }
   strList = ArrayToList(strArr, " ");
   return Evaluate(strList);
}
</cfscript>
<cfif isDefined('form.equation') and len(trim(form.equation))>
   <cfset answer = EvalNaturalEquation(form.equation)>
</cfif>
Mine will allow more than two numbers and one sign as argument, type it out just like a regular equation.
Made the following corrections to the return of EvalNaturalEquation:
try {
      returnVal = Evaluate(strList);
   }
   catch(any e){
      returnVal = "Cannot divide by 0";
   }
   return returnVal;
Kyle,

Tried your demo: Entered "onex plus twoy", got the wrong error message: "Cannot divide by 0"...

Happy Friday!
hmmm i was thinking of making this check for more than one math statement...

Couldn't find any replacement for using Evaluate(), took too long trying to come up with one...

<!--- change this parameter to test --->

<cfparam name="form.mathstring" default="fivex times one" />


<cfscript>

//set invalid variables to 0 : used to check for division against zero invalid =1 means dividing by zero : invalid 3 means invalid characters
variables.invalid = 0;
variables.number1=0;
variables.number2=0;

//set 2D array to put in number calculation **** 1-1 = operator 1-2 = first number 1-3 = second number
variables.mathOperators = ArrayNew(2);

//set array for string operators
variables.MathOperatorsArray = ArrayNew(1);

//set array for symbol operators
variables.MathSymbolArray = ArrayNew(1);

//set array for string number = Array number
variables.NumberArray = Arraynew(1);


variables.mathOperatorsArray[1] = 'plus';
variables.mathOperatorsArray[2] = 'minus';
variables.mathOperatorsArray[3] = 'multiply';
variables.mathOperatorsArray[4]= 'divide';
variables.mathOperatorsArray[5]='times';

variables.mathSymbolArray[1] = '+';
variables.mathSymbolArray[2] = '-';
variables.mathSymbolArray[3] = '*';
variables.mathSymbolArray[4] = '/';
variables.mathSymbolArray[5] = '*';

variables.numberArray[1] = 'one';
variables.numberArray[2] = 'two';
variables.numberArray[3] = 'three';
variables.numberArray[4] = 'four';
variables.numberArray[5] = 'five';
variables.numberArray[6] = 'six';
variables.numberArray[7] = 'seven';
variables.numberArray[8] = 'eight';
variables.numberArray[9] = 'nine';
variables.numberArray[10] = 'ten';
variables.numberArray[11] = 'zero';

//check loop for operators
for(i=1;i lte ArrayLen(variables.MathOperatorsArray); i=i+1)
   {

      //check to see which operator to use
   if (form.mathstring contains variables.MathOperatorsArray[i])
   
         {
            // set math operator into array
            variables.mathOperators[i][1]=variables.mathSymbolArray[i];

            //get first number
            variables.firstnumber = Gettoken(form.mathstring,1,' ');
            
            //get second number
            variables.secondnumber = GetToken(form.mathstring,3,' ');
            
            

            //find numerical value of strings
            for (num1 = 1;num1 lte ArrayLen(variables.numberArray); num1 = num1 + 1)
               {
            
               if(comparenocase(variables.numberArray[num1],variables.firstnumber) eq 0) variables.number1 =num1;
               
               if(comparenocase(variables.numberArray[num1],variables.secondnumber) eq 0)variables.number2 =num1;
               }

            
            //set numbers into array
            variables.mathOperators[i][2] = variables.number1;
            variables.mathOperators[i][3] = variables.number2;
   
            //check for invalid characters
            if(variables.number1 eq 0 or variables.number2 eq 0)variables.invalid = 3;
            
            // check for zero divisability ********************
            if(variables.secondnumber eq 11) variables.invalid = 1;
            
         
            
         }
         
   }
   




//check to see if we should show division by zero
if(variables.invalid eq 0){

   //Do math and check for divisible by 0
   //loop through mathoperators array to find what to calculate
   
   for(math=1; math lte ArrayLen(variables.mathoperators); math = math +1)
      {
         
         if(not ArrayIsEmpty(variables.mathoperators[math]))
         
             variables.calcoutput = 'Your answer is: ' & Evaluate('#variables.mathoperators[math][2]# #variables.mathoperators[math][1]# #variables.mathoperators[math][3]#');
         }
         
}

// say something to tell them that their total was division by 0
else
{
if (variables.invalid eq 1)
variables.calcoutput = 'You know better than to Divide by zero punk!'; else variables.calcoutput = 'Invalid math string! Cannot process the form query.';
}
      
writeoutput(variables.calcoutput);
</cfscript>

<CFDUMP var="#variables.mathoperators#" />
I have not written the code yet (my work dev server does not have internet access). But should be able to do this with a call to google search api. As it contains a calculator and does full text calc also.

http://www.google.com/help/features.html#calculato...

I am a big fan of using existing code when possible.
Good one Daniel.
Here's my attempt. The time limit prevented me from commenting this :-)

I'm pretty sure it works...


<cfif isdefined("form.submit")>
   <cfscript>
   mathString = form.question;

   mathString = rereplacenocase(mathString,'(divided by)|(by)|(over)|(into)','/','all');
   mathString = rereplacenocase(mathString,'(plus)|(added to)|(and)|(to)','+','all');   
   mathString = rereplacenocase(mathString,'(minus)|(less)|(subtract)','-','all');
   mathString = rereplacenocase(mathString,'(from\s)','- -','all');   
   mathString = rereplacenocase(mathString,'(multiplied by)|(times)','*','all');
   mathString = rereplacenocase(trim(mathString),'^multiply\s([a-z0-9]*)\s\+','\1 *');
   mathString = rereplacenocase(trim(mathString),'^((add)|(subtract)|(divide)|(multiply))','');
   numberStrings = 'zero,one,two,three,four,five,six,seven,eight,nine,ten';
   numberDigits = '0,1,2,3,4,5,6,7,8,9,10';
   mathString = trim(replacelist(mathString,numberStrings,numberDigits));
   if (refind('/ 0$',mathString))
      answer = 'undefined';
   else
      answer = evaluate(mathString);   
   writeOutput(answer);
   </cfscript>

</cfif>

<cfform name="math" action="#CGI.SCRIPT_NAME#" method="post" preservedata="yes">
<cfinput name="question" type="text" size="30">
<input type="submit" name="submit" value="do the math" />
</cfform>
Now this is interesting, when you type the english equation into google it actually gives you an english answer. Observe:
http://www.google.com/search?q=five+plus+two&b...
And it automagically assumes parenthesis where needed:
http://www.google.com/search?hl=en&sa=X&oi...

[Add Comment] [Subscribe to Comments]