How to make a calculator in PHP? - php

I want to use PHP to calculate simple algebraic expressions like, 8*(5+1), entered via an <input> tag by a normal user (which means, normal notation: no syntax changes like Multiply(8, Add(5, 1))). Also, it has to show all steps, but that's not hard. The problem, right now, is calculating the value of the expressions.
Note: this is what I thought so far, which is quite inefficient but it's a provisory solution.
Just replace strings where possible: in our example, recognize the string 5+1 and replace it with 6. Then, loop again, replace (6) with 6, loop again, and replace 8*6 with 48.
The code for multiplying, for example, should look like this:
for ($a=1; $a < 1000; $a++) {
for ($b=1; $b < 1000; $b++) {
string_replace($a . '*' . $b, $a*$b, $string);
}
}

Depending on your needs, I would suggest looking into the Shunting Yard Algorithm. It's pretty easy to implement, and works quite well.
Here's an example I whipped up a while ago: GIST.
Here's the code copy/pasted into one block:
Expression Definitions:
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;
}
}
The stack (really simple implementation):
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);
}
}
And finally, the executor class:
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;
}
}
It works by first tokenizing the input (based on word boundary, and tokens). Then, it runs the Shunting Yard algorithm on it to convert the input into a RPN (Reverse Polish Notation) stack. Then, it's just a matter of executing the stack. Here's a quick example:
$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)
Now, this example is significantly more complex than you may need. The reason is that it also handles grouping and operator precedence. But it's a decent example of a running algorithm that doesn't use EVAL and supports variables...

There is a Math Parser class called bcParserPHP that might be of interest.
Seems fairly simple to use and pretty powerful.
Example code from their site:
$parser = new MathParser();
$parser->setVariable('X', 5);
$parser->setVariable('Y', 2);
$parser->setExpression('COS(X)+SIN(Y)/2');
echo $parser->getValue();
Unfortunately, it's a commercial product; I don't know if that would stop you using it or not (guess it depends on the price and on your needs).
A non-commercial alternative might be this one: http://www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html
Note that this class uses eval() internally, which I would avoid doing if possible.
Failing that, writing your own language parser would be the ideal solution, but not really sensible to do that in PHP.

I'd start by stripping the input of anything which shouldn't be in the expression (assuming you just want to allow add, subtract, multiply, divide, and no variables):
$expr = preg_replace('/[^0-9+*\/-]/', '', $expr);
and then, once I'm confident nothing dangerous remains in the user input, simply pass the itthrough eval() to evaluate the expression:
$result = eval("return $expr;");
No need to reinvent the wheel.
Edited to incorporate Kolink's corrections. Thanks!

Related

PHP Binary Tree Recursive Traversal Infinite Loop Issue

I have a binary tree and node class that can create nodes and then recursively traverse the root for pre, post and in-order node orders. This code works when in JS, but for some reason infinitely loops with a warning of "Cannot use '$this' in non-object context." when returning $this in the addSide() function. What is causing this infinite loop, and how can I fix it?
<?php
class Node {
public $value;
public $right = null;
public $left = null;
function __constructor($value) {
$this->value = $value;
}
}
class BinaryTree {
public $root;
function __constructor() {}
function create($value) {
$newNode = new Node($value);
if (!$this->root) {
$this->root = $newNode;
return $this; //no warning
}
$current = $this->root;
function addSide($side, $current, $newNode) {
if (!$current->$side) {
$current->$side = $newNode;
return $this; //Warning: "Cannot use '$this' in non-object context."
}
$current = $current->$side;
};
while (true) {
if ($value === $current->value) return $this;
if ($value < $current->value) addSide("left", $current, $newNode);
else addSide("right", $current, $newNode);
}
}
function preOrder() {
$visited = [];
$current = $this->root;
function traversePreOrder($node) {
array_push($visited, $node->value);
if ($node->left) traversePreOrder($node->left);
if ($node->right) traversePreOrder($node->right);
};
traversePreOrder($current);
return $visited;
}
function postOrder() {
$visited = [];
$current = $this->root;
function traversePostOrder($node) {
if ($node->left) traversePostOrder($node->left);
if ($node->right) traversePostOrder($node->right);
array_push($visited, $node->value);
};
traversePostOrder($current);
return $visited;
}
function inOrder() {
$visited = [];
$current = $this->root;
function traverseInOrder($node) {
if ($node->left) traverseInOrder($node->left);
array_push($visited, $node->value);
if ($node->right) traverseInOrder($node->right);
};
traverseInOrder($current);
return $visited;
}
}
$tree = new BinaryTree();
$tree->create(50);
$tree->create(30);
$tree->create(45);
$tree->create(12);
$tree->create(29);
echo("inOrder: ". $tree->inOrder());
echo("preOrder: ". $tree->preOrder());
echo("postOrder: ". $tree->postOrder());
Since you don't seem to be from a PHP background, here are some of the things to note down:
It is __construct() and not __constructor(). This served to be a major problem in the code during value comparisons.
No need to create functions inside functions. This can lead to cannot redeclare function issues when a method is called twice.
When calling a method from another method inside a class, $this-> is necessary unless the function being called is an inbuilt function in PHP or at least available during code execution.
You seem to be creating a Binary Search Tree instead of just a Binary Tree.
Pass $visited by reference when collecting values during traversal.
You can't print arrays using echo. Use print_r() or use implode() to convert the array to string using a delimiter(say ,) and then print it using echo.
In create(), you sometimes return a node and sometimes $this. Both are not the same. Former one is an object of the Node class and the latter one is the object of the BinaryTree class.
In create() method, you simply need to traverse left or right from the current code according to the given value, which can be achieved using a simple while loop.
Corrected Code:
<?php
class Node {
public $value;
public $right = null;
public $left = null;
function __construct($value) {
$this->value = $value;
}
}
class BinaryTree {
public $root;
function __construct() {
$this->root = null;
}
function create($value) {
$newNode = new Node($value);
if ($this->root === null) {
$this->root = $newNode;
return $newNode; //no warning
}
$current = $this->root;
while($current !== null){
if($current->value > $value){
if($current->left === null){
$current->left = $newNode;
break;
}else{
$current = $current->left;
}
}else if($current->value < $value){
if($current->right === null){
$current->right = $newNode;
break;
}else{
$current = $current->right;
}
}else{
throw new \Exception("Node with $value already exists.");
}
}
return $newNode;
}
function preOrder() {
$visited = [];
$current = $this->root;
$this->traversePreOrder($current,$visited);
return $visited;
}
function traversePreOrder($node,&$visited) {
array_push($visited, $node->value);
if ($node->left !== null) $this->traversePreOrder($node->left,$visited);
if ($node->right !== null) $this->traversePreOrder($node->right,$visited);
}
function postOrder() {
$visited = [];
$current = $this->root;
$this->traversePostOrder($current,$visited);
return $visited;
}
function traversePostOrder($node,&$visited) {
if ($node->left !== null) $this->traversePostOrder($node->left,$visited);
if ($node->right !== null) $this->traversePostOrder($node->right,$visited);
array_push($visited, $node->value);
}
function inOrder() {
$visited = [];
$current = $this->root;
$this->traverseInOrder($current,$visited);
return $visited;
}
function traverseInOrder($node,&$visited) {
if ($node->left != null) $this->traverseInOrder($node->left,$visited);
array_push($visited, $node->value);
if ($node->right !== null) $this->traverseInOrder($node->right,$visited);
}
}
$tree = new BinaryTree();
$tree->create(50);
$tree->create(30);
$tree->create(45);
$tree->create(12);
$tree->create(29);
echo "inOrder: ". implode(",",$tree->inOrder()),PHP_EOL;
echo "preOrder: ". implode(",",$tree->preOrder()),PHP_EOL;
echo "postOrder: ". implode(",",$tree->postOrder()),PHP_EOL;
Online Demo

Is it possible to make a unique value in PHP?

I'm making a array class and want a value to be able to be returned by a higher order function. The idea is that its a instance constant or method returned value such that I can skip the value in a map.
In other languages making an array or some compound value, like ['skip'] will make it pointer equal such that I can then use the operator for pointer equal and it will not be equal to other arrays with the exact same content, but my problem is that ['skip'] === ['skip'] is true so even with === the two values are the same.
Here is an example of usage of my code where I accedentally have the same value as I used to skip:
namespace Test;
use Common\Domain\Collection;
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? Collection::SKIP : ["skip"];
});
echo count($arr2); // prints 0, but should be 2
Is there a way to get a unique value or work around this somehow?
Here is code that implements Collection:
namespace Common\Domain;;
class Collection implements \Iterator, \Countable, \ArrayAccess
{
const SKIP = ["skip"];
private $arr = [];
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result !== self::SKIP) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
// implementation of interfaces \Iterator, \Countable, \ArrayAccess
public function current()
{
return current($this->arr);
}
public function next()
{
next($this->arr);
}
public function key()
{
return key($this->arr);
}
public function valid()
{
return isset($this->arr[$this->key()]);
}
public function rewind()
{
reset($this->arr);
}
public function count()
{
return count($this->arr);
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset)
{
return $this->arr[$offset];
}
public function offsetSet($offset, $value)
{
$this->arr[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->arr[$offset]);
}
}
I guess you are looking for Java-type enumerations, which doesn't exist in PHP. My best guess on your problem would be to use an object instead of a constant, that you would instantiate statically for a convenient use. Then, in the loop of your map function, you check the value with an instanceof instead of the basic equality operator, against the class you defined.
So, here :
class UniqueValue
{
public static function get()
{
return new self();
}
}
Then :
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? UniqueValue::get() : ["skip"];
});
And inside your collection :
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result ! instanceof UniqueValue) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
This is the quickest approach I can think of. If your array contains data from "outside" I don't think it's possible in any way that it matches against a class check from your own code.
I would solve this by implementing another method for this. The method delete would map a function over the collection and remove any elements where the function returns false.
e.g.
class Collection
{
// ...
public function delete($func)
{
$result = new static();
foreach($this->arr as $item)
{
if($func($item) !== false) $result[] = $item;
}
}
}
// example
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->delete(function ($v) {
return $v % 2 ? true : false;
});
var_dump($arr2); // prints [2, 4]

Php: turning it into a recursive function

I have currently two classes.
the ArrayCompare class:
<?php
namespace App\Tools\RegexExtract;
class ArrayCompare
{
public function compare(Array $arrayToCompare)
{
$elementData = new ElementMetaData();
$metaData = $elementData->extract($arrayToCompare[0], [], $initial=true);
foreach ($arrayToCompare as $currentElement) {
$metaData = $elementData->extract($currentElement, $metaData);
}
return $metaData;
}
}
which uses the ElementMetaData class
<?php
/**
* A class for extracting meta data from an element.
*/
namespace App\Tools\RegexExtract;
class ElementMetaData
{
public function extract($element, $metaDataToCompare = [], $initial = false)
{
if ($initial == true) {
$this->isInteger($element) ? $returnMetaData['isInteger'] = $this->isInteger($element) : null;
$returnMetaData['length'] = $this->length($element);
}
else {
$returnMetaData=$metaDataToCompare;
if ($returnMetaData != []) {
if (isset ($returnMetaData['isInteger']) && !$this->isInteger($element)) {
unset($returnMetaData['isInteger']);
}
if (isset ($returnMetaData['length']) && $this->length($element) != $returnMetaData['length']) {
unset($returnMetaData['length']);
}
}
}
return $returnMetaData;
}
private function isInteger($element)
{
return is_int($element);
}
private function length($element)
{
return strlen($element);
}
}
the basic functionality is:
given I have an array
$arr=[1,2,3];
I want to get the "similarities" between ALL Elements. According to a an array i Predefine...so this would deliver this result:
$metaArray=['isInteger'=>true,'length'=>1];
and this would deliver just length as similarity:
$arr=[1,2,'D'];
$metaArray=['length'=>1];
While this array would deliver an empty result []
$arr=[1,2,'3D']; // result is [] since not all integers or not all of same length.
Now my solution does not use recursive functions...but I am sure it can be used somehow.
Also, I want to add more "criteria"....So "isEmailAdress", "beginswithA"....etc....and this would make my if statements a horror....so what is the best strategy/design pattern to follow here?
#deceze beat me to it by fair margin... but I'll still post my solution that works basically with the same principles.
abstract class abstractComparer
{
private $array;
private $result = true;
protected $name;
public function compareArray($array)
{
$current = null;
foreach ($array as $index => $value)
{
$this->result = $this->result && $this->compareValues($index, $current, $value);
$current = $value;
}
}
public function getResult()
{
return $this->result;
}
public function getName()
{
return $this->name;
}
public abstract function compareValues($index, $value1, $value2);
public abstract function getSuccessValue();
}
class intComparer extends abstractComparer
{
protected $name = "isInteger";
public function compareValues($index, $value1, $value2)
{
return is_int($value2);
}
public function getSuccessValue()
{
return true;
}
}
class lengthComparer extends abstractComparer
{
protected $name = "length";
protected $length = 0;
public function compareValues($index, $value1, $value2)
{
$this->length = strlen($value2);
return $index == 0 || strlen($value1) == $this->length;
}
public function getSuccessValue()
{
return $this->length;
}
}
And do the actual processing like this:
$temp = [1,2,3];
$comparers = [new intComparer(), new lengthComparer()];
$result = array();
foreach ($comparers as $comparer)
{
$comparer->compareArray($temp);
if ($comparer->getResult())
{
$result[$comparer->getName()] = $comparer->getSuccessValue();
}
}
//var_dump($result);
I don't see any need for recursion here, so I'll just make a suggestion for a design approach:
Implement each criterion as a class:
abstract class Criterion {
protected $valid = true;
abstract public function initialize($value);
abstract public function check($value);
public function isValid() {
return $this->valid;
}
}
class Length extends Criterion {
protected $length;
public function initialize($value) {
$this->length = strlen($value);
}
public function check($value) {
if ($this->length != strlen($value)) {
$this->valid = false;
}
}
}
You then make an array of all your criteria:
$criteria = [new Length, ...];
foreach ($criteria as $criterion) {
$criterion->initialize($values[0]);
}
And slowly whittle them down through your values:
foreach ($values as $value) {
foreach ($criteria as $criterion) {
$criterion->check($value);
}
}
$commonCriteria = array_filter($criteria, function (Criterion $criterion) {
return $criterion->isValid();
});

Priority Queues in PHP

I'm trying to create a priority queue using this code and I can't find where the problem is. Someone tell me where I went wrong.
<?php
class PriorityQueue implements Iterator , Countable
{
public function __construct() {
$flags = self::EXTR_DATA;
$items = array();
}
function compare ( mixed $priority1 , mixed $priority2 ){}
function count (){
return count($this->items);
}
function current (){
switch ($this->flags) {
case self::EXTR_BOTH:
$ret = array();
$ret['Patient'] = current($this->items);
$ret['Priority'] = $this->key();
break;
case self::EXTR_DATA:
$ret = current($this->items);
break;
case self::EXTR_PRIORITY:
$ret = $this->key();
break;
};
return $ret;
}
function extract (){
$ret = $this->current();
$this->next();
return $ret;
}
function insert ($name,$priority){
$patient = array();
return $patient[$name] = $priority;
}
function isEmpty ()
{
return empty($this->items);
}
function key (){
return substr(key($this->items), 0, 9);
}
function next (){
//array_shift($this->items);
return($this->items);
echo "<br />";
}
function recoverFromCorruption (){}
function rewind (){}
function setExtractFlags (int $flags ){
switch ($flags) {
case self::EXTR_BOTH:
case self::EXTR_DATA:
case self::EXTR_PRIORITY:
$this->flags = $flags;
break;
};
}
function top (){
return $this->current();
}
function valid () {
if (NULL !== key($this->items)) {
return TRUE;
}
return FALSE;
}// function valid
/**
* Extract the data.
*/
const EXTR_DATA = 1;
/**
* Extract the priority.
*/
const EXTR_PRIORITY = 2;
/**
* Extract an array containing both priority and data.
*/
const EXTR_BOTH = 3;
};
$objPQ = new splPriorityqueue();
$objPQ->insert('Richard',9);
$objPQ->insert('paul',1);
$objPQ->insert('Ken',8);
$objPQ->insert('peter',2);
$objPQ->insert('Rick',7);
$objPQ->insert('Dan',5);
echo "PATIENTS = ".$objPQ->count()."<br />";
//mode of extraction
$objPQ->setExtractFlags(splPriorityqueue::EXTR_BOTH);
//Go to TOP
$objPQ->top();
for($i=0,$j=$objPQ->count(); $i<$j; $i++){
//print_r($objPQ->current());
$patients = $objPQ->current();
foreach ($patients as $patient=>$value){
echo $patient."<br />".$value;
$objPQ->next();
echo "<br />";
}
}
?>
I'm now getting some weird result
data-patient Richard
priority-9
......
etc
I want to get results to be
Richard - 9
Ken - 8
Rick - 7
Dan - 5
Peter - 2
Paul - 1
Considering the priority given
The Standard PHP Library (SPL) implements the SplPriorityQueue class :
$pq = new SplPriorityQueue();
// The insert method inserts an element in the queue by shifting it up
$pq->insert('A', 3);
$pq->insert('B', 6);
$pq->insert('C', 1);
$pq->insert('D', 2);
// Count the elements
echo "count ->" . $pq->count() . PHP_EOL;
// Sets the mode of extraction (EXTR_DATA, EXTR_PRIORITY, EXTR_BOTH)
$pq->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
// Go at the node from the top of the queue
$pq->top();
// Iterate the queue (by priority) and display each element
while ($pq->valid()) {
print_r($pq->current());
echo PHP_EOL;
$pq->next();
}
Try this modified class:
class PriorityQueue implements Iterator, Countable {
/**
* Extract the data.
*/
const EXTR_DATA = 1;
/**
* Extract the priority.
*/
const EXTR_PRIORITY = 2;
/**
* Extract an array containing both priority and data.
*/
const EXTR_BOTH = 3;
private $flags;
private $items;
public function __construct() {
$this->flags = self::EXTR_DATA;
$this->items = array();
}
function compare($priority1, $priority2) {}
function count() {
return count($this->items);
}
function extract() {
$result = $this->current();
$this->next();
return $result;
}
function current() {
switch ($this->flags) {
case self::EXTR_BOTH:
$result = $this->key() . ' - ' . current($this->items);
break;
case self::EXTR_DATA:
$result = $this->key();
break;
case self::EXTR_PRIORITY:
$result = current($this->items);
break;
default:
$result = '';
}
return $result;
}
function key() {
return key($this->items);
}
function next() {
return next($this->items);
}
function insert($name, $priority) {
$this->items[$name] = $priority;
asort($this->items);
return $this;
}
function isEmpty() {
return empty($this->items);
}
function recoverFromCorruption() {}
function rewind() {}
function setExtractFlags($flags) {
switch ($flags) {
case self::EXTR_BOTH:
case self::EXTR_DATA:
case self::EXTR_PRIORITY:
$this->flags = $flags;
break;
};
}
function valid() {
return (null === key($this->items)) ? false : true;
}
}
Usage:
$patients = new PriorityQueue();
$patients->setExtractFlags(PriorityQueue::EXTR_BOTH);
$patients->insert('Richard', 9)
->insert('paul', 1)
->insert('Ken', 8)
->insert('peter', 2)
->insert('Rick', 7)
->insert('Dan', 5);
foreach($patients as $patient) {
echo $patient->current();
}

PHP Bi-Directional map

I'm porting to PHP a piece of Java code that uses a lot of Bi-directional maps (Guava's BiMap). Java-like maps are provided by PHP arrays or SplObjectStorage, but is there a library PHP Bi-Directional map available?
This class should provide for most needs of a bi-directional map :
class BiMap
{
private $KtoV, $VtoK;
public function __constructor()
{
$this->KtoV = []; // for version < 5.4.0, syntax must be: $this->KtoV = array();
$this->VtoK = [];
}
public function getKey($v)
{
if($this->hasValue($v))
{
return $this->VtoK[$v];
}
else
{
return null;
}
}
public function getAllKeys()
{
if($this->KtoV)
{
return array_keys($this->KtoV);
}
else
{
return $this->KtoV;
}
}
public function getValue($k)
{
if($this->hasKey($k))
{
return $this->KtoV[$k];
}
else
{
return null;
}
}
public function getAllValues()
{
if($this->VtoK)
{
return array_keys($this->VtoK);
}
else
{
return $this->VtoK;
}
}
public function hasKey($k)
{
return isset($this->KtoV[$k]);
}
public function hasValue($v)
{
return isset($this->VtoK[$v]);
}
public function put($k, $v)
{
if($this->hasKey($k))
{
$this->removeKey($k);
}
if($this->hasValue($v))
{
$this->removeValue($v);
}
$this->KtoV[$k] = $v;
$this->VtoK[$v] = $k;
}
public function putAll($array)
{
foreach($array as $k => $v)
{
$this->put($k, $v);
}
}
public function removeKey($k)
{
if($this->hasKey($k))
{
unset($this->VtoK[$this->KtoV[$k]]);
$v = $this->KtoV[$k];
unset($this->KtoV[$k]);
return $v;
}
else
{
return null;
}
}
public function removeValue($v)
{
if($this->hasValue($v))
{
unset($this->KtoV[$this->VtoK[$v]]);
$k = $this->VtoK[$v];
unset($this->VtoK[$v]);
return $k;
}
else
{
return null;
}
}
}
However, if you require null checking for key/values and/or object/array checking then handling similar to the following lines of code should be given in the body of a function and called appropriately within the hasKey($k), hasValue($v) and put($k, $v) methods :
if($item === null)
{
throw new Exception('null as BiMap key / value is invalid.');
}
if(is_object($item) || is_array($item))
{
throw new Exception('Object / Array as BiMap key / value is invalid.');
}
I did that once putting the values into 2 arrays. If keySet() and valueSet() are disjunct you can even use one value. Example:
$mapKtoV = array();
$mapVtoK = array();
function putInMap ($key,$value)
{
$mapKtoV[$key] = $value;
$mapVtoK[$value] = $key;
}
Of course you can also put them into a class.
Do you also think that this solution appears dodgy and smells? Yes, true, welcome to the world of PHP, which is usually dominated by bad code design. If you are really looking for a good solution, you should actually port you source from PHP to Java ;)
Hope it helps.

Categories