Related
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
)
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).
This is for tie-break situations ranking players in a series. As such, ignore the totals of the scores, they'll be the same (1400 in this example).
So, given the following array:
array(2) {
array(3) {
[0] => array(1) {
["score"] => 500
}
[1] => array(1) {
["score"] => 500
}
[2] => array(1) {
["score"] => 400
}
}
array(4) {
[0] => array(1) {
["score"] => 600
}
[1] => array(1) {
["score"] => 600
}
[2] => array(1) {
["score"] => 100
}
[3] => array(1) {
["score"] => 100
}
}
}
I'd like to sort it so that the subarray with the highest individual score (or NUMBER of highest scores) comes out on top. If that's stiil a tie, we work down to their second highest scores, etc.
E.g. in the above example, 600,600,100,100 is better than 500,500,400, even though they both total 1400.
Similarly 0,100,300,300 would be better than 300,200,200,0,0
0,100,100,50 would be better than 100,100,25,0,0,25, etc.
This is the best I've come up with so far, and it seems very messy to me, I feel there must be better/cleaner options:
function sortTies($a, $b)
{
// get arrays of the scores and their counts
// each array item will have the score as the key and a count as the value
$aa = array_count_values(array_column($a, 'score'));
$bb = array_count_values(array_column($b, 'score'));
// sort them so that the highest scores are first
krsort($aa);
krsort($bb);
// get the length of the longest array
$maxLength = max(count($aa), count($bb));
// now we loop through, comparing the $i'th index of $aa to that of $bb
for($i = 0; $i < $maxLength; $i++) {
reset($aa);
reset($bb);
// move $i items deeper into the arrays
for($n=0; $n < $i; $n++) {
next($aa);
next($bb);
}
// if the keys differ at a certain position, we have our sort criteria, so should return...
if (key($aa) !== key($bb)) {
return key($bb) <=> key($aa)
}
// ...otherwise, we check the value stored under those keys in each array
elseif (current($aa) !== current($bb)) {
return current($bb) <=> current($aa);
}
// if the key and the value are the same, we don't return and we carry on into the next
// iteration of the loop, going one element deeper in each array
}
return 0; //dead heat
}
What could I do to make this better?
Loop through the list, group by score, sum quantity ( instance of score ).
Order by score desc. Get a factor Score * Quantity, then construct an array with the factor and the list.
Finally, sort desc by factor.
Like this:
<?php
$data=array(
array(
array(
"score" => 500
),
array(
"score" => 500
),
array(
"score" => 400
)
),
array(
array(
"score" => 600
),
array(
"score" => 600
),
array(
"score" => 100
),
array(
"score" => 100
)
)
);
$result = array();
foreach( $data as $i => $list ) {
$group = array();
foreach( $list as $index => $item ) {
if ( empty( $group ) ) {
$group[$item["score"]] = 1;
} else {
if ( isset( $group[$item["score"]] ) ) {
$qty = $group[$item["score"]];
$qty = $qty + 1;
$group[$item["score"]] = $qty;
} else {
$group[$item["score"]] = 1;
} // end if isset score
} // end if then else empty
} // end foreach $list
if ( !empty( $group ) ) {
// Order by key, desc
krsort( $group );
// Get the factor
$key = key( $group );
$factor = $key * $group[$key];
// Push
$result[$factor] = $list;
} // end if group not empty
} // end foreach $data
krsort( $result );
print_r( $result );
Following is the PHP code of a smarty plugin to apply pagination to the pages.
<?php
function smarty_function_pagination_link_01($params, &$smarty)
{
if ( !is_array($params['values']) )
{
return "is not array";
}
if ( 0 == count($params['values']) )
{
return "Empty Array";
}
if ( empty($params['values']['current_page']) )
{
return "Invalid Request";
}
$values = $params['values'];
//Seperator Used Betwinn Pagination Links
$seprator = empty( $params['seperator'] ) ? " " : $params['seperator'];
//Class Name For Links
$extra = empty( $params['extra'] ) ? "" : $params['extra'];
$current_page = (int)$values['current_page'];
if ( !empty($values['first']) )
{
//$ret[] = "<a $extra href='{$values['first']}' ><First</a>";
}
if ( !empty($values['previous'] ) )
{
$ret[] = "<a $extra href='{$values['previous']}' class='prev active'><span></span></a>";
}
$ret[] = "<ul>";
foreach( $values as $k => $v )
{
if( is_numeric( $k ) )
{
if ( $k == $current_page)
{
$ret[] = "<li><a $extra class='active'>$k</a></li>";
}
else
{
$ret[] = "<li><a $extra href='$v'>$k </a></li>";
}
}
}
if ( !empty($values['next'] ) )
{
$ret[] = "</ul><a $extra href='{$values['next']}' class='next active'><span></span></a>";
}
if ( !empty($values['last'] ) )
{
//$ret[] = "<a $extra href='{$values['last']}' >Last></a>";
}
//$str_ret = $first . $previous . $str_ret . $next . $last;
if ( $ret )
{
return implode( $seprator, $ret );
}
}
?>
This function is called from the smarty template. Now it's working perfectly. But I want to change the behavious of above function, I want to change the value it is returning. The value of the argument $params the above function is getting is as follows:
Array
(
[values] => Array
(
[1] => /xyz/pqr/web/control/modules/questions/view_questions.php?subject_id=&topic_id=&difficulty_level=&from_date=01/02/1999&to_date=17/02/2014&staff_id=&page=1
[2] => /xyz/pqr/web/control/modules/questions/view_questions.php?subject_id=&topic_id=&difficulty_level=&from_date=01/02/1999&to_date=17/02/2014&staff_id=&page=2
[3] => /xyz/pqr/web/control/modules/questions/view_questions.php?subject_id=&topic_id=&difficulty_level=&from_date=01/02/1999&to_date=17/02/2014&staff_id=&page=3
[next] => /xyz/pqr/web/control/modules/questions/view_questions.php?subject_id=&topic_id=&difficulty_level=&from_date=01/02/1999&to_date=17/02/2014&staff_id=&page=2
[last] => /xyz/pqr/web/control/modules/questions/view_questions.php?subject_id=&topic_id=&difficulty_level=&from_date=01/02/1999&to_date=17/02/2014&staff_id=&page=8170
[current_page] => 1
)
)
Now the display of the page numbers are is as follows:
![enter image description here][1]
But I want the opagination numbers in the following fashion:
1 2 3 4 5 6 7 8 9 10 Next Last
How should I get the above pattern by making changes into the function?Thanks in advance.
Try to configure it with num_links like
$config['num_links'] = 10;
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.