简体   繁体   中英

Get closest date/time from array based on already chosen date/times

Using one already chosen date: Test 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:25:00", "duration" => "300");
$dates[] = array("date" => "2016-02-18 02:30:00", "duration" => "600");

$alreadyChosenDates[] = array("date" => "2016-02-18 02:10:30", "duration" => "600"); // 10 mins
//$alreadyChosenDates[] = array("date" => "2016-02-18 02:05:00", "duration" => "300"); // 5 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's time difference
            // is smaller, populate $closestTime with the time difference and key
            else if($diff < $closestTime[0]) $closestTime = [$diff, $key];
        }
    }
    return $dates[$closestTime[1]];
}

echo "<pre>";
    print_r(returnClosestTime($alreadyChosenDates, $dates));
echo "</pre>";

Output:

Array
(
    [date] => 2016-02-18 02:25:00
    [duration] => 300
)

In the above example i'm using only one already chosen date but i'm looking for some help on how i'd use two already chosen dates?

so instead of just this:

$alreadyChosenDates[] = array("date" => "2016-02-18 02:10:30", "duration" => "600"); // 10 mins

It would be this

$alreadyChosenDates[] = array("date" => "2016-02-18 02:10:30", "duration" => "600"); // 10 mins
$alreadyChosenDates[] = array("date" => "2016-02-18 02:05:00", "duration" => "300"); // 5 mins

and the closest date/time would be the closest to either 2016-02-18 02:10:30 or 2016-02-18 02:05:00 but it would also be good to see if the closest time would be in the middle of 2016-02-18 02:10:30 and 2016-02-18 02:05:00 .

How would I go about doing this?

I don't know if you would like the OOP approach, but you can use this class:

class ClosestIndicationFinder
{
    protected $indications;
    protected $chosenIndications;

    public function __construct(
        array $indications,
        array $chosenIndications = null
    ) {
        $this->indications = $indications;
        $this->chosenIndications = $chosenIndications;
    }

    public function setChosenIndications(array $chosenIndications)
    {
        $this->chosenIndications = $chosenIndications;

        return $this;
    }

    public function addChosenIndication($indication)
    {
        $this->chosenIndications[] = $indication;

        return $this;
    }

    public function findClosestIndication()
    {
        $this->findAverageChosenIndicationTimestamp();
        $this->findAbsoluteIndicationDifferences();

        return $this->findClosestsIndicationBasedOnDiffs();
    }

    protected $averageChosenTimestamp;
    protected function findAverageChosenIndicationTimestamp()
    {
        $sum = array_reduce(
            $this->chosenIndications,
            function ($sum, $indication) {
                return $sum + $this->calculateIndicationEndTimestamp($indication);
            },
            0
        );

        $this->averageChosenTimestamp = (int) $sum / count($this->chosenIndications);
    }

    protected $diffs;
    protected function findAbsoluteIndicationDifferences()
    {
        $this->diffs = array_map(function ($indication) {
            return abs($this->calculateIndicationsDifference(
                $indication,
                $this->averageChosenTimestamp
            ));
        }, $this->indications);
    }

    protected function calculateIndicationsDifference($indication1, $indication2)
    {
        $timestamp1 = is_array($indication1) 
            ? $this->calculateIndicationBeginningTimestamp($indication1)
            : $indication1;

        $timestamp2 = is_array($indication2) 
            ? $this->calculateIndicationEndTimestamp($indication2) 
            : $indication2;

        return $timestamp1 - $timestamp2;
    }

    protected function calculateIndicationBeginningTimestamp(array $indication)
    {
        return strtotime($indication['date']);
    }

    protected function calculateIndicationEndTimestamp(array $indication)
    {
        return strtotime($indication['date']) + $indication['duration'];
    }

    protected function findClosestsIndicationBasedOnDiffs()
    {
        $closestIndicationIndex = array_search(min($this->diffs), $this->diffs);

        if (!isset($this->indications[$closestIndicationIndex])) {
            return null;
        }

        return $this->indications[$closestIndicationIndex];
    }
}

I have tried to make methods self-describing, so there are no comments in the code. The OOP approach makes it possible to tweak the logic by extending the class and rewriting only those methods which behavior should be changed . For example, I saw your other question, where you want to add a newly chosen date to the chosen array. There is also a method for this in the class, however, I would remove the chosen date from initial dates array. But as I know only the end task it is hard for me to make the solution closely fit the initial task.

As for multiple chosen times. You have written "but it would also be good to see if the closest time would be in the middle of" . Correct me if I am wrong but, if you want the date, that resides in the middle of the two dates closest to chosen dates, you can calculate the average of chosen dates and reduce the task to find only one closest date.

Once again if you can tell what are you trying to do, the solution can be made more accurately.

By the way, here is working demo .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM