6

PHPを使用して、通常のユーザーがタグ8*(5+1)を介して入力した、のような単純な代数式を計算したいと思い<input>ます(つまり、通常の表記法:のような構文の変更はありませんMultiply(8, Add(5, 1)))。また、すべてのステップを表示する必要がありますが、それは難しいことではありません。問題、現在、式の値を計算しています。

注:これは私がこれまで考えていたものであり、非常に非効率的ですが、暫定的な解決策です。可能な場合は文字列を置き換えるだけです。この例では、文字列5+1を認識して。に置き換え6ます。次に、もう一度ループし、に置き換え、もう一度(6)ループし6、に置き換えます。たとえば、乗算のコードは次のようになります。8*648

for ($a=1; $a < 1000; $a++) {
    for ($b=1; $b < 1000; $b++) {
        string_replace($a . '*' . $b, $a*$b, $string);
    }
}
4

3 に答える 3

23

ニーズに応じて、操車場アルゴリズムを検討することをお勧めします。実装は非常に簡単で、非常にうまく機能します。

これが私が少し前に作り上げた例です:GIST

これが1つのブロックにコピー/貼り付けされたコードです:

式の定義:

class Parenthesis extends TerminalExpression {

    protected $precidence = 7;

    public function operate(Stack $stack) {
    }

    public function getPrecidence() {
        return $this->precidence;
    }

    public function isNoOp() {
        return true;
    }

    public function isParenthesis() {
        return true;
    }

    public function isOpen() {
        return $this->value == '(';
    }

}

class Number extends TerminalExpression {

    public function operate(Stack $stack) {
        return $this->value;
    }

}

abstract class Operator extends TerminalExpression {

    protected $precidence = 0;
    protected $leftAssoc = true;

    public function getPrecidence() {
        return $this->precidence;
    }

    public function isLeftAssoc() {
        return $this->leftAssoc;
    }

    public function isOperator() {
        return true;
    }

}

class Addition extends Operator {

    protected $precidence = 4;

    public function operate(Stack $stack) {
        return $stack->pop()->operate($stack) + $stack->pop()->operate($stack);
    }

}

class Subtraction extends Operator {

    protected $precidence = 4;

    public function operate(Stack $stack) {
        $left = $stack->pop()->operate($stack);
        $right = $stack->pop()->operate($stack);
        return $right - $left;
    }

}

class Multiplication extends Operator {

    protected $precidence = 5;

    public function operate(Stack $stack) {
        return $stack->pop()->operate($stack) * $stack->pop()->operate($stack);
    }

}

class Division extends Operator {

    protected $precidence = 5;

    public function operate(Stack $stack) {
        $left = $stack->pop()->operate($stack);
        $right = $stack->pop()->operate($stack);
        return $right / $left;
    }

}

class Power extends Operator {

    protected $precidence=6;

    public function operate(Stack $stack) {
        $left = $stack->pop()->operate($stack);
        $right = $stack->pop()->operate($stack);
        return pow($right, $left);
    }
}

abstract class TerminalExpression {

    protected $value = '';

    public function __construct($value) {
        $this->value = $value;
    }

    public static function factory($value) {
        if (is_object($value) && $value instanceof TerminalExpression) {
            return $value;
        } elseif (is_numeric($value)) {
            return new Number($value);
        } elseif ($value == '+') {
            return new Addition($value);
        } elseif ($value == '-') {
            return new Subtraction($value);
        } elseif ($value == '*') {
            return new Multiplication($value);
        } elseif ($value == '/') {
            return new Division($value);
        } elseif ($value == '^') {
            return new Power($value);
        } elseif (in_array($value, array('(', ')'))) {
            return new Parenthesis($value);
        }
        throw new Exception('Undefined Value ' . $value);
    }

    abstract public function operate(Stack $stack);

    public function isOperator() {
        return false;
    }

    public function isParenthesis() {
        return false;
    }

    public function isNoOp() {
        return false;
    }

    public function render() {
        return $this->value;
    }
}

スタック(本当に単純な実装):

class Stack {

    protected $data = array();

    public function push($element) {
        $this->data[] = $element;
    }

    public function poke() {
        return end($this->data);
    }

    public function pop() {
        return array_pop($this->data);
    }

}

そして最後に、エグゼキュータクラス:

class Math {

    protected $variables = array();

    public function evaluate($string) {
        $stack = $this->parse($string);
        return $this->run($stack);
    }

    public function parse($string) {
        $tokens = $this->tokenize($string);
        $output = new Stack();
        $operators = new Stack();
        foreach ($tokens as $token) {
            $token = $this->extractVariables($token);
            $expression = TerminalExpression::factory($token);
            if ($expression->isOperator()) {
                $this->parseOperator($expression, $output, $operators);
            } elseif ($expression->isParenthesis()) {
                $this->parseParenthesis($expression, $output, $operators);
            } else {
                $output->push($expression);
            }
        }
        while (($op = $operators->pop())) {
            if ($op->isParenthesis()) {
                throw new RuntimeException('Mismatched Parenthesis');
            }
            $output->push($op);
        }
        return $output;
    }

    public function registerVariable($name, $value) {
        $this->variables[$name] = $value;
    }

    public function run(Stack $stack) {
        while (($operator = $stack->pop()) && $operator->isOperator()) {
            $value = $operator->operate($stack);
            if (!is_null($value)) {
                $stack->push(TerminalExpression::factory($value));
            }
        }
        return $operator ? $operator->render() : $this->render($stack);
    }

    protected function extractVariables($token) {
        if ($token[0] == '$') {
            $key = substr($token, 1);
            return isset($this->variables[$key]) ? $this->variables[$key] : 0;
        }
        return $token;
    }

    protected function render(Stack $stack) {
        $output = '';
        while (($el = $stack->pop())) {
            $output .= $el->render();
        }
        if ($output) {
            return $output;
        }
        throw new RuntimeException('Could not render output');
    }

    protected function parseParenthesis(TerminalExpression $expression, Stack $output, Stack $operators) {
        if ($expression->isOpen()) {
            $operators->push($expression);
        } else {
            $clean = false;
            while (($end = $operators->pop())) {
                if ($end->isParenthesis()) {
                    $clean = true;
                    break;
                } else {
                    $output->push($end);
                }
            }
            if (!$clean) {
                throw new RuntimeException('Mismatched Parenthesis');
            }
        }
    }

    protected function parseOperator(TerminalExpression $expression, Stack $output, Stack $operators) {
        $end = $operators->poke();
        if (!$end) {
            $operators->push($expression);
        } elseif ($end->isOperator()) {
            do {
                if ($expression->isLeftAssoc() && $expression->getPrecidence() <= $end->getPrecidence()) {
                    $output->push($operators->pop());
                } elseif (!$expression->isLeftAssoc() && $expression->getPrecidence() < $end->getPrecidence()) {
                    $output->push($operators->pop());
                } else {
                    break;
                }
            } while (($end = $operators->poke()) && $end->isOperator());
            $operators->push($expression);
        } else {
            $operators->push($expression);
        }
    }

    protected function tokenize($string) {
        $parts = preg_split('((\d+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        $parts = array_map('trim', $parts);
        return $parts;
    }

}

これは、最初に入力をトークン化することによって機能します(単語の境界とトークンに基づいて)。次に、その上で操車場アルゴリズムを実行して、入力をRPN(逆ポーランド記法)スタックに変換します。次に、スタックを実行するだけです。簡単な例を次に示します。

$math = new Math();

$answer = $math->evaluate('(2 + 3) * 4');
var_dump($answer);
// int(20)

$answer = $math->evaluate('1 + 2 * ((3 + 4) * 5 + 6)');
var_dump($answer);
// int(83)

$answer = $math->evaluate('(1 + 2) * (3 + 4) * (5 + 6)');
var_dump($answer);
// int(231)

$math->registerVariable('a', 4);
$answer = $math->evaluate('($a + 3) * 4');
var_dump($answer);
// int(28)

$math->registerVariable('a', 5);
$answer = $math->evaluate('($a + $a) * 4');
var_dump($answer);
// int(40)

さて、この例はあなたが必要とするよりもかなり複雑です。その理由は、グループ化と演算子の優先順位も処理するためです。しかし、これはEVALを使用せず、変数をサポートする実行中のアルゴリズムの適切な例です...

于 2012-10-02T15:35:42.740 に答える
4

興味深いかもしれないbcParserPHPと呼ばれるMathParserクラスがあります。

使い方はかなり簡単で、かなり強力なようです。

彼らのサイトからのサンプルコード:

$parser = new MathParser();
$parser->setVariable('X', 5);
$parser->setVariable('Y', 2);
$parser->setExpression('COS(X)+SIN(Y)/2');
echo $parser->getValue();

残念ながら、これは商用製品です。それがあなたがそれを使うのをやめるかどうかはわかりません(それは価格とあなたのニーズに依存すると思います)。

非営利の代替案はこれかもしれません:http ://www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html

このクラスはeval()内部で使用することに注意してください。可能であれば、これは避けます。

それができない場合は、独自の言語パーサーを作成するのが理想的なソリューションですが、PHPでそれを行うのはあまり賢明ではありません。

于 2012-10-02T15:02:54.577 に答える
1

式に含めるべきではないものの入力を取り除くことから始めます(加算、減算、乗算、除算を許可し、変数を許可しないと仮定します)。

 $expr = preg_replace('/[^0-9+*\/-]/', '', $expr);

次に、ユーザー入力に危険なものが残っていないことを確認したら、itthrough eval()を渡して式を評価します。

 $result = eval("return $expr;");

車輪の再発明をする必要はありません。

Kolinkの修正を組み込むために編集されました。ありがとう!

于 2012-10-02T14:57:48.533 に答える