How to click the next date / time on an array that matches the current date / time in the array?

What I still have: Testing example

$dates[] = array("date" => "2016-02-18 02:00:00", "duration" => "600"); // 10 mins $dates[] = array("date" => "2016-02-18 02:05:00", "duration" => "300"); // 5 mins $dates[] = array("date" => "2016-02-18 02:10:00", "duration" => "600"); $dates[] = array("date" => "2016-02-18 02:15:00", "duration" => "300"); $dates[] = array("date" => "2016-02-18 02:20:00", "duration" => "600"); $dates[] = array("date" => "2016-02-18 02:25:00", "duration" => "300"); $dates[] = array("date" => "2016-02-18 02:30:00", "duration" => "600"); $alreadyChosenDates[] = array("date" => "2016-02-18 02:05:00", "duration" => "300"); // 10 mins function returnClosestTime($alreadyChosenDates, $dates){ // Set an array called $closestTime that has the time difference and the key $closestTime = [null, null]; // Check each element in array foreach($dates as $key => $date){ foreach($alreadyChosenDates as $chosenDates){ // Calculate difference between already chosen dates array and the dates array $diff = (strtotime($chosenDates["date"]) + $chosenDates["duration"]) - strtotime($date["date"]); if($diff < 0) $diff = $diff * -1; // If $closestTime is empty, populate it if($closestTime[0] === null) $closestTime = [$diff, $key]; // If $closestTime isn't empty and the current date time difference // is smaller, populate $closestTime with the time difference and key else if($diff < $closestTime[0]) $closestTime = [$diff, $key]; } } return $dates[$closestTime[1]]; } $alreadyChosenDates[] = returnClosestTime($alreadyChosenDates, $dates); echo "<pre>"; print_r($alreadyChosenDates); echo "</pre>"; 

I am looking for help to adapt my current code so that it goes through the $dates array loop, collecting the earliest moments, however, the time should be able to match the same thing. This should always work with the already selected $alreadyChosenDates . In my code example, I have 2016-02-18 02:05:00 with a duration of 300 .

Expected result based on the above code example:

 // Already chosen date..... Array ( [0] => Array ( [date] => 2016-02-18 02:05:00 [duration] => 300 ) ) // After first loop Array ( [0] => Array ( [date] => 2016-02-18 02:05:00 [duration] => 300 ) [1] => Array ( [date] => 2016-02-18 02:10:00 [duration] => 600 ) ) // Next loop Array ( [0] => Array ( [date] => 2016-02-18 02:05:00 [duration] => 300 ) [1] => Array ( [date] => 2016-02-18 02:10:00 [duration] => 600 ) [2] => Array ( [date] => 2016-02-18 02:20:00 [duration] => 600 ) ) // Next loop Array ( [0] => Array ( [date] => 2016-02-18 02:05:00 [duration] => 300 ) [1] => Array ( [date] => 2016-02-18 02:10:00 [duration] => 600 ) [2] => Array ( [date] => 2016-02-18 02:20:00 [duration] => 600 ) [3] => Array ( [date] => 2016-02-18 02:30:00 [duration] => 600 ) ) 

Another example with different start times:

 // Already chosen date..... Array ( [0] => Array ( [date] => 2016-02-18 02:25:00 [duration] => 300 ) ) // After first loop Array ( [0] => Array ( [date] => 2016-02-18 02:25:00 [duration] => 300 ) [1] => Array ( [date] => 2016-02-18 02:30:00 [duration] => 600 ) ) // Next loop Array ( [0] => Array ( [date] => 2016-02-18 02:25:00 [duration] => 300 ) [1] => Array ( [date] => 2016-02-18 02:30:00 [duration] => 600 ) [2] => Array ( [date] => 2016-02-18 02:15:00 [duration] => 300 ) ) // Next loop Array ( [0] => Array ( [date] => 2016-02-18 02:25:00 [duration] => 300 ) [1] => Array ( [date] => 2016-02-18 02:30:00 [duration] => 600 ) [2] => Array ( [date] => 2016-02-18 02:15:00 [duration] => 300 ) [3] => Array ( [date] => 2016-02-18 02:05:00 [duration] => 300 ) ) 
+6
source share
1 answer

Requirements

Input:
1) taking into account the list of "selected" dates 2) List of candidate dates

Conclusion: 1) "The final list of" non-overlapping "dates

Where:

a) The first "selected" data is the start date, that is, all dates of the candidate must be indicated or after this date.

b) No date ranges should overlap.

Update 1 - Providing Setter and Process Edge Cases

1) setter provided:

 `setChosen(array $chosenDates)` `setCandidates(array $candidateDates)` 

2) Error margins of missing inputs checked for.

3) Passing arrays using constructor is optional.

Update 2 - Search for a list of optimal non-overlapping dates in a date range.

Demo: https://eval.in/678371

Class source : http://pastebin.com/K81rfytB

  • It finds a list by searching for brute force all dates within a given date range.

todo: convert "brute force search" into "dynamic programming"; adding "memoization". This should not be difficult to do, since a "decision tree" is currently being used.

I will update this answer with instructions later. For now, see the "demo" link above.

Original answer

Demonstration:

Explanation (or how I thought about it)

If lists are sorted by start date, itโ€™s pretty easy to talk about a list of dates.

a) The first start date after the โ€œselected startโ€ date should be the closest.

I can immediately determine if the next date will overlaps with the ones already selected.

So sorting lists is useful.

To make code that checks for overlap, I decided to convert the candidate dates into a standard format that includes the "range" or window each "candidate" as "seconds of an era (unix timestamp)." Does this make the tests clearer?

The withdrawal must not contain matching candidate dates.

This is what the class provides.

class ( ScheduleList ) that does all the work

 /* --------------------------------------------------------------------------------- * Class that does all the work... */ /* * Requirements: * * Input: * 1) given a list of 'chosen' dates * 2) A list of 'candidate' dates * * Output: * 1) A 'final list of 'none-overlapping' dates * * Where: * a) The first 'chosen' data is a 'start' date * ie All candidate dates must be on or after this date. * * b) No date ranges must ovevlap. */ class ScheduleList { /** * A list of all the dates that: * 1) After the 'chosen' start date * 2) Do not overlap with any 'chosen' date * * @var array $candidates */ private $candidates = array(); /** * Any date record we didn't use. * * @var array $unused */ public $unused = array(); /** * List of dates that must be included in the 'final' list. * The earliest date is assumed to be a start date and everything must be later. * * @var array $chosen */ private $chosen = array(); /** * Ordered list of `none overlapping' dates from the chosen and candidates * * @var array $final */ public $final = array(); /** * These are the date lists. * They will be converted, sorted and filters as required. * * @param array $chosenDates * @param array $candidateDates * @return void */ public function __construct(array $chosenDates = array(), array $candidateDates = array()) { if (!empty($chosenDates)) { $this->setChosen($chosenDates); } if (!empty($candidateDates)) { $this->setCandidates($candidateDates); } } /** * Convert chosen dates to date range and sort them * * @param array $chosenDates */ public function setChosen(array $chosenDates) { // convert and sort foreach ($chosenDates as $when) { $this->chosen[] = $this->makeDateRange($when); } if (count($this->chosen) > 1) { // sort them so we can easily compare against them usort($this->chosen, function ($when1, $when2) { return $when1['startTime'] - $when2['startTime']; }); } } /** * setter for candidates - will convert to date range * * @param array $candidateDates * * @return void; */ public function setCandidates(array $candidateDates) { // convert, sort and filter the candidates $this->convertCandidates($candidateDates); } /** * Add the candidates to the final list * * Known conditions: * o Both lists are in start date order * o No candidates overlap with any chosen date * o The candidates may overlap with each other - Hmm... need to check... * * Note: The '$this->isOverlapsAny' method - as it is used a lot will be expensive (O(n^2)) * I can think of ways to reduce that - will happen when it is refactored ;-/ * * @return array */ public function generateList() { if (empty($this->chosen) && empty($this->candidates)) { throw new \Exception('Generate Schedule: no input provided: ', 500); } $this->final = $this->chosen; // first candidate MUST be the closest to the first chosen date due to sorting. $this->final[] = array_shift($this->candidates); // move it to the final list // Add the remaining candidates checking for overlaps as we do so... foreach ($this->candidates as $candidate) { if ($this->isOverlapAny($candidate, $this->final)) { $this->unused[] = $candidate; } else { $this->final[] = $candidate; } } // sort final by start times - makes it easy to reason about usort($this->final, function ($when1, $when2) { return $when1['startTime'] - $when2['startTime']; }); return $this->final; } /** * Convert each date to a dateRange that is easier to check and display * * o Check each candidate date for ovelapping with any of the 'chosen dates' * o Check if before first chosen start data. */ public function convertCandidates(array $candidateDates) { foreach ($candidateDates as $idx => $when) { $candidate = $this->makeDateRange($when); // overlaps with any chosen then ignore it if ($this->isOverlapAny($candidate, $this->chosen)) { // ignore it $this->unused[] = $candidate; // record failed ones so easy to check continue; } // ignore if before first chosen start time if (!empty($this->chosen) && $candidate['endTime'] <= $this->chosen[0]['startTime']) { $this->unused[] = $candidate; // record failed ones so easy to check continue; } $this->candidates[] = $candidate; } // sort candidates by start times - makes it easy to reason about usort($this->candidates, function ($when1, $when2) { return $when1['startTime'] - $when2['startTime']; }); } /** * Convert to UNIX timestamp as seconds will make the calculations easier * * The result has: * 1) the time as a date object - I can do calculations / format it whatever * 2) startTime as epoch seconds * 3) endTime as epoch seconds * * @param array $when * * @return array */ public function makeDateRange(array $when) { $dt = \DateTime::createFromFormat('Ymd H:i:s', $when['date']); $result = array(); $result['when'] = $dt; $result['duration'] = (int) $when['duration']; $result['startTime'] = (int) $dt->format('U'); $result['endTime'] = (int) $result['startTime'] + $when['duration']; return $result; } /** * if start is after other end OR end is before other start then they don't overlap * * Easiest way is to check that they don't overlap and reverse the result */ public function isOverlap($when1, $when2) { return ! ( $when1['endTime'] <= $when2['startTime'] || $when1['startTime'] >= $when2['endTime']); } /** * Check if candidate overlaps any of the dates in the list * * @param array $candidate * @param array $whenList -- list of non-overlapping dates * * @return boolean true if overlaps */ function isOverlapAny($candidate, $whenList) { foreach ($whenList as $when) { if ($this->isOverlap($when, $candidate)) { // ignore it return true; } } return false; } /** * Show a date formatted for debugging purposes * * @param array $when * @return void */ public function displayWhen(array $when) { echo PHP_EOL, 'date: ', $when['when']->format('Ymd H:i:s'), ' len: ', $when['duration'], ' end: ', date('Ymd H:i:s', $when['endTime']), ' start: ', $when['startTime'], ' end: ', $when['endTime']; } /* * `Getters` so you can see what happened */ public function getChosen() { return $this->chosen; } public function getUnused() { return $this->unused; } public function getCandidates() { return $this->candidates; } public function getFinal() { return $this->final; } /** * properties - for those of us that like them */ public function __get($name) { if (property_exists($this, $name)) { return $this->$name; } return null; } } 

Run it

  • Create an instance of ScheduleList by passing the chosen array and the "date" array.
  • Method generateList(); will return a "final" array with non-overlapping dates.

the code:

 $datesListGenerator = new ScheduleList($alreadyChosenDates, $dates); $final = $datesListGenerator->generateList(); 

Update: run using setters:

 $datesListGenerator = new ScheduleList(); $datesListGenerator->setChosen($alreadyChosenDates); $datesListGenerator->setCandidates($dates); 

Various outputs

makeDakeRange is now a public function:

 var_dump('public makeDateRange : ', $datesListGenerator->makeDateRange(array('date' => '2016-04-01 08:09:10', 'duration' => 1))); array (size=4) 'when' => object(DateTime)[83] public 'date' => string '2016-04-01 08:09:10' (length=19) public 'timezone_type' => int 3 public 'timezone' => string 'UTC' (length=3) 'duration' => int 1 'startTime' => int 1459498150 'endTime' => int 1459498151 

Final (non-overlapping with any input candidate)

the code:

 echo PHP_EOL, PHP_EOL, 'Final List'; foreach ($final as $when) { $datesListGenerator->displayWhen($when); } 

exit:

 Final List date: 2016-02-18 02:05:00 len: 300 end: 2016-02-18 02:10:00 start: 1455761100 end: 1455761400 date: 2016-02-18 02:10:00 len: 600 end: 2016-02-18 02:20:00 start: 1455761400 end: 1455762000 date: 2016-02-18 02:20:00 len: 600 end: 2016-02-18 02:30:00 start: 1455762000 end: 1455762600 date: 2016-02-18 02:30:00 len: 600 end: 2016-02-18 02:40:00 start: 1455762600 end: 1455763200 

Not used (before starting or shutting down)

the code:

 echo PHP_EOL, PHP_EOL, 'Unused List'; echo PHP_EOL, 'will be before first Chosen or Overlaps with one in the final list...', PHP_EOL; foreach ($datesListGenerator->getUnused() as $when) { $datesListGenerator->displayWhen($when); } 

Output:

 Unused List will be before first Chosen or Overlaps with one in the final list... date: 2016-02-18 02:00:00 len: 600 end: 2016-02-18 02:10:00 start: 1455760800 end: 1455761400 date: 2016-02-18 02:05:00 len: 300 end: 2016-02-18 02:10:00 start: 1455761100 end: 1455761400 date: 2016-02-18 02:15:00 len: 300 end: 2016-02-18 02:20:00 start: 1455761700 end: 1455762000 date: 2016-02-18 02:25:00 len: 300 end: 2016-02-18 02:30:00 start: 1455762300 end: 1455762600 
+1
source

Source: https://habr.com/ru/post/1012175/


All Articles