Performance with time related algorithm - php

I have a function that take 2 arrays ($schedule, $remove), both are arrays of days with time inside, it will remove time from the schedule .
Now this function is working fine if I have between 1 & 20 user it takes 2-4 seconds to generate the calendar which is fine but when having 20+ user with a lot of schedules entries it goes to 15+ seconds.
I'm working with CodeIgniter and I have this function in a helper where it's called a lot.
So I wanted to know if you guys can see any better way to deal with my problem or adjustments that I make to my algorithm to make it faster.
Note:
In my code below, the big problem I see is the recursive call and the break of the loop every time I modify the structure.
I loop on both arrays and do test to see if the absence is inside/overlap/equal/outside of the availability and then recall the function if the structure was modified if not return the final structure.
Note 2 :
On local the Apache crash because the recursive function sometime is called more than 100 times .
Here is the code I have :
function removeSessionsFromSchedule($schedule, $remove) {
$modified = false;
if (is_array($schedule) && count($schedule) > 0 && is_array($remove) && count($remove) > 0 && checkArrayEmpty($remove)) {
// Minimise the iterations
$remove = minimiseRemoveSchedule($remove);
foreach ($schedule as $s => $dispo) {
if ($modified) {
break;
}
$pos = 0;
$countdispo = count($dispo);
foreach ($dispo as $d) {
$abs = isset($remove[$s]) ? $remove[$s] :null;
$counter = 0;
// availability start/end
$dis_s = strtotime($d['heure_debut']);
$dis_e = strtotime($d['heure_fin']);
if (is_array($abs) && count($abs) > 0) {
foreach ($abs as $a) {
// absence start/end
$abs_s = strtotime($a['heure_debut']);
$abs_e = strtotime($a['heure_fin']);
// Tests to see the if there is overlap between absence and availability
// (2) [a_s]---[ds - de]---[a_e]
if ($abs_s <= $dis_s && $abs_e >= $dis_e) {
// delete availability
unset($schedule[$s][$pos]);
$modified = true;
break;
}
// (7)[as == ds] && [ae < de]
else if ($abs_s == $dis_s && $abs_e < $dis_e) {
unset($schedule[$s][$pos]);
$schedule[$s][$pos] = $d;
$schedule[$s][$pos]['heure_debut'] = date("H:i", $abs_e);
$schedule[$s][$pos]['heure_fin'] = date("H:i", $dis_e);
$modified = true;
break;
}
// (6) [ds -de] --- [as ae] return dispo as is
else if ($abs_s >= $dis_e) {
unset($schedule[$s][$pos]);
$schedule[$s][$pos] = $d;
$modified ?: false;
}
// (5)[as ae] [ds -de] --- return dispo as is
else if ($abs_e <= $dis_s) {
unset($schedule[$s][$pos]);
$schedule[$s][$pos] = $d;
$modified ?: false;
}
// (1)[ds] --- [as] --- [ae] --- [de] (duplicate dis with new times)
else if ($abs_s > $dis_s && $abs_e <= $dis_e) {
// new times as : // s1 = ds-as && s2 = ae-de
unset($schedule[$s][$pos]);
$schedule[$s][$pos] = $d;
$schedule[$s][$pos + 1] = $d;
$schedule[$s][$pos]['heure_debut'] = date("H:i", $dis_s);
$schedule[$s][$pos]['heure_fin'] = date("H:i", $abs_s);
$schedule[$s][$pos + 1]['heure_debut'] = date("H:i", $abs_e);
$schedule[$s][$pos + 1]['heure_fin'] = date("H:i", $dis_e);
// a revoir si ca ne cause pas d'autre problem qu'on fasse pos++ ...
$pos++;
$modified = true;
break;
}
// (3)[as] -- [ds] --- [ae] -- [de]
else if ($abs_s < $dis_s && $abs_e < $dis_e) {
unset($schedule[$s][$pos]);
$schedule[$s][$pos] = $d;
$schedule[$s][$pos]['heure_debut'] = date("H:i", $abs_e);
$schedule[$s][$pos]['heure_fin'] = date("H:i", $dis_e);
$modified = true;
break;
}
// (4) [ds]---[as]--- [de]--- [ae]
else if ($abs_s > $dis_s && $abs_s < $dis_e && $abs_e > $dis_e) {
unset($schedule[$s][$pos]);
$schedule[$s][$pos] = $d;
$schedule[$s][$pos]['heure_debut'] = date("H:i", $dis_s);
$schedule[$s][$pos]['heure_fin'] = date("H:i", $abs_s);
$modified = true;
break;
} else {
$modified ?: false;
}
}
// if($modified == true) { break;}
} else {
$modified = false;
}
$pos++;
}
}
} else {
$modified = false;
}
if ($modified) {
$schedule = resetIndexes($schedule);
$schedule = sortByTime($schedule);
$schedule = removeSessionsFromSchedule($schedule, $remove);
}
return $schedule;
}
Related Helpers
function checkArrayEmpty($array) {
if(is_array($array) && !empty($array)) {
foreach($array as $arr) {
if(is_array($arr) && !empty($arr)) {
return true;
}
}
}
return false;
}
function subval_sort_by_time($a, $subkey) {
if (is_array($a) && count($a) > 0) {
foreach ($a as $k => $v) {
$b[$k] = strtotime($v[$subkey]);
}
asort($b);
foreach ($b as $key => $val) {
$c[] = $a[$key];
}
return $c;
}
else
return $a;
}
// Reset Index function
function resetIndexes($array) {
$new = array();
foreach($array as $date => $arr) {
//$new[$date]= array_values($arr);
$new[$date]= array_merge(array(),$arr);
}
return $new;
}
// sort by time
function sortByTime($array) {
$sorted = array();
if(is_array($array) && !empty($array)){
foreach ($array as $s => $val) {
$sorted[$s] = subval_sort_by_time($val, 'heure_debut');
}
}
return $sorted;
}
function minimiseRemoveSchedule($array) {
$new = array();
foreach($array as $date => $arr) {
$i=0;
if(is_array($arr) && !empty($arr)) {
foreach($arr as $a) {
if(isset($new[$date][$i])) {
if($new[$date][$i]['heure_fin'] == $a['heure_debut']) {
$new[$date][$i]['heure_fin'] = $a['heure_fin'];
}
else {
$i++;
$new[$date][$i]['heure_debut'] = $a['heure_debut'];
$new[$date][$i]['heure_fin'] = $a['heure_fin'];
}
} else {
$new[$date][$i]['heure_debut'] = $a['heure_debut'];
$new[$date][$i]['heure_fin'] = $a['heure_fin'];
}
}
}
}
return $new;
}
Example of Array that I pass:
$schedule = Array(
'2012-11-12' => Array(),
'2012-11-13' => Array(),
'2012-11-14' => Array( 0 => Array("employe_id" => 8 , "heure_debut" => '16:00' ,"heure_fin" => '20:00' ,"date_seance" => 2012-11-14 , "jour_id" => 3)),
'2012-11-15' => Array(
0 => Array("employe_id" => 8 , "heure_debut" => '09:00' ,"heure_fin" => '15:00' ,"date_seance" => 2012-11-15 , "jour_id" => 4),
1 => Array("employe_id" => 8 , "heure_debut" => '16:00' ,"heure_fin" => '21:00' ,"date_seance" => 2012-11-15 , "jour_id" => 4)
),
'2012-11-16' => Array(),
'2012-11-17' => Array(),
'2012-11-18' => Array(),
'2012-11-19' => Array(0 => Array("employe_id" => 8 ,"heure_debut" => '10:00' ,"heure_fin" => '22:00' ,"date_seance" => 2012-11-19 ,"jour_id" => 1)),
'2012-11-20' => Array(
0 => Array("employe_id" => 8 ,"heure_debut" => '09:00' ,"heure_fin" => '15:00' ,"date_seance" => 2012-11-20 ,"jour_id" => 2),
1 => Array("employe_id" => 8 ,"heure_debut" => '16:00' ,"heure_fin" => '20:00' ,"date_seance" => 2012-11-20 ,"jour_id" => 2)
)
);
And for the second array:
$remove = array(
'2012-11-12' => Array(),
'2012-11-13' => Array(),
'2012-11-14' => Array(),
'2012-11-15' => Array(),
'2012-11-16' => Array(),
'2012-11-17' => Array(),
'2012-11-18' => Array(),
// in this example i only have 1 absence ... I could have N absences
'2012-11-19' => Array(0 => Array("employe_id" => 8 ,"date_debut" => 2012-11-19,"date_fin" => 2012-11-19 ,"heure_debut" => '12:00:00',"heure_fin" => '14:00:00')),
'2012-11-20' => Array(),
'2012-11-21' => Array()
);
The resulting array would be:
$result = array(
Array
(
[2012-11-12] => Array()
[2012-11-13] => Array()
// no change
[2012-11-14] => Array( [0] => Array("employe_id" => 8 , "heure_debut" => 16:00 ,"heure_fin" => 20:00 ,"date_seance" => 2012-11-14 , "jour_id" => 3))
// no change
[2012-11-15] => Array(
[0] => Array("employe_id" => 8 , "heure_debut" => 09:00 ,"heure_fin" => 15:00 ,"date_seance" => 2012-11-15 , "jour_id" => 4),
[1] => Array("employe_id" => 8 , "heure_debut" => 16:00 ,"heure_fin" => 21:00 ,"date_seance" => 2012-11-15 , "jour_id" => 4)
)
[2012-11-16] => Array()
[2012-11-17] => Array()
[2012-11-18] => Array()
// since absence from 12 to 14 and we had availability from 8 to 22 instead we will have 8->12 and 14->22
[2012-11-19] => Array(
[0] => Array("employe_id" => 8 ,"heure_debut" => 08:00 ,"heure_fin" => 12:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 1),
[1] => Array("employe_id" => 8 ,"heure_debut" => 14:00 ,"heure_fin" => 22:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 1)
)
// no changes since no absence during those time
[2012-11-20] => Array(
[0] => Array("employe_id" => 8 ,"heure_debut" => 09:00 ,"heure_fin" => 15:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 2),
[1] => Array("employe_id" => 8 ,"heure_debut" => 16:00 ,"heure_fin" => 20:00 ,"date_seance" => 2012-11-20 ,"jour_id" => 2)
)
)

I don't see why you need an exponential time recursion to execute this task. You can get away with an O(r * e^2) solution (where e is the average number of availabilities/removals per day, and r is size of removed times) via nested loop. Pseudocode below:
for removeday in remove:
define scheduleday := schedule[removeday.date]
if scheduleday not found:
continue
for removesegment in removeday:
define temparray := empty
for availsegment in scheduleday:
if availsegment.employeid != removesegment.employeid:
continue
if no overlap:
temparray.add(availsegment)
if partial overlap:
temparray.add(availsegment.split(removesegment))
scheduleday = temparray
schedule[removeday.date] := scheduleday
return schedule

The code below produces the same output for the given sample but I haven't tested all possible cases.
Working Demo
function removeSessionsFromScheduleHelper(&$schedule,&$remove) {
$change = false;
foreach($remove as $date => &$remove_ranges) {
if(empty($remove_ranges) || !isset($schedule[$date]))
continue;
foreach($remove_ranges as &$remove_range) {
foreach($schedule[$date] as $day_key => &$time) {
//start after finish, no overlap and because schedules are sorted
//next items in schedule loop will also not overlap
//break schedule loop & move to next remove iteration
if($time['heure_debut'] >= $remove_range['heure_fin'])
break;
//finish before start, no overlap
if($time['heure_fin'] <= $remove_range['heure_debut'])
continue;
//complete overlap, remove
if($time['heure_debut'] >= $remove_range['heure_debut']
&& $time['heure_fin'] <= $remove_range['heure_fin']) {
unset($schedule[$date][$day_key]);
continue;
}
//split into 2 ranges
if($time['heure_debut'] < $remove_range['heure_debut']) {
if($time['heure_fin'] > $remove_range['heure_fin']) {
$schedule[$date][] = array(
'heure_debut' => $remove_range['heure_fin'],
'heure_fin' => $time['heure_fin']
);
}
$change = true;
$time['heure_fin'] = $remove_range['heure_debut'];
continue;
}
if($time['heure_debut'] >= $remove_range['heure_debut']) {
$change = true;
$time['heure_debut'] = $remove_range['heure_fin'];
}
}
}
}
if($change) {
foreach($schedule as &$values) {
usort($values,'compare_schedule');
}
}
return $change;
}
function compare_schedule($a,$b) {
return strtotime($a['heure_debut']) - strtotime($b['heure_debut']);
}
function removeFromSchedule(&$schedule,$remove) {
foreach($remove as $k => &$v) {
foreach($v as $k2 => &$v2) {
$v2['heure_debut'] = substr($v2['heure_debut'],0,5);
$v2['heure_fin'] = substr($v2['heure_fin'],0,5);
}
}
while(removeSessionsFromScheduleHelper($schedule,$remove));
}
removeFromSchedule($schedule,$remove);
print_r($schedule);

If you don't want to add recursion to your function then you have to kind of convert it first to seconds of available schedule array matrix. Here the idea:
function scheduleToSecondsMatrix($value, $available=true){
if(!is_array($value) || empty($value))
return false;
$object = array();
foreach($value as $v) {
$s = strtotime('1970-01-01 ' . $v['heure_debut'] . (!$available ? ' +1 seconds' : '')); // ref. http://stackoverflow.com/questions/4605117/how-to-convert-a-hhmmss-string-to-seconds-with-php
$e = strtotime('1970-01-01 ' . $v['heure_fin'] . (!$available ? ' -1 seconds' : ''));
if($e < $s) continue; // logically end time should be greater than start time
while($s <= $e) {
// i use string as key as this result will be merged: http://php.net/manual/en/function.array-merge.php
$object["in_" . $s] = $available; // means in this seconds range is available
$s++;
}
}
return $object;
}
/**
* This function assume:
* - all parameters refer to only one employee
*/
function removeSessionsFromScheduleRev($schedule, $remove) {
if(!is_array($schedule) || !is_array($remove) || empty($schedule) || empty($remove)) return false;
foreach($schedule as $s => &$dispo){
if(empty($remove[$s]))
continue;
// convert the schedule to seconds array matrix, that's i call it :)
$seconds_available = scheduleToSecondsMatrix($dispo, true);
$seconds_not_available = scheduleToSecondsMatrix($remove[$s], false);
if( !$seconds_available || !$seconds_not_available ) continue; // nothing changed
$seconds_new = array_merge($seconds_available, $seconds_not_available);
$seconds_new = array_filter($seconds_new); // remove empty/false value
$new_time_schedule = array();
$last_seconds = 0;
$i=0;
foreach($seconds_new as $in_seconds => $val){
$in_seconds = intval(str_replace('in_', '', $in_seconds));
if($in_seconds > ($last_seconds+1)){
if(!empty($new_time_schedule)) $i++;
}
if(empty($new_time_schedule[$i]['start'])) $new_time_schedule[$i]['start'] = $in_seconds;
$new_time_schedule[$i]['end'] = $in_seconds;
$last_seconds = $in_seconds;
}
foreach($new_time_schedule as $idx => $val){
if($idx && empty($dispo[$idx])) $dispo[$idx] = $dispo[$idx-1];
$dispo[$idx]['heure_debut'] = date('H:i:s', $val['start']);
$dispo[$idx]['heure_fin'] = date('H:i:s', $val['end']);
}
}
return $schedule;
}
I haven't benchmark the performance yet so you may try this code on yours. I hope it works.

I think jma127 is on the right track with their pseudocode. Let me supplement their answer with some commentary.
Your basic structure is to loop through entries of $schedule, and then for each one, pull out the corresponding entry from $remove, and make some changes. As soon as a change happens, you break out of the loop, and start over again. The control structure you use to start over again is a recursive call. When you start over again, you loop again through all the entries of $schedule which you've already checked and don't need to change anymore.
Array $schedule and array $remove are related through shared subscripts. For a given index i, $remove[i] affects only $schedule[i] and no other part. If there is no entry $remove[i], then $schedule[i] is unchanged. Thus jma127 is right to restructure the loop to iterate first through entries of $remove, and have an inner code block to combine the entries of $remove[i] and $schedule[i]. No need for recursion. No need for repeatedly iterating over $schedule.
I believe this is the major reason your code becomes slow as the number of entries increases.
For a given day's entries in $remove and $schedule, the way you combine them is based on start times and end times. jma127 is right to point out that if you sort the day's entries by time (start time firstly, and end time secondly), then you can make a single pass through the two arrays, and end up with the correct result. No need for recursion or repeated looping.
I believe this is a secondary reason your code becomes slow.
Another thing I notice about your code is that you frequently put code inside a loop that isn't affected by the loop. It would be a tiny bit more efficient to put it outside the loop. For instance, your validity check for $remove and $schedule:
if (is_array($schedule) && count($schedule) > 0 \
&& is_array($remove) && count($remove) > 0)...
is repeated every time the routine is called recursively. You could instead move this check to an outer function, which calls the inner function, and the inner function won't need to check $remove and $schedule again:
function removeSessionsFromSchedule_outer($schedule, $remove) {
if ( is_array($schedule) && count($schedule) > 0
&& is_array($remove) && count($remove) > 0 ) {
$schedule = removeSessionsFromSchedule($schedule, $remove);
}
return $schedule;
}
Similarly,
foreach ($dispo as $d) {
if (isset($remove[$s])) {
$abs = $remove[$s];
} else
$abs = null;
// rest of loop....
}/*foreach*/
could be rewritten as:
if (isset($remove[$s])) {
$abs = $remove[$s];
} else
$abs = null;
foreach ($dispo as $d) {
// rest of loop....
}/*foreach*/
Another minor inefficiency is that your data structures don't contain the data in the format that you need. Instead of receiving a structure with data like:
[2012-11-14] => Array( [0] => Array(..."heure_debut" => 16:00 ...))
and each time during the loop, doing a data conversion like:
$abs_s = strtotime($a['heure_debut']);
How about having your upstream caller convert the data themselves:
["2012-11-14"] => Array([0]=>Array(..."heure_debut"=>strtotime("16:00") ...))
Another little detail is that you use syntax like 2012-11-14 and 16:00. PHP treats these as strings, but your code would be clearer if you put them in quotes to make it clear they are strings. See Why is $foo[bar] wrong? in PHP documenation Arrays.
I won't try to rewrite your code to make all these changes. I suspect you can figure that out yourself, looking at my comments and jma127's answer.

You have an availability schedule, implemented as a 2D array on day and entry number, and an absence schedule, implemented the same way, both sorted on time, and wish to update first using the second.
Both arrays are indexed the same way on their major dimension (using dates), so we can safely work on each of these rows without fear of modifying the rest of the arrays.
For a given day:
Within a day the simplest way to do it is to loop through all the $remove entries, and for each match on the employee_id, check the time and modify the schedule accordingly (something you already implemented, so we can reuse some of it). You want to keep the day schedule in order of time. The original arrays are well sorted, and if we store the modification in a new array in order creation, we won't have to sort it afterwards.
<?php
// create a schedule entry from template, with begin & end time
function schedule($tmpl, $beg, $end) {
$schedule = $tmpl;
$schedule['heure_debut'] = date("H:i", $beg);
$schedule['heure_fin'] = date("H:i", $end);
return $schedule;
}
// return one updated entry of a schedule day, based on an absence
function updateAvailability($d, $a){
// absence start/end
$dis_s = strtotime($d['heure_debut']);
$dis_e = strtotime($d['heure_fin']);
$abs_s = strtotime($a['heure_debut']);
$abs_e = strtotime($a['heure_fin']);
// Tests to see the if there is overlap between absence and availability
// (2) [a_s]---[ds - de]---[a_e]
if ($abs_s <= $dis_s && $abs_e >= $dis_e) {
return array();
}
// (7)[as == ds] && [ae < de]
else if ($abs_s == $dis_s && $abs_e < $dis_e) {
return array(schedule($d,$abs_e,$dis_e));
}
// (1)[ds] --- [as] --- [ae] --- [de] (duplicate dis with new times)
else if ($abs_s > $dis_s && $abs_e <= $dis_e) {
// new times as :
// s1 = ds-as && s2 = ae-de
return array(schedule($d,$dis_s,$abs_s), schedule($d,$abs_e,$dis_e));
}
// (3)[as] -- [ds] --- [ae] -- [de]
else if ($abs_s < $dis_s && $abs_e < $dis_e) {
return array(schedule($d,$abs_e,$dis_e));
}
// (4) [ds]---[as]--- [de]--- [ae]
else if ($abs_s > $dis_s && $abs_s < $dis_e && $abs_e > $dis_e) {
return array(schedule($d,$dis_s,$abs_s));
}
return array($d);
}
// move through all the entries of one day of schedule, and change
function updateDaySchedule($day, $absence){
$n = array();
foreach($day as $avail){
// intersect availability with absence
$a = updateAvailability($avail,$absence);
// append new entries
$n = array_merge($n, $a);
}
return $n;
}
function removeSessionsFromSchedule($schedule, $remove) {
if (!checkValidScheduleInput($schedule,$remove)
return $schedule;
foreach($remove as $day => $absences) {
// only update matching schedule day
if (isset($schedule[$day])) {
foreach ($absences as $abs)
$schedule[$day] = updateDaySchedule($schedule[$day], $abs);
}
}
return $schedule;
}
?>
There's still some room for improvement:
the $dis_s, $dis_e, etc. values in updateAvailability are recomputed each time, whereas some could be computed once, and passed in as parameter to the function. It may not be worth the hassle though.
the 'heure_debut' etc. constants could be made as defined constants:
define('HD','heure_debut');
This avoid possible typos (php will tell you if a constant is mispelled, but it won't tell you for a string literal), and make it easier for refactoring if the key names have to change.

The recursive nature of the function is your problem, nothing else in your function takes much processing power, so this should be quite fast. You really need to find a way to do this processing without recursing.

Related

Pointer of Array conditional sum qty

I have a question about how to make an iteration. I want to place a total row after each item in the array if the next element in the array matches a specific condition. Spesific conditions have logic like this
the data like this
if i request a qty for example = 60 the result i hope like this
you can see
data[2] = 01/03/2020 just took 10 out of 40
$iter = new \ArrayIterator($values);
$sum = 0;
foreach($values as $key => $value) {
$nextValue = $iter->current();
$iter->next();
$nextKey = $iter->key();
if(condition) {
$sum += $value;
}
}
dd($iter);
how to make this logic work on php language/ laravel?
Following logic might help you on your way:
<?php
$stock = [
'01/01/2020' => 20,
'01/02/2020' => 30,
'01/03/2020' => 40
];
showStatus($stock, 'in stock - before transaction');
$demand = 60;
foreach ($stock as $key => $value) {
if ($value <= $demand) {
$stock[$key] = 0;
$supplied[$key] = $value;
$demand -= $value;
} else {
$stock[$key] -= $demand;
$supplied[$key] = $value - ($value - $demand);
$demand = 0;
}
}
showStatus($supplied, 'supplied');
showStatus($stock, 'in stock - after transaction');
function showStatus($arr = [], $msg = '')
{
echo $msg;
echo '<pre>';
print_r($arr);
echo '</pre>';
}
?>
**Output:**
in stock - before transaction
Array
(
[01/01/2020] => 20
[01/02/2020] => 30
[01/03/2020] => 40
)
supplied
Array
(
[01/01/2020] => 20
[01/02/2020] => 30
[01/03/2020] => 10
)
in stock - after transaction
Array
(
[01/01/2020] => 0
[01/02/2020] => 0
[01/03/2020] => 30
)
Working demo
I'm not sure I've understood you correctly but this might help:
$values = [
'01/01/2020' => 20,
'01/02/2020' => 30,
'01/03/2020' => 40
];
$demand = 60;
$total = array_sum($values);
$decrease = $total - $demand; //(20+30+40) - 60 = 30
$last_key = array_keys($values,end($values))[0]; //Is 01/03/2020 in this case
$values[$last_key] -= $decrease; //Decrease value with 30 calulated above
Would output:
Array
(
[01/01/2020] => 20
[01/02/2020] => 30
[01/03/2020] => 10
)

PHP How to set the current values of array?

I'm using codeigniter for this project;
at the start of my script there's this query that checks the completed steps and sets it into array $completed_steps.
public function checkSteps()
{
$completed_steps = $this->prefModel->checkStepstbl();
$this->getPref($completed_steps);
}
so the example result is like;
Array
(
[01] => Array
(
[cat_id] => 15
[offset] => 4951
)
[02] => Array
(
[cat_id] => 15
[offset] => 4251
)
[03] => Array
(
[cat_id] => 15
[offset] => 4001
)
[04] => Array
(
[cat_id] => 15
[offset] => 4951
)
)
this is my function to get prefectures;
public function getPref($completed_steps)
{
$prefectures = $this->prefModel->getList();
foreach ( $prefectures as $prefecture ) {
$prefectureId = $prefecture["id"];
$batch_count = 0;
$max_batch = 10;
$this->getInd($prefectureId, $completed_steps,$batch_count,$max_batch);
}
}
The $prefectures have;
$prefectures = array(array("id"=>"01","name"=>"北海道"),array("id"=>"02","name"=>"青森県"),array("id"=>"03","name"=>"岩手県"),array("id"=>"04","name"=>"宮城県"),array("id"=>"05","name"=>"秋田県"),array("id"=>"06","name"=>"山形県"),array("id"=>"07","name"=>"福島県"),array("id"=>"08","name"=>"茨城県"),array("id"=>"09","name"=>"栃木県"),array("id"=>"10","name"=>"群馬県"),array("id"=>"11","name"=>"埼玉県"),array("id"=>"12","name"=>"千葉県"),array("id"=>"13","name"=>"東京都"),array("id"=>"14","name"=>"神奈川県"),array("id"=>"15","name"=>"新潟県"),array("id"=>"16","name"=>"富山県"),array("id"=>"17","name"=>"石川県"),array("id"=>"18","name"=>"福井県"),array("id"=>"19","name"=>"山梨県"),array("id"=>"20","name"=>"長野県"),array("id"=>"21","name"=>"岐阜県"),array("id"=>"22","name"=>"静岡県"),array("id"=>"23","name"=>"愛知県"),array("id"=>"24","name"=>"三重県"),array("id"=>"25","name"=>"滋賀県"),array("id"=>"26","name"=>"京都府"),array("id"=>"27","name"=>"大阪府"),array("id"=>"28","name"=>"兵庫県"),array("id"=>"29","name"=>"奈良県"),array("id"=>"30","name"=>"和歌山県"),array("id"=>"31","name"=>"鳥取県"),array("id"=>"32","name"=>"島根県"),array("id"=>"33","name"=>"岡山県"),array("id"=>"34","name"=>"広島県"),array("id"=>"35","name"=>"山口県"),array("id"=>"36","name"=>"徳島県"),array("id"=>"37","name"=>"香川県"),array("id"=>"38","name"=>"愛媛県"),array("id"=>"39","name"=>"高知県"),array("id"=>"40","name"=>"福岡県"),array("id"=>"41","name"=>"佐賀県"),array("id"=>"42","name"=>"長崎県"),array("id"=>"43","name"=>"熊本県"),array("id"=>"44","name"=>"大分県"),array("id"=>"45","name"=>"宮崎県"),array("id"=>"46","name"=>"鹿児島県"),array("id"=>"47","name"=>"沖縄県"));
when getting industry, this is the function;
public function getInd($prefectureId,$completed_steps,$batch_count,$max_batch)
{
$industries = $this->indModel->getList();
foreach ( $industries as $industry ) {
$industryId = $industry["id"];
$this->companiesCount($prefectureId,$industryId,$completed_steps,$batch_count,$max_batch);
}
}
$industries would give;
$industries=array(array("id"=>"1","name"=>"グルメ"),array("id"=>"2","name"=>"住まい"),array("id"=>"3","name"=>"医療・健康・介護"),array("id"=>"4","name"=>"美容・ファッション"),array("id"=>"5","name"=>"暮らし"),array("id"=>"6","name"=>"ショッピング"),array("id"=>"7","name"=>"ペット"),array("id"=>"8","name"=>"旅行宿泊"),array("id"=>"9","name"=>"ビジネス"),array("id"=>"10","name"=>"教育・習い事"),array("id"=>"11","name"=>"趣味"),array("id"=>"12","name"=>"公共機関・団体"),array("id"=>"13","name"=>"レジャー・スポーツ"),array("id"=>"14","name"=>"冠婚葬祭・イベント"),array("id"=>"15","name"=>"自動車・バイク"));
and in my companiesCount function;
public function companiesCount($prefectureId,$industryId, $completed_steps, $batch_count,$max_batch)
{
$loop_flg = true;
$offset = 1;
$limit = 50;
while ($loop_flg) {
if ($completed_steps != null) {
if ((array_key_exists($prefectureId, $completed_steps)) && ($completed_steps[$prefectureId]["cat_id"] == $industryId) && $completed_steps[$prefectureId]["offset"] == $offset) {
continue;
}
}
if($batch_count >= $max_batch){
sleep(75);
$last_offset = $this->lastOffset($prefectureId,$industryId);
$batch_count = $this->batchCount();
if($last_offset == ($offset - $limit)) {
if(!empty($batch_count)) {
$result = $this->indModel->getprocessId();
}
if(!empty($result)) {
if(!($this->PsExists($result->pid))) {
$pid = $this->startCompany($result->prefecture_id,$result->industry_id,$result->offset);
$batch_count++;
$this->compModel->updatePid($result->prefecture_id,$result->industry_id,$pid,$result->offset);
}
}
} else {
return $loop_flg = false;
}
} else {
$pid = $this->startCompany($prefectureId,$industryId,$offset);
$this->compModel->saveStepflg($prefectureId,$industryId,$pid,$offset);
$batch_count++;
$offset += 50;
}
}
}
What I want to do is skip through the array from what is given by the $completed_steps. So that it can start from what remains from the other arrays.
this functionality is located from this code;
if ((array_key_exists($prefectureId, $completed_steps)) && ($completed_steps[$prefectureId]["cat_id"] == $industryId) && $completed_steps[$prefectureId]["offset"] == $offset) {
continue;
}
But I can't do it somehow, help is really needed.
The current results are the perimeter $prefectureId starts again at 01, also the $industryId starts at 1, $offset too will begin from 1.
expected results, based on given $completed_steps;
I want the $prefectureId to set in 05. $industryId starts at 1(because max industry is at 15), offset to start 1 also(because usually offset max is at 4951).

Search nested multidimensional array

I have a function that fills an array:
foreach ($request->get('ids') as $id) {
$pdfArray['other']++; // Yes this is initialized
$pdfArray['rows'][$i]['id'] = $schedule->getId();
$pdfArray['rows'][$i]['date'] = $schedule->getStart()->format('d.m.Y');
$pdfArray['rows'][$i]['dateSort'] = $schedule->getStart()->format('Y-m-d H:i');
$pdfArray['rows'][$i]['from'] = $schedule->getStart()->format('H:i');
$pdfArray['rows'][$i]['to'] = $schedule->getEnd()->format('H:i');
$pdfArray['rows'][$i]['desc'] = $schedule->getDescription();
}
What I want to do
On each loop, I want to check if the array (so far) already has a desc entry equal to the current $schedule->getDescription() AND the same date as $schedule->getStart()->format('d.m.Y') (actually more, but let's keep it simple)
What I tried
public function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value OR (is_array($value) && $this->recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
Source
I use it like that:
if ($this->recursive_array_search($schedule->getDescription(), $pdfArray['rows']) &&
$this->recursive_array_search($schedule->getStart()->format('d.m.Y'), $pdfArray['rows'])){
$pdfArray['ma'][$schedule->getId()]++;
}
but this is true when ANY of the start or desc are SOMEWHERE in the current array.
How would I check if desc is found and start is in the SAME $i level?
EDIT for example
Let's say I have 10 $ids to loop through. After 2 loops, the $pdfArray looks like this:
Array
(
[other] => 2
[rows] => Array
(
[0] => Array
(
[id] => 1
[date] => 13.07.2016
[dateSort] => 2016-07-13 08:00
[from] => 08:00
[to] => 09:00
[desc] => TEST
)
[1] => Array
(
[id] => 2
[date] => 12.07.2016
[dateSort] => 2016-07-12 08:00
[from] => 08:00
[to] => 09:00
[desc] => TEST
)
)
)
The next iteration has the following:
$schedule->getStart()->format('d.m.Y') => 12.07.2016
$schedule->getDescription() => TEST
So I want to have the info that the combination already exists in the array.
BUT
$schedule->getStart()->format('d.m.Y') => 12.07.2016
$schedule->getDescription() => TEST2
should NOT return true upon checking of it exists.
To test for a "duplicate" you can use this function:
function testPresence($pdfArray, $desc, $date) {
foreach ($pdfArray["rows"] as $row) {
if ($row["desc"] == $desc && $row["date"] == $date) return true;
}
}
Example use:
echo testPresence($pdfArray, "TEST2", "12.07.2016") ? "Found" : "Not found"; // Not found
echo testPresence($pdfArray, "TEST", "12.07.2016") ? "Found" : "Not found"; // Found
In your original loop, you can use it as follows:
foreach ($request->get('ids') as $id) {
if (testPresence($pdfArray, $schedule->getDescription(),
$schedule->getStart()->format('d.m.Y')) {
// We have a duplicate. Maybe skip this entry?:
continue;
}
$pdfArray['other']++;
$pdfArray['rows'][$i]['id'] = $schedule->getId();
$pdfArray['rows'][$i]['date'] = $schedule->getStart()->format('d.m.Y');
$pdfArray['rows'][$i]['dateSort'] = $schedule->getStart()->format('Y-m-d H:i');
$pdfArray['rows'][$i]['from'] = $schedule->getStart()->format('H:i');
$pdfArray['rows'][$i]['to'] = $schedule->getEnd()->format('H:i');
$pdfArray['rows'][$i]['desc'] = $schedule->getDescription();
}
try this at your validation function
public function array_search($needle1, $needle2 ,$haystack) {
foreach($haystack as $singleArray){
if (in_array($needle1, $singleArray) && in_array($needle2, $singleArray))
return true;
else
continue;
}
return false;
}
and invoke your recursive_array_search like this
if ($this->array_search($schedule->getStart(), $schedule->getDescription(), $pdfArray['rows'])
continue;//Or any other kind of logic you want. At this place you know that description and date staet exist in at your array level
$pdfArray['other']++; // Yes this is initialized
$pdfArray['rows'][$i]['id'] = $schedule->getId();
$pdfArray['rows'][$i]['date'] = $schedule->getStart()->format('d.m.Y');
$pdfArray['rows'][$i]['dateSort'] = $schedule->getStart()->format('Y-m-d H:i');
$pdfArray['rows'][$i]['from'] = $schedule->getStart()->format('H:i');
$pdfArray['rows'][$i]['to'] = $schedule->getEnd()->format('H:i');
$pdfArray['rows'][$i]['desc'] = $schedule->getDescription();
Function version:
/**
* Find matches for $item into pdfArray.
* Returns an index array, possibly empty if no matches.
* #param $item item to find
* #param $rows rows where to search
*/
function findPdfArrayMatches(array $item, array $rows) {
return array_keys(
array_filter(
$rows,
function ($entry) use ($item) {
// These are the matching criteria. More than one row may match.
return $entry['desc'] == $item['desc']
&& $entry['date'] == $item['date']
;
}
)
);
}
You could do like this, in the loop:
$item = [
'id' => $schedule->getId(),
'date' => $schedule->getStart()->format('d.m.Y'),
'dateSort' => $schedule->getStart()->format('Y-m-d H:i'),
'from' => $schedule->getStart()->format('H:i'),
'to' => $schedule->getEnd()->format('H:i'),
'desc' => $schedule->getDescription(),
];
$matches = findPdfArrayMatches($item, $pdfArray['rows']);
if (!empty($matches)) {
...do something with the matches:
foreach ($matches as $match) {
$pdfArray['rows'][$match]['Duplicate'] = true;
}
}
// Add new item
$pdfArray['rows'][$i] = $item;

PHP algorithm to find between values in varying distance

I'm trying to create an algorithm that returns a price depending on number of hours. But the distance between the number of hours are varying. For example I have an array:
$set = [
1 => 0.5,
2 => 1,
3 => 1.5,
4 => 2,
5 => 2.5,
12 => 4
];
$value = 3;
end($set);
$limit = (int)key($set);
foreach($set as $v => $k) {
// WRONG, doesn't account for varying distance
if($value >= $v && $value <= $v) {
if($value <= $limit) {
return $k;
} else {
return false;
}
}
}
The trouble is, the distance between 5 and 12 register as null. I might as well use $value == $v instead as the line I've marked as incorrect does anyway.
So I was wondering if there was a better way to round up to the next index in that array and return the value for it?
Cheers in advance!
Try this:
$set = array(1 => 0.5, 2 => 1, 3 => 1.5, 4 => 2, 5 => 2.5, 12 => 4);
function whatever(idx, ary){
if(in_array(idx, array_keys(ary))){
return ary[idx];
}
else{
foreach(ary as $i => $v){
if($i > idx){
return $v;
}
}
}
return false;
}
echo whatever(7, $set);
The problem is that $v is a single value, so $value >= $v && $value <= $v is equivalent to $value == $v.
Instead, consider that if the loop hasn't ended, then the cutoff hasn't been reached yet - and a current "best price" is recorded. This requires that the keys are iterated in a well-ordered manner that can be stepped, but the logic can be updated for a descending order as well.
$price_chart = [
1 => 0.5,
2 => 1,
3 => 1.5,
4 => 2,
5 => 2.5,
12 => 4
];
function get_price ($hours) {
global $price_chart;
$best_price = 0;
foreach($price_chart as $min_hours => $price) {
if($hours >= $min_hours) {
// continue to next higher bracket, but remember the best price
// which is issued for this time bracket
$best_price = $price;
continue;
} else {
// "before" the next time cut-off, $hours < $min_hours
return $best_price;
}
}
// $hours > all $min_hours
return $best_price;
}
See the ideone demo. This code could also be updated to "fill in" the $price_chart, such that a price could be found simply by $price_chart[$hours] - but such is left as an exercise.

PHP: Condense array of similar strings into one merged array

Working with an array of dates (opening times for a business). I want to condense them to their briefest possible form.
So far, I started out with this structure
Array
(
[Mon] => 12noon-2:45pm, 5:30pm-10:30pm
[Tue] => 12noon-2:45pm, 5:30pm-10:30pm
[Wed] => 12noon-2:45pm, 5:30pm-10:30pm
[Thu] => 12noon-2:45pm, 5:30pm-10:30pm
[Fri] => 12noon-2:45pm, 5:30pm-10:30pm
[Sat] => 12noon-11pm
[Sun] => 12noon-9:30pm
)
What I want to achieve is this:
Array
(
[Mon-Fri] => 12noon-2:45pm, 5:30pm-10:30pm
[Sat] => 12noon-11pm
[Sun] => 12noon-9:30pm
)
I've tried writing a recursive function and have managed to output this so far:
Array
(
[Mon-Fri] => 12noon-2:45pm, 5:30pm-10:30pm
[Tue-Fri] => 12noon-2:45pm, 5:30pm-10:30pm
[Wed-Fri] => 12noon-2:45pm, 5:30pm-10:30pm
[Thu-Fri] => 12noon-2:45pm, 5:30pm-10:30pm
[Sat] => 12noon-11pm
[Sun] => 12noon-9:30pm
)
Can anybody see a simple way of comparing the values and combining the keys where they're similar? My recursive function is basically two nested foreach() loops - not very elegant.
Thanks,
Matt
EDIT: Here's my code so far, which produces the 3rd array above (from the first one as input):
$last_time = array('t' => '', 'd' => ''); // blank array for looping
$i = 0;
foreach($final_times as $day=>$time) {
if($last_time['t'] != $time ) { // it's a new time
if($i != 0) { $print_times[] = $day . ' ' . $time; }
// only print if it's not the first, otherwise we get two mondays
} else { // this day has the same time as last time
$end_day = $day;
foreach($final_times as $day2=>$time2) {
if($time == $time2) {
$end_day = $day2;
}
}
$print_times[] = $last_time['d'] . '-' . $end_day . ' ' . $time;
}
$last_time = array('t' => $time, 'd' => $day);
$i++;
}
I don't think there is a particularly elegant solution to this. After much experimenting with the built in array_* functions trying to find a nice simple solution, I gave up and came up with this:
$lastStart = $last = $lastDay = null;
$new = array();
foreach ($arr as $day => $times) {
if ($times != $last) {
if ($last != null) {
$key = $lastStart == $lastDay ? $lastDay : $lastStart . '-' . $lastDay;
$new[$key] = $last;
}
$lastStart = $day;
$last = $times;
}
$lastDay = $day;
}
$key = $lastStart == $lastDay ? $lastDay : $lastStart . '-' . $lastDay;
$new[$key] = $last;
It only uses one foreach loop as opposed to your two, as it keeps a bunch of state. It'll only merge adjacent days together (i.e., you won't get something like Mon-Tue,Thu-Fri if Wednesday is changed, you'll get two separate entries).
I'd approach it by modelling it as a relational database:
day start end
1 12:00 14:45
1 17:30 22:30
...
Then its fairly easy to reduce - there are specific time intervals:
SELECT DISTINCT start, end
FROM timetable;
And these will occur on specific days:
SELECT start, end, GROUP_CONCAT(day) ORDER BY day SEPERATOR ','
FROM timetable
GROUP BY start,end
(this uses the MySQL-only 'group_concat' function - but the method is the same where this is not available)
would give:
12:00 14:45 1,2,3,4,5
17:30 22:30 1,2,3,4,5
12:00 23:00 6
12:00 21:30 7
Then it's fairly simple to work out consecutive date ranges from the list of days.
C.
As an alternative, I managed to cobble together a version using array_* functions. At some point though, 'elegance', 'efficiency' and 'readability' all packed up and left. It does, however, handle the edge cases I mentioned in the other answer, and it left me with a nice warm glow for proving it could be done in a functional manner (yet at the same time a sense of shame...)
$days = array_keys($arr);
$dayIndices = array_flip($days);
var_dump(array_flip(array_map(
function ($mydays) use($days, $dayIndices) {
return array_reduce($mydays,
function($l, $r) use($days, $dayIndices) {
if ($l == '') { return $r; }
if (substr($l, -3) == $days[$dayIndices[$r] - 1]) {
return ((strlen($l) > 3 && substr($l, -4, 1) == '-') ? substr($l, 0, -3) : $l) . '-' . $r;
}
return $l . ',' . $r;
}, '');
}, array_map(
function ($day) use ($arr) {
return array_keys($arr, $arr[$day]);
}, array_flip($arr)
)
)));
I tested it with this input:
'Mon' => '12noon-2:45pm, 5:30pm-10:30pm',
'Tue' => '12noon-2:45pm, 5:30pm-10:30pm',
'Wed' => '12noon-2:45pm, 5:30pm-10:00pm',
'Thu' => '12noon-2:45pm, 5:30pm-10:30pm',
'Fri' => '12noon-2:45pm, 5:30pm-10:00pm',
'Sat' => '12noon-2:45pm, 5:30pm-10:30pm',
'Sun' => '12noon-9:30pm'
And got this:
["Mon-Tue,Thu,Sat"]=> string(29) "12noon-2:45pm, 5:30pm-10:30pm"
["Wed,Fri"]=> string(29) "12noon-2:45pm, 5:30pm-10:00pm"
["Sun"]=> string(13) "12noon-9:30pm"
Basically, the array_map at the end transforms the input into an associative array of times to an array of days that they occur on. The large block of code before that reduces those days into a nicely formatted string using array_reduce, consulting the $days and $dayIndices arrays to check if days are consecutive or not.

Categories