PHP实现的一个简单的混乱计算器

最近准备换工作,就多留意了一下招聘信息,正好有一个HR说他们要招高级PHP工程师,就跟她聊了一会儿。然后她就让他们的技术老大给我出了一道题来考我。题目是:实现一个混乱计算器。

 

那么,什么是混乱计算器?

普通的计算器是从左到右计算结果,*/的优先级大于+-,因此1+2-3/3=2。而混乱计算器刚好相反,从右往左计算结果,+-的优先级大于*/,括号具有最高优先级。因此1+2-3/3=1.5。(混乱计算器貌似没有任何实际意义!)

 

那么怎么实现这样的功能呢?仔细观察了一下,这个跟普通的计算器基本上没有区别除了优先级和运算的放向。因此,我就按照计算器的实现原理做了一个混乱计算器的版本:

 

假设函数为:

function calculate($expression){}

 

首先通过正则表达式把$expression中的数字与符号分离出来,放到一个数组中。我们可以通过下面两句得到这样的结果:

$pattern = '/\+|\-|\*|\/|\d*\.?\d|\(|\)/';      // 匹配表达式
preg_match_all($pattern, $expression, $out)

 

这个$out就是我们匹配出来的结果。通常的计算器到这一步就够了,但是因为混乱计算器的运算放向是相反的,因此我们需要把数组的顺序颠倒一下:

$out = array_reverse($out[0]);     // 先把数组反转

 

下面我们开始对$out进行解析。解析过程是这样的:

首先创建两个栈$opStack和$numStack分别用来存储操作符(+-*/和括号)和数字。php中可以通过array来表示栈。

循环依次取得$out的所有值$v,分以下情况处理:

1. 如果$v是数字,则放入数字栈$numStack。

2. 如果$v是符号,则做以下判断:

    1)$opStack为空,或者符号栈不为空但$v==“)”,则直接存入$opStack。

    2)$opStack不为空,且$v !=“(”,则判断当$v与$opStack中最顶部的符号$op的优先级。

    如果$v的优先级大于$op,则把$v放入$opStack

    如果$v的优先级小于或等于$op,则计算$numStack中两个数与$op之间的结果,并将结果存到$numStack中,然后将$v存入$opStack中

    3)$opStack不为空,且$v == "(",则应该计算括号之内的所有值,并且将计算结果存到$numStack中。

循环完成之后,就可以直接通过循环把$numStack中的值计算出来了。

 

需要注意的是,因为混乱计算器的放心跟普通计算器的放向刚好相反,因此做计算的时候需要用$val2-$va1(假设$op为减号):

$val1 = array_pop($numStack);
$val2 = array_pop($numStack);
array_push($numStack, calc($val2, $val1, $op));     // 把两个值与之前的操作符进行计算并放回数组。

 

这里的calc函数的实现如下:

function calc($val1, $val2, $op) {
    switch ($op) {
        case '+':
            return $val1+$val2;
        case '-':
            return $val1-$val2;
        case '*':
            return $val1*$val2;
        case '/':
            if ($val2 == 0) {
                throw new Exception('除0错误!');
            } else {
                return $val1/$val2;
            }
    }
}


$calculate函数的实现如下:

function calculate($expression) {
    $pattern = '/\+|\-|\*|\/|\d*\.?\d|\(|\)/';      // 匹配表达式
    $numStack = array();
    $opStack = array();

    if (preg_match_all($pattern, $expression, $out) !== false) {
        $out = array_reverse($out[0]);     // 先把数组反转
        foreach ($out as $v) {
            if (is_numeric($v)) {
                // 添加到数字堆栈
                array_push($numStack, $v);
            } else {
                $opNum = count($opStack);
                if (count($opStack) == 0) {
                    array_push($opStack, $v);   // 没有操作符,加入堆栈
                } else {
                    // 有操作符,则比较两个操作符的优先级
                    if ( in_array($v, array('+', '-')) && in_array($opStack[$opNum-1], array('*', '/')) ||
                            $v == ')' ||
                        $opStack[$opNum-1] == ')' && $v !== '('
                    ) {
                        array_push($opStack, $v);
                    } else if ($v !== '('){
                        $val1 = array_pop($numStack);
                        $val2 = array_pop($numStack);
                        array_push($numStack, calc($val2, $val1, array_pop($opStack)));     // 把两个值与之前的操作符进行计算并放回数组。
                        array_push($opStack, $v);        // 把新的操作符放入堆栈
                    } else {
                        // 遇到括号的结尾,则计算括号范围内的所有的表达式的值
                        while (count($opStack) > 0 && ($op = array_pop($opStack)) != ')') {
                            $val1 = array_pop($numStack);
                            $val2 = array_pop($numStack);
                            array_push($numStack, calc($val2, $val1, $op));     // 把两个值与之前的操作符进行计算并放回数组。
                        }
                    }
                }
            }
        }

        // 输出结果
        while (count($opStack) >0 ) {
            $op = array_pop($opStack);
            $val1 = array_pop($numStack);
            $val2 = array_pop($numStack);
            array_push($numStack, calc($val2, $val1, $op));     // 把两个值与之前的操作符进行计算并放回数组。
        }

        return $numStack[0];
    } else {
        throw new Exception('解析错误!');
    }
}


测试了一下:

echo calculate('1+2-3/3').'<br>';                  // 1.5
echo calculate('1+(2-3*5)/3').'<br>';           // 0.5
echo calculate('1+((2-3*5)/3)').'<br>';         // 1.6
echo calculate('1+(2-3*5+0.2)/3').'<br>';     // 0.48387096774194


看起来大功告成!但是这个程序还有很多值得优化和完善的地方:

1. 没有对非法的输入进行检查。

2. 如果括号缺失,可能会导致计算结果异常甚至出错。

3. 性能可以做进一步的优化。

 

总之,混乱计算器的实现方法跟普通计算器是一样的。

posted @ 2014-05-23 22:49  疯狂的小猪  阅读(395)  评论(0编辑  收藏  举报