Advertisement
rfv123

W42270362 - TimeSlot Lookup Class (binary search)

Mar 7th, 2017
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 6.55 KB | None | 0 0
  1. <?php // http://stackoverflow.com/questions/42270362/optimal-algorithm-to-get-the-next-available-time-slot-in-an-associative-array-co
  2.  
  3.  
  4. class TimeRanges {
  5.  
  6.   /**
  7.    * need this for binary search
  8.    */    
  9.   private $startTimes = array();
  10.   private $lastSlotIdx = 0;
  11.    
  12.   // outputs so you don't need to run the search again
  13.    
  14.   private $timeNow = 0;
  15.   private $outSlots = array();  
  16.  
  17.   private $dateWhen = null; // used to convert seconds to different formats
  18.    
  19.   /**
  20.    * @param array The timeSlots that will be searched
  21.    */    
  22.   public function __construct(array $timeSlots) {
  23.       $this->buildAllstartTimes($timeSlots);
  24.       $this->dateWhen = new \DateTime();
  25.   }
  26.  
  27.   /**
  28.    * Create a list of real and 'virtual' timeslots.  
  29.    * i.e. All gaps between the real timeslots we treat as a timeslot
  30.    *      when searching for a matching timeslot  
  31.    */    
  32.   protected function buildAllStartTimes($timeSlots) {
  33.      
  34.       $this->startTimes = array();
  35.  
  36.       $iter = new ArrayIterator($timeSlots);
  37.      
  38.       $prvEndTime = PHP_INT_MAX; // needed to identify 'gaps'
  39.          
  40.       while ($iter->valid()) {
  41.  
  42.           $curStartTime = $this->asSeconds($iter->key());   // seconds
  43.           $curEndTime = $this->asSeconds($iter->current()); // so is easy to check
  44.          
  45.           if ($curStartTime > $prvEndTime) { // make a 'gap' timeslot
  46.               $this->startTimes[]  = ['s' => $prvEndTime + 1,
  47.                                       'e' => $curStartTime,
  48.                                       'real' => false];
  49.               $prvEndTime = $curStartTime;
  50.          
  51.           } else { // is real
  52.               $this->startTimes[] = ['s' => $curStartTime,
  53.                                      'e' => $curEndTime,
  54.                                      'real' => true];
  55.               $prvEndTime = $curEndTime;
  56.               $iter->next();          
  57.           }          
  58.       }
  59.       $this->lastSlotIdx = count($this->startTimes) - 1;
  60.   }
  61.  
  62.   /**
  63.    *  Search the array using a modifield binary search
  64.    *  
  65.    *  This can return zero, one or two 'real' timeslots:      
  66.    *  
  67.    *  o timeNow before first timeslot -- return first timeslot
  68.    *  o timeNow after last timeslot   -- return empty array
  69.    *  o timeNow on a 'gap'            -- return one timeslot (the next one)    
  70.    *      
  71.    * @param string current time
  72.    */        
  73.   public function findTimeSlots($timeNow) {
  74.       $this->timeNow = $this->asSeconds($timeNow);
  75.       $startSlotIdx = $this->searchStartTimes($this->timeNow, 0, $this->lastSlotIdx);      
  76.  
  77.        
  78.       $returnedSlots = 1;
  79.       if ($startSlotIdx > $this->lastSlotIdx ) {
  80.           $returnedSlots = 0;
  81.      
  82.       } elseif (    isset($this->startTimes[$startSlotIdx])  
  83.                  && $this->startTimes[$startSlotIdx]['real']) {
  84.  
  85.           $returnedSlots = 2;
  86.       }
  87.  
  88.  
  89.       $out = array();  // need current and next real timeslots in the array    
  90.       for ($numSlots = $returnedSlots; $numSlots > 0; $numSlots--, $startSlotIdx++) {
  91.      
  92.           $startSlotIdx = $this->getNextRealTimeSlotIdx($startSlotIdx);
  93.  
  94.           if (    isset($this->startTimes[$startSlotIdx])
  95.                && $this->startTimes[$startSlotIdx]['real']) { // valid timeslot
  96.                
  97.               $out[] = ['s' => $this->asHHMMSS($this->startTimes[$startSlotIdx]['s']),
  98.                         'e' => $this->asHHMMSS($this->startTimes[$startSlotIdx]['e'])];
  99.           }
  100.       }      
  101.       $this->outSlots = $out;
  102.       return $out;
  103.   }
  104.  
  105.   /**
  106.    * find required timeslot using a nodified binary search on $startTimes
  107.    *      
  108.    * This can be used to find the next valid timeslot so we need to return
  109.    * various conditions:
  110.    *
  111.    *  o  -1           -- timeNow is before the first valid timeslot
  112.    *  o  0 .. n       -- timeslot (real or not) that is within the array
  113.    *  o  PHP_MAX_INT  -- timeNow is after the end of the array        
  114.    *      
  115.    * @param string   current time  
  116.    * @param integer  startRange  to  timeslots to search  
  117.    * @param integer  endRange  of timeslots  to search
  118.    * @return integer index
  119.    */
  120.   protected function searchStartTimes($timeNow, $rangeStart, $rangeEnd) {  
  121.  
  122. //      \Kint::dump(__METHOD__.__LINE__, $timeNow, $this->asHHMMSS($timeNow), $rangeStart, $rangeEnd);
  123.  
  124.       while ($rangeStart <= $rangeEnd) {
  125.  
  126.           // mid point of the range
  127.           $currentIdx = (int) ($rangeStart + ($rangeEnd - $rangeStart) / 2); // see floor()
  128.  
  129.           if ($timeNow >= $this->startTimes[$currentIdx]['s']) { // possible start key
  130.          
  131.               // check is within the end of the timeSlot
  132.               if ($timeNow <= $this->startTimes[$currentIdx]['e']) { // found it
  133.                   return $currentIdx; // finished
  134.               }  
  135.      
  136.               // not found correct time slot yet - search upper range
  137.               if ($timeNow >= $this->startTimes[$currentIdx]['s']) {
  138.                   $rangeStart = $currentIdx + 1;                  
  139.               }
  140.        
  141.           } else {
  142.             // timeNow is after current StartTime  - go find a lower range
  143.               $rangeEnd = $currentIdx - 1;                            
  144.           }
  145.       } // endwhile
  146.      
  147.       return $rangeEnd < 0 ? -1 : PHP_INT_MAX; // not in the array
  148.   }
  149.  
  150.   /**
  151.    * Find the next 'real' timeSlot in the array
  152.    *
  153.    *  The input can be:
  154.    *     o Before the first time in the array
  155.    *     o On a timeSlot in the array
  156.    *     o On a 'gap' in the array
  157.    *
  158.    *  PHP_INT_MAX indicates not in the array.  
  159.    *
  160.    * @param  integer Current timeslot position
  161.    * @return integer    
  162.    */
  163.   protected function getNextRealTimeSlotIdx($startSlotIdx) {
  164.  
  165.       while (    $startSlotIdx < 0  
  166.              || ( isset($this->startTimes[$startSlotIdx])
  167.                   && !$this->startTimes[$startSlotIdx]['real'])) {
  168.      
  169.           $startSlotIdx++;      
  170.       }
  171.      
  172.       if (!isset($this->startTimes[$startSlotIdx])) {
  173.           $startSlotIdx = PHP_INT_MAX;
  174.       }
  175.      
  176.       return $startSlotIdx;
  177.   }
  178.  
  179.   /**
  180.    *  get any information from the class. It is maintained correctly
  181.    */    
  182.   public function __get($name) {
  183.      if (property_exists($this, $name)) {
  184.         return $this->$name;
  185.      }
  186.      return null;
  187.   }
  188.  
  189.   public function asSeconds($hhmmss) {
  190.       return strtotime($hhmmss);
  191.   }
  192.  
  193.   public function asHHMMSS($seconds) {
  194.       $this->dateWhen->setTimestamp($seconds);
  195.       return $this->dateWhen->format('H:i:s');
  196.   }
  197.  
  198. } // end class
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement