Rectangle 27 1

math Evaluating a string of simple mathematical expressions?


import re
from operator import *

def compute(s):
    operators = dict(zip("+-/*()", (add, sub, truediv, mul)))
    stack = [add, 0]
    for val, op in re.findall("(-?[.\d]+)|([+-/*()])", s):
        if val:
            stack = [float(val)] + stack
        elif op == "(":
            stack = [add, 0] + stack
        elif op != ")":
            stack = [operators[op]] + stack
        if val or op == ")":
            stack[:3] = [stack[1](stack[2], stack[0])]
    return stack[0]

@gooli: Are you using Windows? By my count, the posted solution is only 273. One explanation for this may be that you counted newlines as two characters each. (Python doesn't care if you have single-char newlines, even in Windows.) Another explanation is that hit 8 when you meant 7. ;)

I wanted to see if I cab beat the other Python solutions using regular expressions.

The regular expression I'm using creates a list of pairs (val, op) where only one item in each pair is valid. The rest of the code is a rather standard stack based parser with a neat trick of replacing the top 3 cells in the stack with the result of the computation using Python list assignment syntax. Making this work with negative numbers required only two additional characters (-? in the regex).

You can save a couple of bytes by removing "()" from your operator string; zip stops at the end of the shorter list.

Note
Rectangle 27 1

math Evaluating a string of simple mathematical expressions?


def f(p)
  accumulators, operands = [0], ['+']
  p.gsub(/-/,'+-').scan(/(?:(-?\d+(?:\.\d+)?)|(.))\s*/).each do |n|
    number, operand = n
    case operand
      when '('
        accumulators << 0
        operands << '+'
      when ')'
        number = accumulators.pop
        operands.pop
      else 
        operands[-1] = operand
    end if number.nil?
    accumulators[-1] = accumulators.last.method(operands[-1]).call(number.to_f) unless number.nil?
  end
  accumulators.first
end
def f(p);a,o=[0],['+']
p.sub(/-/,'+-').scan(/(?:(-?\d+(?:\.\d+)?)|(.))\s*/).each{|n|
q,w=n;case w;when'(';a<<0;o<<'+';when')';q=a.pop;else;o<<w
end if q.nil?;a[-1]=a[-1].method(o.pop).call(q.to_f) if !q.nil?};a[0];end
def f(p);a,o=[0],[:+]
p.scan(/(?:(-?\d+(?:\.\d+)?)|(.))\s*/).each{|n|q,w=n;case w
when'(';a<<0;o<<:+;when')';q=a.pop;else;o<<w;end if !q
a<<a.pop.send(o.pop,q.to_f)if q};a[0];end

(3+7) - (5+2) -- that's what I've meant by several parentheses groups. Your solution has problem with non-nested parentheses, because of regex greediness.

Actually, mine is shorter (198) and uses regex to solve parenthesis from the top down before the final result of the mathematics. So "3 + (3 * (3 + 9))" goes: "3 + (3 * 12)", "3 + 36", 39. It goes top-to-bottom, left-to-right. It solves all tests, except one minor oversight which requires spaces between tokens. See: stackoverflow.com/questions/928563/

I don't think the use of 'method' or 'send' is cheating - it's just a table lookup - you are not using the built-in parser.

Not that yours isn't clever, it very much is.

That may be, but I had been fiddling around with my parser yesterday night and improved it on my system (with mathematical functions and single letter variables). So I pulled my better regex from it and it works just fine, the post is updated with a new char count too. ;-) I'll roll in your equation into the tests in the answer momentarily.

This is the shortest ruby solution up to now (one heavily based on RegExp yields incorrect answers when string contains few groups of parenthesis) -- no longer true. Solutions based on regex and substitution are shorter. This one is based on stack of accumulators and parses whole expression from left to right. It is re-entrant, and does not modify input string. It could be accused of breaking the rules of not using eval, as it calls Float's methods with identical names as their mathematical mnemonics (+,-,/,*).

Note
Rectangle 27 1

math Evaluating a string of simple mathematical expressions?


# Decimal number, as a capturing group, for substitution
# in the main regexp below.
N='( *-?[\d.]+ *)'

# The evaluation function
def e(x)
  matched = x.sub!(/\(#{N}\)|#{N}([^\d.])#{N}/) do
    # Group 1 is a numeric literal in parentheses.  If this is present then
    # just return it.
    if $1
      $1
    # Otherwise, $3 is an operator symbol and $2 and $4 are the operands
    else
      # Recursively call e to parse the operands (we already know from the
      # regexp that they are numeric literals, and this is slightly shorter
      # than using :to_f)
      e($2).send($3, e($4))
      # We could have converted $3 to a symbol ($3.to_s) or converted the
      # result back to string form, but both are done automatically anyway
    end
  end
  if matched then
    # We did one reduction. Now recurse back and look for more.
    e(x)
  else
    # If the string doesn't look like a non-trivial expression, assume it is a
    # string representation of a real number and attempt to parse it
    x.to_f
  end
end
N='( *-?[\d.]+ *)'
def e x
x.sub!(/\(#{N}\)|#{N}([^.\d])#{N}/){$1or(e$2).send$3,e($4)}?e(x):x.to_f
end

@ephemient that's a bug you found - it could not handle negative numbers.

Edit: Borrowing Daniel Martin's idea of merging the regexps saves another 11 characters!

Edit: Converting the 'while' to a conditional + tail recursion has saved a few characters, so it is no longer non-recursive (though the recursion is not semantically necessary.)

Edit: That recursion is even more useful than I first thought! x.to_f can be rewritten as e(x), if x happens to contain a single number.

Edit: Using 'or' instead of '||' allows a pair of parentheses to be dropped.

Getting rid of 'e=readline.chomp;...;p e.to_f' and using 'def q(e);...;e.to_f;end' like the other solutions would save 10 characters. However, it fails to q("1 + 3 / -8")==-0.5 as in the question.

I almost thought this was the new leader until I saw the Perl one had been edited to become even shorter! Good job, anyway.

The gsub!('--','') in my code is for how the parenthetical argument works, if negated. If the result of the interior of a negated parenthetical is negative the minus outside the statement remains: --7.0, for example. However, supporting that costs me 24 characters, still 19 above you. I don't know that I can shrink it any more than the tricks I learned from you. (But I did great for a 2nd try!)

This is a non-recursive version of The Wicked Flea's solution. Parenthesized sub-expressions are evaluated bottom-up instead of top-down.

Using "send" is really coming close to violating the "no eval" rule. But nice trick incorporating the spaces into your number regex. Using that trick and another one got my perl solution down to 119 characters.

Note
Rectangle 27 1

math Evaluating a string of simple mathematical expressions?


-- Strip spaces from the input, evaluate with empty accumulator,
-- and output the second field of the result.
q :: String -> Double
q = snd . flip eval id . filter (not . isSpace)

-- eval takes a string and an accumulator, and returns
-- the final value and whats left unused from the string.
eval :: (Fractional a, Read a) => String -> (a -> a) -> (String, a)

-- If the beginning of the string parses as a number, add it to the accumulator,
-- then try to read an operator and further.
eval str accum | [(num, rest)] <- readsPrec 0 str = oper rest (accum num)

-- If the string starts parentheses, evaluate the inside with a fresh
-- accumulator, and continue after the closing paren.
eval ('(':str) accum = oper rest (accum num) where (')':rest, num) = eval str id

-- oper takes a string and current value, and tries to read an operator
-- to apply to the value.  If there is none, its okay.
oper :: (Fractional a, Read a) => String -> a -> (String, a)

-- Handle operations by giving eval a pre-seeded accumulator.
oper ('+':str) num = eval str (num +)
oper ('-':str) num = eval str (num -)
oper ('*':str) num = eval str (num *)
oper ('/':str) num = eval str (num /)

-- If theres no operation parsable, just return.
oper str num = (str, num)
import Data.Char;import Text.ParserCombinators.Parsec
q=either(error.show)id.runParser t id"".filter(' '/=);t=do
s<-getState;a<-fmap read(many1$oneOf".-"<|>digit)<|>between(char '('>>setState id)(char ')'>>setState s)t
option(s a)$choice(zipWith(\c o->char c>>return(o$s a))"+-*/"[(+),(-),(*),(/)])>>=setState>>t
q=snd.(`e`id).filter(' '/=)
e s c|[(f,h)]<-readsPrec 0 s=g h(c f);e('(':s)c=g h(c f)where(')':h,f)=e s id
g('+':h)=e h.(+);g('-':h)=e h.(-);g('*':h)=e h.(*);g('/':h)=e h.(/);g h=(,)h

@ephemient: I tried using both regex and the parser module in Python quickly. Was almost immediately below 300 chars, but saw no chance to get competitive. The issue is the import and the function calls eat up too much. That is true for most languages (exception Perl). BTW, you don't have to parse yourself to get substantially below 300 chars, as my solution shows.

And now, really getting into the golf spirit, 3 lines and 182 bytes:

I suspect that it may still be possible to get below 225, but this is as far as I can get at 3am.

It's interesting to me that the Parsec solution (parser combinators = generalized regex + more), even if minimization was attempted, doesn't come close to the hand-rolled parsing. I don't think F#'s syntax can be as concise as Haskell, but I'd welcome some competition too :)

That appears to be quite an elegant functional solution. (One that I certainly wouldn't have understand without the comments, so thanks for those.) Also, you're just marginally ahead of Dave's Python solution at the moment, so you seem to be leading! I'd be curious if an F# solution could match or even beat this.

Note
Rectangle 27 1

math Evaluating a string of simple mathematical expressions?


WITH
  Input(id, str) AS (    
    SELECT 1, '1 + 3 / -8'
    UNION ALL SELECT 2, '2*3*4*5+99'
    UNION ALL SELECT 3, '4 * (9 - 4) / (2 * 6 - 2) + 8'
    UNION ALL SELECT 4, '1 + ((123 * 3 - 69) / 100)'
    UNION ALL SELECT 5, '2.45/8.5*9.27+(5*0.0023)'
  )
, Separators(i, ch, str_src, priority) AS (
    SELECT 1, '-', 1, 1
    UNION ALL SELECT 2, '+', 1, 1
    UNION ALL SELECT 3, '*', 1, 1
    UNION ALL SELECT 4, '/', 1, 1
    UNION ALL SELECT 5, '(', 0, 0
    UNION ALL SELECT 6, ')', 0, 0
  )
, SeparatorsStrSrc(str, i) AS (
    SELECT CAST('[' AS varchar(max)), 0
    UNION ALL
    SELECT
        str + ch
      , SSS.i + 1
    FROM
        SeparatorsStrSrc SSS
          INNER JOIN Separators S ON SSS.i = S.i - 1
    WHERE
        str_src <> 0
  )
, SeparatorsStr(str) AS (
    SELECT str + ']' FROM SeparatorsStrSrc
    WHERE i = (SELECT COUNT(*) FROM Separators WHERE str_src <> 0)
  )
, ExprElementsSrc(id, i, tmp, ele, pre_ch, input_str) AS (
    SELECT
        id
      , 1
      , CAST(LEFT(str, 1) AS varchar(max))
      , CAST('' AS varchar(max))
      , CAST(' ' AS char(1))
      , SUBSTRING(str, 2, LEN(str))
    FROM
        Input
    UNION ALL
    SELECT
        id
      , CASE ele
        WHEN '' THEN i
                ELSE i + 1
        END
      , CAST(
          CASE
          WHEN LEFT(input_str, 1) = ' '
            THEN ''
          WHEN tmp = '-'
            THEN CASE
                 WHEN pre_ch LIKE (SELECT str FROM SeparatorsStr)
                   THEN tmp + LEFT(input_str, 1)
                   ELSE LEFT(input_str, 1)
                 END
          WHEN LEFT(input_str, 1) IN (SELECT ch FROM Separators)
               OR
               tmp IN (SELECT ch FROM Separators)
            THEN LEFT(input_str, 1)
            ELSE tmp + LEFT(input_str, 1)
          END
        AS varchar(max))
      , CAST(
          CASE
          WHEN LEFT(input_str, 1) = ' '
            THEN tmp
          WHEN LEFT(input_str, 1) = '-'
            THEN CASE
                 WHEN tmp IN (SELECT ch FROM Separators)
                   THEN tmp
                   ELSE ''
                 END
          WHEN LEFT(input_str, 1) IN (SELECT ch FROM Separators)
               OR
               tmp IN (SELECT ch FROM Separators)
            THEN CASE
                 WHEN tmp = '-' AND pre_ch LIKE (SELECT str FROM SeparatorsStr)
                   THEN ''
                   ELSE tmp
                 END
            ELSE ''
          END
        AS varchar(max))
      , CAST(LEFT(ele, 1) AS char(1))
      , SUBSTRING(input_str, 2, LEN(input_str))
    FROM
        ExprElementsSrc
    WHERE
        input_str <> ''
        OR
        tmp <> ''
  )
, ExprElements(id, i, ele) AS (
    SELECT
        id
      , i
      , ele
    FROM
        ExprElementsSrc
    WHERE
        ele <> ''
  )
, Scanner(id, i, val) AS (
    SELECT
        id
      , i
      , CAST(ele AS varchar(max))
    FROM
        ExprElements
    WHERE
        ele <> ''
    UNION ALL
    SELECT
        id
      , MAX(i) + 1
      , NULL
    FROM
        ExprElements
    GROUP BY
        id
  )
, Operator(op, priority) AS (
    SELECT
        ch
      , priority 
    FROM
        Separators
    WHERE
        priority <> 0
  )
, Calc(id, c, i, pop_count, s0, s1, s2, stack, status) AS (
    SELECT
        Scanner.id
      , 1
      , 1
      , 0
      , CAST(scanner.val AS varchar(max))
      , CAST(NULL AS varchar(max))
      , CAST(NULL AS varchar(max))
      , CAST('' AS varchar(max))
      , CAST('init' AS varchar(max))
    FROM
        Scanner
    WHERE
        Scanner.i = 1
    UNION ALL
    SELECT
        Calc.id
      , Calc.c + 1
      , Calc.i
      , 3
      , NULL
      , NULL
      , NULL
      , CASE Calc.s1
        WHEN '+' THEN CAST(CAST(Calc.s2 AS real) + CAST(Calc.s0 AS real) AS varchar(max))
        WHEN '-' THEN CAST(CAST(Calc.s2 AS real) - CAST(Calc.s0 AS real) AS varchar(max))
        WHEN '*' THEN CAST(CAST(Calc.s2 AS real) * CAST(Calc.s0 AS real) AS varchar(max))
        WHEN '/' THEN CAST(CAST(Calc.s2 AS real) / CAST(Calc.s0 AS real) AS varchar(max))
                 ELSE NULL
        END
          + ' '
          + stack
      , CAST('calc ' + Calc.s1 AS varchar(max))
    FROM
        Calc
          INNER JOIN Scanner NextVal ON Calc.id = NextVal.id
                                          AND Calc.i + 1 = NextVal.i
    WHERE
        Calc.pop_count = 0
          AND ISNUMERIC(Calc.s2) = 1
          AND Calc.s1 IN (SELECT op FROM Operator)
          AND ISNUMERIC(Calc.s0) = 1
          AND (SELECT priority FROM Operator WHERE op = Calc.s1)
            >= COALESCE((SELECT priority FROM Operator WHERE op = NextVal.val), 0)
    UNION ALL
    SELECT
        Calc.id
      , Calc.c + 1
      , Calc.i
      , 3
      , NULL
      , NULL
      , NULL
      , s1 + ' ' + stack
      , CAST('paren' AS varchar(max))
    FROM
        Calc
    WHERE
        pop_count = 0
          AND s2 = '('
          AND ISNUMERIC(s1) = 1
          AND s0 = ')'
    UNION ALL
    SELECT
        Calc.id
      , Calc.c + 1
      , Calc.i
      , Calc.pop_count - 1
      , s1
      , s2
      , CASE
        WHEN LEN(stack) > 0
          THEN SUBSTRING(stack, 1, CHARINDEX(' ', stack) - 1)
          ELSE NULL
        END
      , CASE
        WHEN LEN(stack) > 0
          THEN SUBSTRING(stack, CHARINDEX(' ', stack) + 1, LEN(stack))
          ELSE ''
        END
      , CAST('pop' AS varchar(max))
    FROM
        Calc
    WHERE
        Calc.pop_count > 0
    UNION ALL
    SELECT
        Calc.id
      , Calc.c + 1
      , Calc.i + 1
      , Calc.pop_count
      , CAST(NextVal.val AS varchar(max))
      , s0
      , s1
      , coalesce(s2, '') + ' ' + stack
      , cast('read' as varchar(max))
    FROM
        Calc
          INNER JOIN Scanner NextVal ON Calc.id = NextVal.id
                                          AND Calc.i + 1 = NextVal.i
    WHERE
        NextVal.val IS NOT NULL
          AND Calc.pop_count = 0
          AND (
            (Calc.s0 IS NULL or calc.s1 is null or calc.s2 is null)
            OR
            NOT(
              ISNUMERIC(Calc.s2) = 1
                AND Calc.s1 IN (SELECT op FROM Operator)
                AND ISNUMERIC(calc.s0) = 1
                AND (SELECT priority FROM Operator WHERE op = Calc.s1)
                  >= COALESCE((SELECT priority FROM Operator WHERE op = NextVal.val), 0)
            )
              AND NOT(s2 = '(' AND ISNUMERIC(s1) = 1 AND s0 = ')')
          )
  )
SELECT
    Calc.id
  , Input.str
  , Calc.s0 AS result
FROM
    Calc
      INNER JOIN Input ON Calc.id = Input.id
WHERE
    Calc.c = (SELECT MAX(c) FROM Calc calc2
              WHERE Calc.id = Calc2.id)
ORDER BY
    id
WITH Input(id,str)AS(SELECT 1,'1 + 3 / -8'UNION ALL SELECT 2,'2*3*4*5+99'UNION ALL SELECT 3,'4 * (9 - 4)/ (2 * 6 - 2)+ 8'UNION ALL SELECT 4,'1 + ((123 * 3 - 69)/ 100)'UNION ALL SELECT 5,'2.45/8.5*9.27+(5*0.0023)'),Separators(i,ch,str_src,priority)AS(SELECT 1,'-',1,1UNION ALL SELECT 2,'+',1,1UNION ALL SELECT 3,'*',1,1UNION ALL SELECT 4,'/',1,1UNION ALL SELECT 5,'(',0,0UNION ALL SELECT 6,')',0,0),SeparatorsStrSrc(str,i)AS(SELECT CAST('['AS varchar(max)),0UNION ALL SELECT str+ch,SSS.i+1FROM SeparatorsStrSrc SSS INNER JOIN Separators S ON SSS.i=S.i-1WHERE str_src<>0),SeparatorsStr(str)AS(SELECT str+']'FROM SeparatorsStrSrc WHERE i=(SELECT COUNT(*)FROM Separators WHERE str_src<>0)),ExprElementsSrc(id,i,tmp,ele,pre_ch,input_str)AS(SELECT id,1,CAST(LEFT(str,1)AS varchar(max)),CAST(''AS varchar(max)),CAST(' 'AS char(1)),SUBSTRING(str,2,LEN(str))FROM Input UNION ALL SELECT id,CASE ele WHEN''THEN i ELSE i+1 END,CAST(CASE WHEN LEFT(input_str,1)=' 'THEN''WHEN tmp='-'THEN CASE WHEN pre_ch LIKE(SELECT str FROM SeparatorsStr)THEN tmp+LEFT(input_str,1)ELSE LEFT(input_str,1)END WHEN LEFT(input_str,1)IN(SELECT ch FROM Separators)OR tmp IN(SELECT ch FROM Separators)THEN LEFT(input_str,1)ELSE tmp+LEFT(input_str,1)END AS varchar(max)),CAST(CASE WHEN LEFT(input_str,1)=' 'THEN tmp WHEN LEFT(input_str,1)='-'THEN CASE WHEN tmp IN(SELECT ch FROM Separators)THEN tmp ELSE''END WHEN LEFT(input_str,1)IN(SELECT ch FROM Separators)OR tmp IN(SELECT ch FROM Separators)THEN CASE WHEN tmp='-'AND pre_ch LIKE(SELECT str FROM SeparatorsStr)THEN''ELSE tmp END ELSE''END AS varchar(max)),CAST(LEFT(ele,1)AS char(1)),SUBSTRING(input_str,2,LEN(input_str))FROM ExprElementsSrc WHERE input_str<>''OR tmp<>''),ExprElements(id,i,ele)AS(SELECT id,i,ele FROM ExprElementsSrc WHERE ele<>''),Scanner(id,i,val)AS(SELECT id,i,CAST(ele AS varchar(max))FROM ExprElements WHERE ele<>''UNION ALL SELECT id,MAX(i)+1,NULL FROM ExprElements GROUP BY id),Operator(op,priority)AS(SELECT ch,priority FROM Separators WHERE priority<>0),Calc(id,c,i,pop_count,s0,s1,s2,stack,status)AS(SELECT Scanner.id,1,1,0,CAST(scanner.val AS varchar(max)),CAST(NULL AS varchar(max)),CAST(NULL AS varchar(max)),CAST(''AS varchar(max)),CAST('init'AS varchar(max))FROM Scanner WHERE Scanner.i=1UNION ALL SELECT Calc.id,Calc.c+1,Calc.i,3,NULL,NULL,NULL,CASE Calc.s1 WHEN'+'THEN CAST(CAST(Calc.s2 AS real)+CAST(Calc.s0 AS real)AS varchar(max))WHEN'-'THEN CAST(CAST(Calc.s2 AS real)-CAST(Calc.s0 AS real)AS varchar(max))WHEN'*'THEN CAST(CAST(Calc.s2 AS real)*CAST(Calc.s0 AS real)AS varchar(max))WHEN'/'THEN CAST(CAST(Calc.s2 AS real)/CAST(Calc.s0 AS real)AS varchar(max))ELSE NULL END+' '+stack,CAST('calc '+Calc.s1 AS varchar(max))FROM Calc INNER JOIN Scanner NextVal ON Calc.id=NextVal.id AND Calc.i+1=NextVal.i WHERE Calc.pop_count=0AND ISNUMERIC(Calc.s2)=1AND Calc.s1 IN(SELECT op FROM Operator)AND ISNUMERIC(Calc.s0)=1AND(SELECT priority FROM Operator WHERE op=Calc.s1)>=COALESCE((SELECT priority FROM Operator WHERE op=NextVal.val),0)UNION ALL SELECT Calc.id,Calc.c+1,Calc.i,3,NULL,NULL,NULL,s1+' '+stack,CAST('paren'AS varchar(max))FROM Calc WHERE pop_count=0AND s2='('AND ISNUMERIC(s1)=1AND s0=')'UNION ALL SELECT Calc.id,Calc.c+1,Calc.i,Calc.pop_count-1,s1,s2,CASE WHEN LEN(stack)>0THEN SUBSTRING(stack,1,CHARINDEX(' ',stack)-1)ELSE NULL END,CASE WHEN LEN(stack)>0THEN SUBSTRING(stack,CHARINDEX(' ',stack)+1,LEN(stack))ELSE''END,CAST('pop'AS varchar(max))FROM Calc WHERE Calc.pop_count>0UNION ALL SELECT Calc.id,Calc.c+1,Calc.i+1,Calc.pop_count,CAST(NextVal.val AS varchar(max)),s0,s1,coalesce(s2,'')+' '+stack,cast('read'as varchar(max))FROM Calc INNER JOIN Scanner NextVal ON Calc.id=NextVal.id AND Calc.i+1=NextVal.i WHERE NextVal.val IS NOT NULL AND Calc.pop_count=0AND((Calc.s0 IS NULL OR calc.s1 IS NULL OR calc.s2 IS NULL)OR NOT(ISNUMERIC(Calc.s2)=1AND Calc.s1 IN(SELECT op FROM Operator)AND ISNUMERIC(calc.s0)=1AND (SELECT priority FROM Operator WHERE op=Calc.s1)>=COALESCE((SELECT priority FROM Operator WHERE op=NextVal.val),0))AND NOT(s2='('AND ISNUMERIC(s1)=1AND s0=')')))SELECT Calc.id,Input.str,Calc.s0 AS result FROM Calc INNER JOIN Input ON Calc.id=Input.id WHERE Calc.c=(SELECT MAX(c)FROM Calc calc2 WHERE Calc.id=Calc2.id)ORDER BY id

@Noldorin, why isn't it in the spirit of code golf?

Gosh, I don't think I was ever expecting an SQL solution! This isn't completely in the spirit of code golf, but up-voted anyway for the audacity (and not even using a programming language). :)

It is not shortest. But I think that it is very flexible for SQL. It's easy to add new operators. It's easy to change priority of operators.

Note
Rectangle 27 0

math Evaluating a string of simple mathematical expressions?


''
perl -le 'sub e{($_)=@_;$n='\''( *-?[.\d]++ *)'\'';s:\($n\)|$n(.)$n:($1,0,$2*$4,$2+$4,0,$2-$4)[7&ord$3]//$2/$4:e?e($_):$_}' -e 'print e($_) for @ARGV' '1 + 3' '1 + ((123 * 3 - 69) / 100)' '4 * (9 - 4) / (2 * 6 - 2) + 8' '2*3*4*5+99' '2.45/8.5*9.27+(5*0.0023) ' '1 + 3 / -8'
send
sub e {
  my $_ = "($_[0])";
  s/\s//g;
  $n=q"(-?\d++(\.\d+)?+)"; # a regex for "number", including capturing groups
                           # q"foo" in perl means the same as 'foo'
                           # Note the use of ++ and ?+ to tell perl
                           # "no backtracking"

  @a=(sub{$1},             # 0 - no operator found
      1,                   # placeholder
      sub{$3*$6},          # 2 - ord('*') = 052
      sub{$3+$6},          # 3 - ord('+') = 053
      4,                   # placeholder
      sub{$3-$6},          # 5 - ord('-') = 055
      6,                   # placeholder
      sub{$3/$6});         # 7 - ord('/') = 057

  # The (?<=... bit means "find a NUM WHATEVER NUM sequence that happens
  # immediately after a left paren", without including the left
  # paren.  The while loop repeatedly replaces "(" NUM WHATEVER NUM with
  # "(" RESULT and "(" NUM ")" with NUM.  The while loop keeps going
  # so long as those replacements can be made.

  while(s:\($n\)|(?<=\()$n(.)$n:$a[7&ord$5]():e){}

  # A perl function returns the value of the last statement
  $_
}
sub e{$_=$_[0];s/\s//g;$n=q"(-?\d++(\.\d+)?+)";
1while s:\($n\)|$n(.)$n:($1,1,$3*$6,$3+$6,4,$3-$6,6,$6&&$3/$6)[7&ord$5]:e;$_}
sub e{($_)=@_;$n='( *-?[.\d]++ *)';
s:\($n\)|$n(.)$n:($1,0,$2*$4,$2+$4,0,$2-$4)[7&ord$3]//$2/$4:e?e($_):$_}
sub e{my$_="($_[0])";s/\s//g;$n=q"(-?\d++(\.\d+)?+)";
@a=(sub{$1},1,sub{$3*$6},sub{$3+$6},4,sub{$3-$6},6,sub{$3/$6});
while(s:\($n\)|(?<=\()$n(.)$n:$a[7&ord$5]():e){}$_}

Fully obfuscated function: (167 characters if you join these three lines into one)

I didn't notice that nobody has mentioned it yet, but this solution requires Perl 5.10. For compatibility with 5.8, use (-?(?>\d+(\.\d+)?)) which is two characters longer.

I had misread the rules initially, so I'd submitted a version with "eval". Here's a version without it.

It's pretty scary how small Perl can go, I edited my answer to keep it the smallest Ruby implementation and ran out of room at 170 chars. But 124? Good gravy!

Taking edits by ephemient into account, it's now down to 124 characters: (join the two lines into one)

The latest bit of insight came when I realized that the last octal digit in the character codes for +, -, /, and * is different, and that ord(undef) is 0. This lets me set up the dispatch table @a as an array, and just invoke the code at the location 7 & ord($3).

The ruby solution down below is pushing me further, though I can't reach its 104 characters:

There's an obvious spot to shave off one more character - change q"" into '' - but that would make it harder to cut-and-paste into the shell.

Note
Rectangle 27 0

math Evaluating a string of simple mathematical expressions?


float E(string i){i=i.Replace(" ","");Regex b=new Regex(@"\((?>[^()]+|\((?<D>)|\)(?<-D>))*(?(D)(?!))\)");i=b.Replace(i,m=>Eval(m.Value.Substring(1,m.Length-2)).ToString());float r=0;foreach(Match m in Regex.Matches(i,@"(?<=^|\D)-?[\d.]+")){float f=float.Parse(m.Value);if(m.Index==0)r=f;else{char o=i[m.Index-1];if(o=='+')r+=f;if(o=='-')r-=f;if(o=='*')r*=f;if(o=='/')r/=f;}}return r;}
private static float Eval(string input)
{
    input = input.Replace(" ", "");
    Regex balancedMatcher = new Regex(@"\(
                                            (?>
                                                [^()]+
                                            |
                                                \( (?<Depth>)
                                            |
                                                \) (?<-Depth>)
                                            )*
                                            (?(Depth)(?!))
                                        \)", RegexOptions.IgnorePatternWhitespace);
    input = balancedMatcher.Replace(input, m => Eval(m.Value.Substring(1, m.Length - 2)).ToString());

    float result = 0;

    foreach (Match m in Regex.Matches(input, @"(?<=^|\D)-?[\d.]+"))
    {
        float floatVal = float.Parse(m.Value);
        if (m.Index == 0)
        {
            result = floatVal;
        }
        else
        {
            char op = input[m.Index - 1];
            if (op == '+') result += floatVal;
            if (op == '-') result -= floatVal;
            if (op == '*') result *= floatVal;
            if (op == '/') result /= floatVal;
        }
    }

    return result;
}
Note
Rectangle 27 0

math Evaluating a string of simple mathematical expressions?


def f(p)
  accumulators, operands = [0], ['+']
  p.gsub(/-/,'+-').scan(/(?:(-?\d+(?:\.\d+)?)|(.))\s*/).each do |n|
    number, operand = n
    case operand
      when '('
        accumulators << 0
        operands << '+'
      when ')'
        number = accumulators.pop
        operands.pop
      else 
        operands[-1] = operand
    end if number.nil?
    accumulators[-1] = accumulators.last.method(operands[-1]).call(number.to_f) unless number.nil?
  end
  accumulators.first
end
def f(p);a,o=[0],['+']
p.sub(/-/,'+-').scan(/(?:(-?\d+(?:\.\d+)?)|(.))\s*/).each{|n|
q,w=n;case w;when'(';a<<0;o<<'+';when')';q=a.pop;else;o<<w
end if q.nil?;a[-1]=a[-1].method(o.pop).call(q.to_f) if !q.nil?};a[0];end
def f(p);a,o=[0],[:+]
p.scan(/(?:(-?\d+(?:\.\d+)?)|(.))\s*/).each{|n|q,w=n;case w
when'(';a<<0;o<<:+;when')';q=a.pop;else;o<<w;end if !q
a<<a.pop.send(o.pop,q.to_f)if q};a[0];end

This is the shortest ruby solution up to now (one heavily based on RegExp yields incorrect answers when string contains few groups of parenthesis) -- no longer true. Solutions based on regex and substitution are shorter. This one is based on stack of accumulators and parses whole expression from left to right. It is re-entrant, and does not modify input string. It could be accused of breaking the rules of not using eval, as it calls Float's methods with identical names as their mathematical mnemonics (+,-,/,*).

Note
Rectangle 27 0

math Evaluating a string of simple mathematical expressions?


float E(string i){i=i.Replace(" ","");Regex b=new Regex(@"\((?>[^()]+|\((?<D>)|\)(?<-D>))*(?(D)(?!))\)");i=b.Replace(i,m=>Eval(m.Value.Substring(1,m.Length-2)).ToString());float r=0;foreach(Match m in Regex.Matches(i,@"(?<=^|\D)-?[\d.]+")){float f=float.Parse(m.Value);if(m.Index==0)r=f;else{char o=i[m.Index-1];if(o=='+')r+=f;if(o=='-')r-=f;if(o=='*')r*=f;if(o=='/')r/=f;}}return r;}
private static float Eval(string input)
{
    input = input.Replace(" ", "");
    Regex balancedMatcher = new Regex(@"\(
                                            (?>
                                                [^()]+
                                            |
                                                \( (?<Depth>)
                                            |
                                                \) (?<-Depth>)
                                            )*
                                            (?(Depth)(?!))
                                        \)", RegexOptions.IgnorePatternWhitespace);
    input = balancedMatcher.Replace(input, m => Eval(m.Value.Substring(1, m.Length - 2)).ToString());

    float result = 0;

    foreach (Match m in Regex.Matches(input, @"(?<=^|\D)-?[\d.]+"))
    {
        float floatVal = float.Parse(m.Value);
        if (m.Index == 0)
        {
            result = floatVal;
        }
        else
        {
            char op = input[m.Index - 1];
            if (op == '+') result += floatVal;
            if (op == '-') result -= floatVal;
            if (op == '*') result *= floatVal;
            if (op == '/') result /= floatVal;
        }
    }

    return result;
}

@Jeff: Well, technically it won't compile without the using statement, so by default it should be included. Petty point, however, given that our C# solutions are all significantly behind the leader.

Thanks for that solution. :) I wasn't sure whether I would see a C# solution with regex, but here we have it. Now, it's arguable whether you should include the "using System.Text.RegularExpressions;" in your char count, but it's a good solution nonetheless.

That wasn't part of the rules :). If you add "using R=System.Text.RegularExpressions.Regex;" and replace my "Regex" with R, it goes to 417.

Note
Rectangle 27 0

math Evaluating a string of simple mathematical expressions?


function callback1($m) {return c($m[1]);}
function callback2($n,$m) {
    $o=$m[0];
    $m[0]=' ';
    return $o=='+' ? $n+$m : ($o=='-' ? $n-$m : ($o=='*' ? $n*$m : $n/$m));
}
function c($s){ 
    while ($s != ($t = preg_replace_callback('/\(([^()]*)\)/','callback1',$s))) $s=$t;
    preg_match_all('![-+/*].*?[\d.]+!', "+$s", $m);
    return array_reduce($m[0], 'callback2');
}


$str = '  2.45/8.5  *  -9.27   +    (   5   *  0.0023  ) ';
var_dump(c($str));
# float(-2.66044117647)
function f($m){return c($m[1]);}function g($n,$m){$o=$m[0];$m[0]=' ';return$o=='+'?$n+$m:($o=='-'?$n-$m:($o=='*'?$n*$m:$n/$m));}function c($s){while($s!=($t=preg_replace_callback('/\(([^()]*)\)/',f,$s)))$s=$t;preg_match_all('![-+/*].*?[\d.]+!',"+$s",$m);return array_reduce($m[0],g);}

Should work with any valid input (including negative numbers and arbitrary whitespace)

preg_replace() with the e modifier would save you some more bytes.

Note