算術式を文字列として計算するためのアルゴリズム

私たちの栄光ある会社には、いわゆる非常に優れたインセンティブシステムがあります。成績:6か月ごとに、すべての開発者が成績を上げることができます。これには、給与の引き上げが伴います。言い換えれば、成績は証明です。給料を上げたいですか?6か月に1回、次のステップの認定を受け、ジュニアからシニアに成長できます(一度にジャンプできるのは2ステップまでです)。証明は友好的な方法で行われ、質問はナレッジベースに投稿され、官僚的な官僚的な形式主義はありません。認定への入学の条件は、アルゴリズムの問​​題の解決です。







そして、私は認定され、文字列の形式で算術式計算するというタスクが与えられますはい、ごみの質問です、あなたは言います(私が最初にしたように)。これはすべて長い間説明されており、ここでは複雑なことは何もありません。あなたは同時に正しいことと間違っていることになるでしょう。もちろん、問題はゴミですが、これはアルゴリズムのタスクです。既製のライブラリを使用することはできません。アルゴリズムソリューションを作成する必要があります。そして、バイナリと単項の両方のオペランド、演算子の世界に飛び込みました。そして、これらすべてを美しく解析する方法、括弧と混同しないようにする方法、そして...最も陰湿なのは単項マイナスでした。







ソリューションをphpで記述します。







もちろん、この問題に新しいことは何もありません。しばらくグーグルした後、算術式を文字列として解析するには、機械で逆ポーランド記法が最適であることがわかりましたHMOには多くの資料があり、詳細に分解しても意味がありません。たとえば、ウィキへのリンク







HMOのエントリの例: 3 4 2 + *









簡略化すると、OPPは、オペランドの後に演算子が記述され、括弧がない算術式のレコードであると言えます。

オペランドとは実数を意味し、演算子とは算術演算の記号+、-、*、/、^







HMOがマシンコンピューティングに非常に適しているのはなぜですか?







, , . . ( ).

, , , , , , , . , .







( ) :







<?php

$expression = explode(' ', '32 44 2 + *');

$stack = [];

foreach ($expression as $item) {
    if (is_numeric($item)) {
        $stack[] = (float)$item;
        continue;
    }
    $right = array_pop($stack);
    $left = array_pop($stack);
    switch ($item) {
        case '-':
            $stack[] = $left - $right;
            break;
        case '+':
            $stack[] = $left + $right;
            break;
        case '*':
            $stack[] = $left * $right;
            break;
        case '/':
            $stack[] = $left / $right;
            break;
        case '^':
            $stack[] = $left ** $right;
            break;
    }
}
//    
echo $stack[0] . PHP_EOL;
      
      





, , . :







,

.. -



() . , .

. , ~



.

( )!







? - ( ), ? , ?







, — , , , , .







. () :







  1. , , -.
  2. — .


? :

$a = -2





, ?

$a



2.

— . . 2, . .. 0.

.. $a



0 - 2



. , .







. , --2



.

? , : 0 - (0 - 2)



.

.. — , , .







, :







  • -



    (), ,




  1. , ( )
  2. , , ( ~)




, . .







.







:







    private const UNARY_MINUS = '~';
    private const OPEN_BRACKET = '(';
    private const CLOSE_BRACKET = ')';
    private const MINUS = '-';
    private const PLUS = '+';
    private const DIVISION = '/';
    private const MULTIPLICATION = '*';
    private const EXPONENTIATION = '^';

    private const PRIORITY = [
        self::OPEN_BRACKET => 0,
        self::CLOSE_BRACKET => null,
        self::PLUS => 2,
        self::MINUS => 2,
        self::MULTIPLICATION => 3,
        self::DIVISION => 3,
        self::EXPONENTIATION => 4,
        self::UNARY_MINUS => 5
    ];
      
      





:







    private const RIGHT_ASSOCIATIVE_EXPRESSION = [
        self::EXPONENTIATION, self::UNARY_MINUS
    ];
      
      





( ) .









,













  • , —







  • , , ,















  • , , , , . , , — .

    .
  • , , , .


. .










"" 2 * (2 + -2 ^ 2 ^ 3) - 1



,



















    $stack = [];
    $outString = [];
      
      





2 * (2 + -2 ^ 2 ^ 3) - 1









  • 2, —







    $outString = [2];
          
          





  • *









    $outString = [2];
    $stack = ['*'];
          
          





  • (









    $outString = [2];
    $stack = ['*', '('];
          
          





  • — 2 —







    $outString = [2, 2];
    $stack = ['*', '('];
          
          





  • +









    $outString = [2, 2];
    $stack = ['*', '(', '+'];
          
          





  • ~









    $outString = [2, 2];
    $stack = ['*', '(', '+', '~'];
          
          





  • — 2 —







    $outString = [2, 2, 2];
    $stack = ['*', '(', '+', '~'];
          
          





  • ^



    — , ^









    $outString = [2, 2, 2, '~'];
    $stack = ['*', '(', '+', '^'];
          
          







… — , , , , , , . .

, , . .







2 2 2 ~ 2 3 ^ ^ + * 1 -









, , , .







  • .
  • , .
  • , , (0 ).








  • ,


行が終了すると、スタックから計算値が返されます(算術式が正しければ、1つの要素がスタックに残ります)。










言語による完全なソリューション php









ネタバレ

Calculate







<?php
require_once __DIR__ . '/Calculate.php';

$expression = '2.5 * (-22 + 2 ^ 2 ^ 3) * (3 - 1)' . PHP_EOL;
$calc = new Calculate($expression);
if ($calc->postfixString) {
    echo ' : ' . $expression;
    echo '    (~ -   ): ' . $calc->postfixString . PHP_EOL;
    echo '   : ' . $calc->result . PHP_EOL;
} else {
    echo $calc->result . PHP_EOL;
}
      
      





Calculate







<?php

/** @noinspection PhpIllegalPsrClassPathInspection */

class Calculate
{
    private const UNARY_MINUS = '~';
    private const OPEN_BRACKET = '(';
    private const CLOSE_BRACKET = ')';
    private const MINUS = '-';
    private const PLUS = '+';
    private const DIVISION = '/';
    private const MULTIPLICATION = '*';
    private const EXPONENTIATION = '^';

    private const PRIORITY = [
        self::OPEN_BRACKET => 0,
        self::CLOSE_BRACKET => 1,
        self::PLUS => 2,
        self::MINUS => 2,
        self::MULTIPLICATION => 3,
        self::DIVISION => 3,
        self::EXPONENTIATION => 4,
        self::UNARY_MINUS => 5
    ];

    private const RIGHT_ASSOCIATIVE_EXPRESSION = [
        self::EXPONENTIATION, self::UNARY_MINUS
    ];

    private array $stack = [];
    private array $outString = [];

    /**
     * @var float|string
     */
    public $result;
    public string $postfixString = '';

    public function __construct(string $expression)
    {
        try {
            $expression = $this->checkExpression($expression);
            $this->createOutString($expression);
            $this->postfixString = implode(' ', $this->outString);
            $this->calcFromOutString();
        } catch (Exception $e) {
            $this->result = $e->getMessage();
        }
    }

    private function checkExpression(string $expression): string
    {
        preg_match('/-?\d+\s+-?\d+/', $expression, $matches);
        if ($matches) {
            throw new DomainException('   !');
        }
        $openBracket = substr_count($expression, self::OPEN_BRACKET);
        $closeBracket = substr_count($expression, self::CLOSE_BRACKET);
        if ($openBracket !== $closeBracket) {
            throw new DomainException(' !');
        }
        //     
        $expression = preg_replace('/\s/', '', $expression);
        $expression = str_replace(',', '.', $expression);
        preg_match('/[^\d()+\/*-.^]+/', $expression, $matches);
        if ($matches) {
            throw new DomainException('!      , ,   +, -, *, /, ^');
        }

        return $expression;
    }

    private function calc($left, $right, $operator)
    {
        switch ($operator) {
            case self::MINUS:
                return $left - $right;
            case self::PLUS:
                return $left + $right;
            case self::MULTIPLICATION:
                return $left * $right;
            case self::EXPONENTIATION:
                return $left ** $right;
            case self::DIVISION:
                if ($right == 0) {
                    throw new DomainException('  !');
                }
                return $left / $right;
            default:
                throw new DomainException('  ' . $operator);
        }
    }

    /**
     *    
     */
    private function createOutString(string $expression)
    {
        $length = strlen($expression) - 1;
        $number = null;

        for ($i = 0; $i <= $length; $i++) {
            $item = $expression[$i];
            $left = $i === 0 ? null : $expression[$i - 1];
            $right = $i === $length ? null : $expression[$i + 1];

            if (is_numeric($item) || $item === '.') {
                if ($item === '.') {
                    if ($left === null || $right === null || !is_numeric($left) || !is_numeric($right)) {
                        throw new DomainException('  !');
                    }
                }
                $number .= $item;
                if ($right !== '.' && !is_numeric($right)) {
                    $this->outString[] = (float)$number;
                    $number = null;
                }
                continue;
            }

            if ($item === self::MINUS) {
                if (!is_numeric($left) && $left !== self::CLOSE_BRACKET) {
                    $item = self::UNARY_MINUS;
                }
            }

            if ($item === self::OPEN_BRACKET && is_numeric($left)) {
                throw new DomainException('    ');
            }
            if ($item === self::CLOSE_BRACKET && (is_numeric($right) || $right === self::OPEN_BRACKET)) {
                throw new DomainException('    ');
            }

            $this->addToStackAndPushFromStack($item);
        }

        while ($this->stack) {
            $this->outString[] = array_pop($this->stack);
        }
    }

    private function addToStackAndPushFromStack(string $operator)
    {
        if (!$this->stack || $operator === self::OPEN_BRACKET) {
            $this->stack[] = $operator;
            return;
        }

        $stack = array_reverse($this->stack);

        if ($operator === self::CLOSE_BRACKET) {
            foreach ($stack as $key => $item) {
                unset($stack[$key]);
                if ($item === self::OPEN_BRACKET) {
                    $this->stack = array_reverse($stack);
                    return;
                }
                $this->outString[] = $item;
            }
        }

        foreach ($stack as $key => $item) {
            if (in_array($item, self::RIGHT_ASSOCIATIVE_EXPRESSION) && $item === $operator) {
                break;
            }
            if (self::PRIORITY[$item] < self::PRIORITY[$operator]) {
                break;
            }
            $this->outString[] = $item;
            unset($stack[$key]);
        }

        $this->stack = array_reverse($stack);
        $this->stack[] = $operator;
    }

    /**
     *    
     */
    private function calcFromOutString()
    {
        $stack = [];
        foreach ($this->outString as $item) {
            if (is_float($item)) {
                $stack[] = $item;
                continue;
            }
            if ($item === self::UNARY_MINUS) {
                $last = array_pop($stack);
                if (!is_numeric($last)) {
                    throw new DomainException(' !');
                }
                $stack[] = 0 - $last;
                continue;
            }
            $right = array_pop($stack) ?? null;
            $left = array_pop($stack) ?? null;
            if ($right === null || $left === null) {
                throw new DomainException(' !');
            }
            $stack[] = $this->calc($left, $right, $item);
        }
        $this->result = $stack[0];
    }
}

      
      





まとめましょう



文字列形式の算術式を美しく計算するには、次のものが必要です。







  1. 逆ポーランド記法とは何か、そしてそれがマシンコンピューティングに理想的である理由を理解する
  2. 算術式をHMOに変換し、計算します


最初のポイントと2番目のポイントの両方で、重要な概念はスタック(原則に従って編成されたシーケンス)です。最後の1つが入り、最初の1つが出ます。スタックの最後の要素は常にスタックの一番上にあります。








All Articles