3

I would like to apologize in advance for the long post but I wanted to give you a clear picture of my problem. Any help is appreciated.

I have an array called $data with the following format:

enter image description here

... ...

Given an arbitrary start date, I need to search the array for matching dates:

//$holidays is an array with dates of public holidays which are not considered business days
$search_results = array();
$minDate = -10;
$maxDate = 100;
$start_date = "2015-02-25";

echo "Before loop: " . xdebug_time_index() . "<br>";

for ($i=$minDate; $i<=$maxDate; $i++) {
    if (in_array_r(getBusinessDay(new DateTime($start_date), $holidays, $i), $data)){
        $a_date = getBusinessDay(new DateTime($start_date), $holidays, $i);
        $a_key = array_search($a_date, array_column($data, "date"));
        $search_results[]=array($i, $data[$a_key]["data"]);
    }
}

echo "After loop: " . xdebug_time_index() . "<br>";

var_dump($search_results);

However, this code snippet, which might run 10-15 times as the page loads, takes a long time to execute (at least 6 seconds on bigger arrays) each turn:

enter image description here

Could you please help me understand which part of code is causing this delay and how could I possibly speed up this process?

Thank you in advance for your help.

Here are the functions used in the code snippet:

function getBusinessDay($startdate, $holidays, $days) {

    $calculator = new BusinessDaysCalculator($startdate, $holidays, [BusinessDaysCalculator::SATURDAY, BusinessDaysCalculator::SUNDAY]);
    $calculator->addBusinessDays($days);
    $result = $calculator->getDate()->format('Y-m-d');
    unset($calculator);

    return $result;
}

function in_array_r($needle, $haystack, $strict = false) {
    foreach ($haystack as $item) {
        if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && in_array_r($needle, $item, $strict))) {
            return true;
        }
    }
    return false;
}

And the calculator which returns the next business day (skips weekends and any dates in the $holidays array):

class BusinessDaysCalculator {

    const MONDAY    = 1;
    const TUESDAY   = 2;
    const WEDNESDAY = 3;
    const THURSDAY  = 4;
    const FRIDAY    = 5;
    const SATURDAY  = 6;
    const SUNDAY    = 7;

    /**
     * @param DateTime   $startDate       Date to start calculations from
     * @param DateTime[] $holidays        Array of holidays, holidays are no considered business days.
     * @param int[]      $nonBusinessDays Array of days of the week which are not business days.
     */
    public function __construct(DateTime $startDate, array $holidays, array $nonBusinessDays) {
        $this->date = $startDate;
        $this->holidays = $holidays;
        $this->nonBusinessDays = $nonBusinessDays;
    }

    public function addBusinessDays($howManyDays) {
        $i = 0;
        while ($i < abs($howManyDays)) {
            if ($howManyDays < 0) {
                $this->date->modify("-1 day");
            } else {
                $this->date->modify("+1 day");
            }
            if ($this->isBusinessDay($this->date)) {
                $i++;
            }
        }
    }

    public function getDate() {
        return $this->date;
    }

    private function isBusinessDay(DateTime $date) {
        if (in_array((int)$date->format('N'), $this->nonBusinessDays)) {
            return false; //Date is a nonBusinessDay.
        }
        foreach ($this->holidays as $day) {
            if ($date->format('Y-m-d') == $day->format('Y-m-d')) {
                return false; //Date is a holiday.
            }
        }
        return true; //Date is a business day.
    }
}

UPDATE 1: I updated the structure of the $data array to

enter image description here

and the loop to:

for ($i=$minDate; $i <= $maxDate; $i++) {
        $day = getBusinessDay(new DateTime($start_date), $holidays, $i);
        if (array_key_exists($day, $data)) {
            $search_results[]=array($i, $data[$day]);
        }
}

The times improved only slightly:

enter image description here

Is array_key_exists the cause of the delay?

UPDATE 2: This is the $holidays array (It's static, always the same):

enter image description here

8
  • Is the date column unique in your data ? Commented Mar 11, 2015 at 10:25
  • Yes, the same date does not appear twice in the $data array. Commented Mar 11, 2015 at 10:27
  • array_filter will probably be faster Commented Mar 11, 2015 at 10:53
  • Profiling PHP Scripts. You can use KCacheGrind on *nix, or WinCacheGrind on Windows, to read the profiling file. Commented Mar 11, 2015 at 11:44
  • Change $i++ to ++$i, this would reduce the time a little. Commented Mar 11, 2015 at 11:59

1 Answer 1

2

Accoring to your comment, the date column contains unique values. Since you're only filtering on the date column, it's a lot more efficient to index the array via the date column, so you should restructure your data like this:

$data = array(
    '2015-02-19' => 1.35625,
    '2015-02-20' => 1.4015,
    '2015-02-23' => 0.9095,
    '2015-02-24' => 1.0635,
    '2015-02-25' => 1.08775,
    '2015-02-26' => 0.947,
    /* ... */
)

in_array needs to loop through the whole array an element corresponding to one date, which can be slow for large arrays. Using this structure, you can get the data immediately by accessing the data with $data[$date].

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the answer @benoit . The times improved but slightly( 1/2 second). Do you think the array_key_exists (see update in original post) is slowing it down again?
On my computer the loop takes no more than 0.1 seconds, and this time isn't dependent on the array size (I tried it with one million elements). Can you post sample data for the $holidays variable? My test was done using an empty array.
Thanks for the help. Please see update 2 in the original post for the $holidays array. Do you think I should have the dates as strings instead of objects?
If I set the value of $holidays to an array of 100 DateTime objects, the loop doesn't take 0.1 seconds anymore, but 3 seconds. Using strings instead of objects will help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.