<?php
set_exception_handler(function (Exception $e) {
$e->getMessage();
$e->getCode();
$e->getFile();
$e->getLine();
$e->getTrace();
});
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
});
class Addition extends ArithmeticOperator
{
public static $precedence = 2;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
if ($rightOperand->getNumerator() === 0) {
return $leftOperand;
}
$commonDenumerator = $leftOperand->getDenumerator() * $rightOperand->getDenumerator();
$resultNumerator = $leftOperand->getNumerator() * $rightOperand->getDenumerator();
$resultNumerator += $leftOperand->getDenumerator() * $rightOperand->getNumerator();
return new SimpleFraction($resultNumerator, $commonDenumerator);
}
}
abstract class ArithmeticOperator
{
public function getPrecedence()
{
return static::$precedence;
}
public function isLeftAssociative()
{
return !! static::$isLeftAssociative;
}
public function isRightAssociative()
{
return ! static::$isLeftAssociative;
}
abstract function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand);
}
class DivideByZeroException extends Exception
{
public function __construct($numerator)
{
echo sprintf("Ошибка: (%s/0) делить на ноль нельзя", $numerator); }
}
class Division extends ArithmeticOperator
{
public static $precedence = 3;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
if ($rightOperand->getNumerator() === 0) {
throw new DivideByZeroException($leftOperand);
}
$multiplication = new Multiplication();
return $multiplication->operate($leftOperand, $rightOperand->invert());
}
}
class LeftParenthesis {}
class Lexer
{
public function getLexemsFromInfix($infix)
{
if (!$this->isValidInfix($infix)) throw new InvalidArgumentException();
preg_match_all('/(-?\d+(\.\d+)?|([\+\-\/\*\^\(\)]))/', $infix, $match); $lexems = $match[0];
return $lexems;
}
private function isValidInfix($infix)
{
return true;
}
}
class MathHelper
{
public static function getLeastCommonMultiple($a, $b) {
$lcm = $max;
for ($i = 1; $lcm % $min != 0; $i++) {
$lcm = $max * $i;
}
return $lcm;
}
}
class Multiplication extends ArithmeticOperator
{
public static $precedence = 3;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
$newNumerator = $leftOperand->getNumerator() * $rightOperand->getNumerator();
$newDenumerator = $leftOperand->getDenumerator() * $rightOperand->getDenumerator();
return new SimpleFraction($newNumerator, $newDenumerator);
}
}
class OperatorFactory
{
public static function getOperatorBySign($sign)
{
switch ($sign) {
case '+': return new Addition();
case '-': return new Substraction();
case '*': return new Multiplication();
case '/': return new Division();
case '^': return new Power();
case '(': return new LeftParenthesis();
case ')': return new RightParenthesis();
default: throw new InvalidArgumentException();
}
}
}
class Power extends ArithmeticOperator
{
public static $precedence = 4;
public static $isLeftAssociative = false;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
$newNumerator = pow($leftOperand->getNumerator(), $rightOperand->getNumerator()); $newDenumerator = pow($leftOperand->getDenumerator(), $rightOperand->getNumerator());
return new SimpleFraction($newNumerator, $newDenumerator);
}
}
class RightParenthesis {}
class RpnQueueEvaluator
{
private $stack;
public function __construct(SplStack $stack)
{
$this->stack = $stack;
}
public function evaluate(SplQueue $tokens)
{
foreach ($tokens as $token) {
if ($token instanceof SimpleFraction) {
$this->stack->push($token);
} elseif ($token instanceof ArithmeticOperator) {
$rightOperand = $this->stack->pop();
$leftOperand = $this->stack->pop();
$fraction = $token->operate($leftOperand, $rightOperand);
$this->stack->push($fraction);
} else {
throw new InvalidArgumentException();
}
}
return $this->stack->top();
}
}
class ShuntingYard
{
private $operatorStack;
private $outputQueue;
public function __construct(SplStack $operatorStack, SplQueue $outputQueue)
{
$this->operatorStack = $operatorStack;
$this->outputQueue = $outputQueue;
}
public function createRpnQueueFromTokens
(array $tokens) {
foreach ($tokens as $token) {
if ($token instanceof SimpleFraction) {
$this->outputQueue->enqueue($token);
} elseif ($token instanceof ArithmeticOperator) {
if ($this->operatorStack->isEmpty()) {
$this->operatorStack->push($token);
continue;
}
$operatorTop = $this->operatorStack->top();
if (($operatorTop instanceof ArithmeticOperator)
&& (($token->isLeftassociative() && $token->getPrecedence() <= $operatorTop->getPrecedence())
|| ($token->isRightAssociative() && $token->getPrecedence() < $operatorTop->getPrecedence()))
) {
$this->outputQueue->enqueue($this->operatorStack->pop());
}
$this->operatorStack->push($token);
} elseif ($token instanceof LeftParenthesis) {
$this->operatorStack->push($token);
} elseif ($token instanceof RightParenthesis) {
$operatorTop = $this->operatorStack->top();
if (!$operatorTop instanceof LeftParenthesis) {
$this->outputQueue->enqueue($this->operatorStack->pop());
}
$this->operatorStack->pop();
}
}
while (!$this->operatorStack->isEmpty()) {
$operatorTop = $this->operatorStack->pop();
if ($operatorTop instanceof LeftParenthesis || $operatorTop instanceof RightParenthesis) {
throw new Exception('Mismatched parentheses');
}
$this->outputQueue->enqueue($operatorTop);
}
return $this->outputQueue;
}
}
class SimpleFraction
{
private $numerator;
private $denumerator;
public function __construct($numerator, $denumerator = 1)
{
$this->numerator = $numerator;
$this->denumerator = $denumerator;
$this->simplify();
}
public static function createFromDecimal($decimal)
{
preg_match_all('/^(-?\d+)(?:\.(\d+))?$/', (string
) $decimal, $matches);
if (!$matches) throw new InvalidArgumentException();
$integerPart = $matches[1][0];
$fractionalPart = empty($matches[2][0]) ?
0 : $matches[2][0];
$numerator = $integerPart * 10 + $fractionalPart;
$denumerator = 10;
return new self($numerator, $denumerator);
}
public function getDenumerator()
{
return $this->denumerator;
}
public function getNumerator()
{
return $this->numerator;
}
public function changeSign()
{
$this->numerator *= -1;
return $this;
}
public function invert()
{
list($this->denumerator, $this->numerator) = [$this->numerator, $this->denumerator];
return $this;
}
public function showAsDecimal()
{
$result = $this->numerator / $this->denumerator;
$valueAsString = sprintf('%s', $result); } else {
list($integerPart, $fractionalPart) = explode('.', (string
) $result); $valueAsString = sprintf('%s.%s', $integerPart, $fractionalPart); }
return $valueAsString;
}
public function __toString()
{
if ($this->denumerator == 10) {
$valueAsString = sprintf('%s', $this->showAsDecimal()); } elseif ($this->denumerator == 1 || $this->denumerator == -1) {
$valueAsString = sprintf('%s', $this->numerator * $this->denumerator); } else {
$valueAsString = sprintf('%s/%s', $this->numerator, $this->denumerator); }
return $valueAsString;
}
private function simplify()
{
if ($this->numerator === 0) return;
$lcm = MathHelper::getLeastCommonMultiple($this->numerator, $this->denumerator);
$oldNumerator = $this->numerator;
$this->numerator = $lcm / $this->denumerator;
$this->denumerator = $lcm / $oldNumerator;
}
}
class Substraction extends ArithmeticOperator
{
public static $precedence = 2;
public static $isLeftAssociative = true;
public function operate(SimpleFraction $leftOperand, SimpleFraction $rightOperand)
{
$addition = new Addition();
return $addition->operate($leftOperand, $rightOperand->changeSign());
}
}
class Tokenizer
{
public function tokenizeLexems
(array $lexems) {
$tokens = [];
foreach ($lexems as $lexem) {
$fraction = SimpleFraction::createFromDecimal($lexem);
$tokens[] = $fraction;
} elseif (in_array($lexem, ['+', '-', '/', '*', '^', '(', ')'])) { $operator = OperatorFactory::getOperatorBySign($lexem);
$tokens[] = $operator;
} else {
throw new Exception
(sprintf('Invalid lexem %s given', $lexem)); }
}
return $tokens;
}
}
$infix_correctAnswer = [
'2 + 3' => '5',
'4 - 3' => ' 1',
'2 + (-3)' => '-1',
'4 * 5' => '20',
'6/4' => '3/2',
'1.2 + 1/2' => '1.7',
'1/(-3) ' => '-1/3',
'0.5 + 0.2' => '0.7',
'3 ^ 2 ^ 2' => '81',
'17654/342' => '8827/171',
'2/3 ^ 2' => '2/9',
'(2/3) ^ 2' => '4/9',
'(2 + 3) / (2 - 2)' => 'Ошибка',
];
$lexer = new Lexer();
$tokenizer = new Tokenizer();
$shuntingYard = new ShuntingYard(new SplStack, new SplQueue);
$rpnQueueEvaluator = new RpnQueueEvaluator(new SplStack);
foreach ($infix_correctAnswer as $infix => $correctAnswer) {
$lexems = $lexer->getLexemsFromInfix($infix);
$tokens = $tokenizer->tokenizeLexems($lexems);
$rpnQueue = $shuntingYard->createRpnQueueFromTokens($tokens);
echo sprintf("\n%20s ожидается: %10s, получено ", $infix, $correctAnswer); $result = $rpnQueueEvaluator->evaluate($rpnQueue);
}