<?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 
    } 
} 
?> 
 
 |