<?php /** * Seek the input value that generates the required output value for a given function and context * * Given a supplied (callback) function, finds out what input value to the function is required to generate the specified * result value for a given set of conditions. For example, what value of the quality parameter is needed to * achieve a specified file size for a given image. The binary chop search method is used to minimise the * number of attempts. This method requires a comparison between the required value and the most recent achieved value. * This comparison is also carried out in a callback function so that the comparison is under user control. * * @property callback string The name of a function which maps the independent variable to the dependent variable * @property comparator string The name of a function which compares the result of callback to the sought value * @property maxattempts integer Max number of times to iterate. Default 30 * @property boundLower integer the minimum value of the independent variable. Default 0 * @property boundUpper integer the maximum value of the independent variable. Default 99 * @property searchvalue mixed this value will be passed to comparator for comparison with the result of callback function * @property items mixed cargo for the callback function. Invariate value(s) * @property-read status integer how the search was completed. Values 1-4 * @ property-read statusmsg string how the search was completed. * @ property-read outputvalue mixed contains the final result from callback * @method mixed search returns outputvalue, documented above * */ class ValueSeeker { private $callback; //The function which transforms the input value into the output value - default: pick element from array private $comparator; //used for tests of $searchvalue against outputvalue of callback function - default: simple comparison a gt/lt/= b private $maxattempts; //maximum number of times the search is iterated before giving up - default 30 private $boundLower; //lowest input value to the callback function - default 0 private $boundUpper; //highest input value to the callback function - default 99 private $searchvalue; //The output value to be aimed for private $items; //parametric data for the callback function private $status; private $statusmsg; private $inputvalue; //used to probe output values of callback. value range between $boundUpper and $boundLower private $outputvalue; //hold the result of the callback function
/** * Function to compare two result values * * This is a simple comparison between two integers or strings. It is the default * callback function for comparator. It will normally be overridden by a more complex * comparison. * * @param mixed a the value of the searchvalue property * @param mixed b the return value after calling the callback function defined in the callback property * @return integer -1: a is less, +1: b is less; 0: they are the same * */ private function compare($a, $b) { //default function for comparator if ($a == $b) return 0; if ($a > $b) return 1; return -1; }
/** * Function to map an independent variable to a dependent variable * * This is a sample function. It assumes that the property 'items' contains a number * indexed array. This sample callback function transforms the index of the array to * the value of that indexed item. * */ private function arrayselect($array, $index) { //default function for callback return $array[$index]; }
function __construct() { $this->callback = 'arrayselect'; $this->comparator = 'compare'; $this->maxattempts = 30; $this->boundLower = 0; $this->boundUpper = 99; }
public function __set ($var, $value) { switch ($var) { case 'searchvalue': $this->searchvalue = $value; break;
case 'items': $this->items = $value; break;
case 'callback': if(!function_exists($value)) throw new Exception("$var function '$value()' does not exist",4); $this->callback = $value; break;
case 'comparator': if(!function_exists($value)) throw new Exception("$var function '$value()' does not exist",5); $this->comparator = $value; break;
case 'maxAttempts': if(!is_integer($value)) throw new Exception("maxattempts is not an integer",2); if( $maxattempts < 1 ) throw new Exception("maxattempts must be at least 1", 3); $this->maxattempts = $value; break;
case 'boundLower': if(!is_integer($value)) throw new Exception("lower bound of range is not an integer",2); $this->boundLower = $value; break;
case 'boundUpper': if(!is_integer($value)) throw new Exception("upper bound of range is not an integer",2); $this->boundUpper = $value; break;
default: throw new Exception("Attempt to set non-existent property:$var\n\r",10); } } public function __get($var) { switch ($var) { case 'searchvalue': return $this->searchvalue; break; case 'items': return $this->items; break; case 'maxattempts': return $this->maxattempts; break; case 'boundLower': return $this->boundLower; break; case 'boundUpper': return $this->boundUpper; break; case 'comparator': return $this->comparator; break; case 'callback': return $this->callback; break; case 'outputvalue': return $this->outputvalue; break; case 'status': return $this->status; break; case 'statusmsg': return $this->statusmsg; break; default: throw new Exception("Attempt to get non-existent property:$var\n\r",11); } }
function search() { if($this->boundUpper < $this->boundLower) throw new Exception("range invalid:lower bound > upper bound",1);
$boundUpper = $this->boundUpper; $boundLower = $this->boundLower;
$inputValue = round(($boundUpper + $boundLower) / 2, 0); $attempts = 0; $comparison = 0;
while( $attempts < $this->maxattempts ) { $this->outputvalue = call_user_func($this->callback, $this->items, $inputValue); $comparison = call_user_func($this->comparator, $this->searchvalue, $this->outputvalue );
if ( $comparison > 0 ) { $boundLower = $inputValue; $inputValue = round(($boundUpper + $inputValue) / 2, 0); if($inputValue == $boundLower) { if($boundLower == $this->boundLower) { $this->statusmsg = "Hit lower bound"; $this->status = 3; } else { $this->statusmsg = "Found nearest input value(-)"; $this->status = 2; } return $inputValue;//gone as low as we can } } else { if ( $comparison < 0 ) { $boundUpper = $inputValue; $inputValue = round(($boundLower + $inputValue) / 2, 0); if( $inputValue == $boundUpper ) { if($boundUpper == $this->boundUpper) { $this->statusmsg = "Hit upper bound"; $this->status = 4; } else { $this->statusmsg = "Found nearest input value(-)"; $this->status = 2; } return $inputValue - 1;//gone as high as we can } } else { $this->statusmsg = "Reached target value"; $this->status = 1; return $inputValue; } } $attempts++; } $this->statusmsg = "Performed maximum no of attempts"; $this->status = 4; return $inputValue; //bombed out on attempts limit } } ?>
|