Skip to content

Instantly share code, notes, and snippets.

@theking2
Last active July 9, 2025 13:16
Show Gist options
  • Save theking2/0f3184ba8f4a9fd604d8e9cfeb64fbc9 to your computer and use it in GitHub Desktop.
Save theking2/0f3184ba8f4a9fd604d8e9cfeb64fbc9 to your computer and use it in GitHub Desktop.
Polish notation
<?php
/**
* Polish notation
* @param $expression - Space seperated operators and param indices
* @param $param - two or more params for the calculation
*/
function pn(string $expression, mixed ...$param) {
$operators = "+-*/%"; $delimiter = " ";
$expression = explode($delimiter, $expression);
$stack = [];
do {
$itm=array_pop($expression);
if( !strstr($operators, $itm) ) { // treat everything not an operator as operand
if( !ctype_digit( $itm ) ) {
throw new \InvalidArgumentException( "Operand index not integer: $itm\n" );
}
if(! (0 <= $itm and $itm < count( $param )) ) {
throw new \OutOfBoundsException ( "Operand index out of range: $itm\n" );
}
if( !is_numeric( $param[$itm] ) ) {
throw new \InvalidArgumentException( "Operand not numeric: $param[$itm]\n" );
}
array_push($stack, $param[$itm]);
continue;
}
// we have an operator
switch($itm) {
case '+': array_push( $stack, array_pop($stack) + array_pop($stack) ); break;
case '-': array_push( $stack, array_pop($stack) - array_pop($stack) ); break;
case '*': array_push( $stack, array_pop($stack) * array_pop($stack) ); break;
case '/': array_push( $stack, array_pop($stack) / array_pop($stack) ); break;
case '%': array_push( $stack, array_pop($stack) % array_pop($stack) ); break;
//default: throw new \InvalidArgumentException( "Unknown operator $itm" );
}
} while ($expression);
return array_pop($stack);
}
// Parameters are indexed from 0 upwards
try{
echo pn("/ + 3 0 / 2 1", 30, 40, 50, 69), "\n";
} catch( Exception $e) {echo $e->getMessage();}
try{
echo pn("/ + 3 0 / 2 1", 30, 40, 50), "\n";
} catch( Exception $e) {echo $e->getMessage();}
try{
echo pn("/ + 3 3 / 2 1.1", 30, 40, 50, "a"), "\n";
} catch( Exception $e) {echo $e->getMessage();}
try{
echo pn("/ + 3 3 / 2 1", 30, 40, 50, "a"), "\n";
} catch( Exception $e) {echo $e->getMessage();}
// If parameters are in an array already use unpacking
$operants = [3, 4, 5, 6];
$expresion = "/ + 0 1 / 2 3";
echo pn($expresion, ...$operants), "\n";
/**
* Evaluates a prefix (Polish Notation) expression.
*
* Operands in the expression string are expected to be integer indices
* referring to values in the $param variable-length argument list.
*
* @param $expression The prefix expression, with elements separated by a space.
* @param $param A variable number of parameters, which serve as the actual operands.
* @return The result of the prefix expression.
* @throws \InvalidArgumentException If an operand index is not an integer,
* an actual operand is not numeric,
* not enough operands for an operator,
* an unknown operator is encountered,
* or the expression does not resolve to a single result.
* @throws \OutOfBoundsException If an operand index is out of the range of the $param array.
* @throws \DivisionByZeroError If a division or modulo by zero occurs.
*/
function ppn(string $expression, mixed ...$param): int|float {
$operators = "+-*/%";
$delimiter = " ";
$expression_tokens = explode($delimiter, $expression);
$stack = [];
// Process tokens from right to left using array_pop.
// This is the standard way to evaluate prefix expressions.
while (!empty($expression_tokens)) {
$token = array_pop($expression_tokens);
// Check if the current token is an operator.
if (str_contains($operators, $token)) {
// Operator: Pop operands, perform operation, push result.
// Ensure there are enough operands on the stack.
if (count($stack) < 2) {
throw new \InvalidArgumentException("Not enough operands for operator '{$token}' in prefix expression.");
}
// For prefix notation evaluated right-to-left:
// The first popped operand is the "right" operand.
// The second popped operand is the "left" operand.
$operand1 = array_pop($stack); // Right operand
$operand2 = array_pop($stack); // Left operand
$result = 0; // Initialize result variable
switch ($token) {
case '+':
$result = $operand2 + $operand1;
break;
case '-':
$result = $operand2 - $operand1;
break;
case '*':
$result = $operand2 * $operand1;
break;
case '/':
if ($operand1 == 0) { // Check for division by zero (right operand is the divisor)
throw new \DivisionByZeroError("Division by zero in prefix expression.");
}
$result = $operand2 / $operand1;
break;
case '%':
if ($operand1 == 0) { // Check for modulo by zero (right operand is the divisor)
throw new \DivisionByZeroError("Modulo by zero in prefix expression.");
}
$result = $operand2 % $operand1;
break;
default:
// This should theoretically not be reached if $operators is exhaustive
// and validation for operands passes first.
throw new \InvalidArgumentException("Unknown operator '{$token}' encountered in prefix expression.");
}
array_push($stack, $result);
} else {
// Operand: Validate the index and the parameter value, then push to stack.
if (!ctype_digit($token)) {
throw new \InvalidArgumentException("Operand index not an integer: {$token}");
}
$index = (int)$token; // Convert string token to integer index
if (! (0 <= $index && $index < count($param))) {
throw new \OutOfBoundsException("Operand index out of range: {$index}");
}
if (!is_numeric($param[$index])) {
throw new \InvalidArgumentException("Operand at index {$index} not numeric: {$param[$index]}");
}
array_push($stack, $param[$index]);
}
}
// After processing all tokens, the stack should contain only the final result.
if (count($stack) !== 1) {
throw new \InvalidArgumentException("Invalid prefix expression: Stack did not resolve to a single result.");
}
return array_pop($stack);
}
@theking2
Copy link
Author

theking2 commented Mar 25, 2025

FSR
Make this into a class to

  1. be able to (not) reverse
  2. be able to specify a delimiter other than ' '
  3. checking of a parameters and better exception handling.
  4. custom operators (add swap, dup(icate), drop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment