This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
I have created a helper function to return the first valid payment date of the month. The day is not valid if it's a holiday (pulled from a list in a db table), a Saturday, or a Sunday. In this example the 1st January 2016 is a holiday.
I have created this recursive function in a CodeIgniter helper but I'm seeing really weird behaviour. What it should do is identify the 1st as a holiday, call itself to identify the 2nd as a Saturday, again to identify the 3rd as a Sunday, before eventually returning the 4th as the first valid day.
After the function calls itself, the part after the call will keep running in an infinite loop unless I insert a break;. If I do insert a break I get the following output:
2016-01-01: 1
2016-01-02: 2
2016-01-03: 3
Final: 2016-01-04: 4
Final: 2016-01-04: 4
Final: 2016-01-03: 3
Final: 2016-01-02: 2
before finally returning 2 (which is wrong).
function day_check($paymentDate, $paymentDay = 1) {
$CI =& get_instance();
$dateParts = explode("-", $paymentDate);
$holQ = $CI->db->query("SELECT * FROM holidays WHERE holidayDate = '$paymentDate'");
$holR = $holQ->row();
if ($paymentDay <= 0) {
$paymentDay = 1;
}
while (($holR->holidayDate == $paymentDate) || (date("l", strtotime($paymentDate)) == 'Saturday') || (date("l", strtotime($paymentDate)) == 'Sunday')) {
echo "$paymentDate: $paymentDay <br>";
$refinedDay = $dateParts[2] + 1;
if ($refinedDay < 10) {
$refinedDay = "0" . $refinedDay;
}
$paymentDate = $dateParts[0] . "-" . $dateParts[1] . "-" . ($refinedDay);
$paymentDay = $dateParts[2] + 1;
day_check($paymentDate, $paymentDay);
break;
}
echo "Final: $paymentDate: $paymentDay <br>";
return $paymentDay;
}
Initial $paymentDate provided to function is 2016-01-01
I've been looking at this for hours and I don't understand why this is happening. Is this a quirk of Codeigniter or do I completely misunderstand my recursion logic?
The problem is misunderstanding the logic. In your code the recursion call result is not used ever.
function day_check($paymentDate, $paymentDay = 1) {
$CI =& get_instance();
$dateParts = explode("-", $paymentDate);
$holQ = $CI->db->query("SELECT * FROM holidays WHERE holidayDate = '$paymentDate'");
$holR = $holQ->row();
if ($paymentDay <= 0) {
$paymentDay = 1;
}
// while -> if
if (($holR->holidayDate == $paymentDate) || (date("l", strtotime($paymentDate)) == 'Saturday') || (date("l", strtotime($paymentDate)) == 'Sunday')) {
echo "$paymentDate: $paymentDay <br>";
$refinedDay = $dateParts[2] + 1;
if ($refinedDay < 10) {
$refinedDay = "0" . $refinedDay;
}
$paymentDate = $dateParts[0] . "-" . $dateParts[1] . "-" . ($refinedDay);
$paymentDay = $dateParts[2] + 1;
return day_check($paymentDate, $paymentDay); // return!
// break; // no need
}
echo "Final: $paymentDate: $paymentDay <br>";
return $paymentDay;
}
Also change while to if since we don't need a loop there.
Related
Is there a way to get the true statement from $this if condition:
$a = 1;
$b = 3;
$c = 7;
if ($a == 3 || $b == 4 || $c == 7) {
echo "The true statement was: ";
}
I expect to get this output:
The true statement was: 7
Is it possible to do this in PHP?
Or better to say how can i check which statement has triggered the if condition?
You can't without multiple conditions. Whatever answer you will get here eg:
Inline if statements
Wrap in a function
Condition result assignment in the condition
Switch
Loops
etc. Will always require you to have multiple conditions.
If you don't mind multiple conditions and just looking for most elegant way to write it, thats another question and we can help.
This can only ever show 1 true statement because of how the if works:
$a = 1;
$b = 3;
$c = 7;
if (($t = $a) ==3 || ($t = $b) == 4 || ($t=$c) == 7) {
echo "The true statement was: $t";
}
What happens here is it sets $t to each variable and then checks if the assignment result (which is the value) was successful. Since this is an || then it stops at the first success and so $t will have the last compared value.
Try this.
<?php
$day = 1;
$month = 3;
$year = 2017;
$str = "The true statements are: " . ($day == 3 ? "$day, " : "") . ($month == 4 ? "$month, " : "") . ($year == 2017 ? "$year, " : "");
echo substr($str, 0, strlen($str) - 2);
?>
If I understand correctly, this should work.
The strlen($str) -2 is to remove the trailing ", ".
Here is a solution with boolean variables:
$day = 1;
$month = 3;
$year = 2017;
$cday = $day == 3;
$cmonth = $month == 4;
$cyear = $year == 2017;
if ($cday || $cmonth || $cyear) {
echo "The true statements are: ";
if($cday) echo "$day<br>\n";
if($cmonth) echo "$month<br>\n";
if($cyear) echo "$year<br>\n";
}
This might help -
// actual values
$day = 1;
$month = 3;
$year = 2017;
// values & variable names to check
$checks = array(
'day' => 1,
'month' => 4,
'year' => 2017,
);
// Loop through the checks
foreach($checks as $check => $value) {
// compare values
if($$check == $value) {
// output and stop looping
echo "The true statement was: $check -> $value";
break;
}
}
Demo
I'm having an issue with some code that someone else has previously worked on.
The goal is to iterate through a directory and push any files that are within a certain date range to an array (files are in mmddyyy.txt format).
The (terribly named, not by my own doing) variables in the code represent the following:
$aYear - A given year, read in from a text file. This variable changes during every iteration of the loop. The same goes for $aMonth and $aDay.
$sYear1 - Start year. $sMonth1 and $sDay1 are used in respect to $sYear1.
$sYear2 - End year. $sMonth2 and $sDay2 are used in respect to $sYear2.
$isGood - File will be added to the array.
$isGood = false;
if($aYear >= $sYear1 && $aYear <= $sYear2)
{
if($aYear == $sYear1)
{
if($aMonth == $sMonth1)
{
if($aDay >= $sDay1 && $aDay <= $sDay2)
{
$isGood = true;
}
}
else
{
if($aMonth >= $sMonth1 && $aMonth <= $sMonth2)
{
$isGood = true;
}
}
}
else if($aYear == $sYear2)
{
if($aMonth == $sMonth2)
{
if($aDay <= $sDay2)
{
$isGood = true;
}
}
else
{
if($aMonth <= $sMonth2)
{
$isGood = true;
}
}
}
else
{
$isGood = true;
}
}
if($isGood)
{
//echo "Found good article";
$a = $a . "===" . $file;
array_push($result, $a);
}
I'm not getting the results that I expected. I'm looking for some help as to how I can simplify this code and get it working properly. I do need to keep this solution in PHP.
Thank you in advance.
It seems to me Month statement if($aMonth >= $sMonth1 && $aMonth <= $sMonth2) needs work
eg start date- 03 Aug 2013 end date- 04 Sep 2016 and check date say 08 Nov 2013
would make isGood=false whereas it should be true.
Removing && $aDay <= $sDay2 and && $aMonth <= $sMonth2 should work.
As #Sandeep pointed out you're issues are with:
if ($aMonth >= $sMonth1 && $aMonth <= $sMonth2)
and
if ($aDay >= $sDay1 && $aDay <= $sDay2)
as you don't need to be comparing the date with end dates as well.
That being said you can clear up your code completely by doing something like:
$date = (new DateTime)->setDate($aYear, $aMonth, $aDay);
$start = (new DateTime)->setDate($sYear1, $sMonth1, $sDay1);
$end = (new DateTime)->setDate($sYear2, $sMonth2, $sDay2);
if ($start <= $date && $date <= $end) {
//echo "Found good article";
$a = $a . "===" . $file;
array_push($result, $a);
}
Hope this helps!
I am working on a bit of PHP and I've come upon a bit of issues.
I am using PHP to randomly choose a number from 1-360. I am trying to compare the answer to a list of value determined by range.
$NumberA = rand(0,180);
$NumberB = rand(0,180);
$NumberC = $NumberA + $NumberB;
if ($NumberC = range(0,21) {
$result = "Orange";
}
elseif ($NumberC = range(22,42) {
$result = "Red";
}
elseif ($NumberC = range(43,63) {
$result = "Blue";
}
//This goes on for a while ...
else {
$result = "Green";
}
echo = $result;
Anytime i do this, the result always assigns the value of "Orange" to $result .
Im sure im doing something wrong here, please help!
First of all, you used just one '=' to compare while it should have been '=='. Second range() generates an array and you cannot compare an integer to an array. Third why generating the range every single time when you can check that $NumberC lies between the minimum and the maximum numbers of the range?
Change your code to:
$NumberA = rand(0,180);
$NumberB = rand(0,180);
$NumberC = $NumberA + $NumberB;
if ($NumberC >= 0 && $NumberC <= 21) {
$result = "Orange";
} elseif ($NumberC >= 22 && $NumberC <= 42) {
$result = "Red";
} elseif ($NumberC >= 43 && $NumberC <= 63) {
$result = "Blue";
} else {
$result = "Green";
}
echo $result;
Shall work. Hope this helps.
I'm having an asbolute nightmare dealing with an array of numbers which has the following structure :
Odd numbers in the array : NumberRepresenting Week
Even numbers in the array : NumberRepresenting Time
So for example in the array :
index : value
0 : 9
1 : 1
2 : 10
3 : 1
Would mean 9 + 10 on Day 1 (Monday).
The problem is, I have a an unpredictable number of these and I need to work out how many "sessions" there are per day. The rules of a session are that if they are on a different day they are automatically different sessions. If they are next to each other like in the example 9 + 10 that would count as a single session. The maximum number than can be directly next to eachother is 3. After this there needs to be a minimum of a 1 session break in between to count as a new session.
Unfortunately, we cannot also assume that the data will be sorted. It will always follow the even / odd pattern BUT could potentially not have sessions stored next to each other logically in the array.
I need to work out how many sessions there are.
My code so far is the following :
for($i = 0; $i < (count($TimesReq)-1); $i++){
$Done = false;
if($odd = $i % 2 )
{
//ODD WeekComp
if(($TimesReq[$i] != $TimesReq[$i + 2])&&($TimesReq[$i + 2] != $TimesReq[$i + 4])){
$WeeksNotSame = true;
}
}
else
{
//Even TimeComp
if(($TimesReq[$i] != ($TimesReq[$i + 2] - 1))&& ($TimesReq[$i + 2] != ($TimesReq[$i + 4] - 1)))
$TimesNotSame = true;
}
if($TimesNotSame == true && $Done == false){
$HowMany++;
$Done = true;
}
if($WeeksNotSame == true && $Done == false){
$HowMany++;
$Done = true;
}
$TimesNotSame = false;
$WeeksNotSame = false;
}
However this isn't working perfectly. for example it does not work if you have a single session and then a break and then a double session. It is counting this as one session.
This is, probably as you guessed, a coursework problem, but this is not a question out of a textbook, it is part of a timetabling system I am implementing and is required to get it working. So please don't think i'm just copy and pasting my homework to you guys!
Thank you so much!
New Code being used :
if (count($TimesReq) % 2 !== 0) {
//throw new InvalidArgumentException();
}
for ($i = 0; $i < count($TimesReq); $i += 2) {
$time = $TimesReq[$i];
$week = $TimesReq[$i + 1];
if (!isset($TimesReq[$i - 2])) {
// First element has to be a new session
$sessions += 1;
$StartTime[] = $TimesReq[$i];
$Days[] = $TimesReq[$i + 1];
continue;
}
$lastTime = $TimesReq[$i - 2];
$lastWeek = $TimesReq[$i - 1];
$sameWeek = ($week === $lastWeek);
$adjacentTime = ($time - $lastTime === 1);
if (!$sameWeek || ($sameWeek && !$adjacentTime)) {
if(!$sameWeek){//Time
$Days[] = $TimesReq[$i + 1];
$StartTime[] = $TimesReq[$i];
$looking = true;
}
if($sameWeek && !$adjacentTime){
}
if($looking && !$adjacentTime){
$EndTime[] = $TimesReq[$i];
$looking = false;
}
//Week
$sessions += 1;
}
}
If you want a single total number of sessions represented in the data, where each session is separated by a space (either a non-contiguous time, or a separate day). I think this function will get you your result:
function countSessions($data)
{
if (count($data) % 2 !== 0) throw new InvalidArgumentException();
$sessions = 0;
for ($i = 0; $i < count($data); $i += 2) {
$time = $data[$i];
$week = $data[$i + 1];
if (!isset($data[$i - 2])) {
// First element has to be a new session
$sessions += 1;
continue;
}
$lastTime = $data[$i - 2];
$lastWeek = $data[$i - 1];
$sameWeek = ($week === $lastWeek);
$adjacentTime = ($time - $lastTime === 1);
if (!$sameWeek || ($sameWeek && !$adjacentTime)) {
$sessions += 1;
}
}
return $sessions;
}
$totalSessions = countSessions(array(
9, 1,
10, 1,
));
This of course assumes the data is sorted. If it is not, you will need to sort it first. Here is an alternate implementation that includes support for unsorted data.
function countSessions($data)
{
if (count($data) % 2 !== 0) throw new InvalidArgumentException();
$slots = array();
foreach ($data as $i => $value) {
if ($i % 2 === 0) $slots[$i / 2]['time'] = $value;
else $slots[$i / 2]['week'] = $value;
}
usort($slots, function($a, $b) {
if ($a['week'] == $b['week']) {
if ($a['time'] == $b['time']) return 0;
return ($a['time'] < $b['time']) ? -1 : 1;
} else {
return ($a['week'] < $b['week']) ? -1 : 1;
}
});
$sessions = 0;
for ($i = 0; $i < count($slots); $i++) {
if (!isset($slots[$i - 1])) { // First element has to be a new session
$sessions += 1;
continue;
}
$sameWeek = ($slots[$i - 1]['week'] === $slots[$i]['week']);
$adjacentTime = ($slots[$i]['time'] - $slots[$i - 1]['time'] === 1);
if (!$sameWeek || ($sameWeek && !$adjacentTime)) {
$sessions += 1;
}
}
return $sessions;
}
Here is my little attempt at solving your problem. Hopefully I understand what you want:
$TimesReq = array(9,4,11,4,13,4,8,4,7,2,12,4,16,4,18,4,20,4,17,4);
// First just create weeks with all times lumped together
$weeks = array();
for($tri=0; $tri<count($TimesReq); $tri+=2){
$time = $TimesReq[$tri];
$week = $TimesReq[$tri+1];
$match_found = false;
foreach($weeks as $wi=>&$w){
if($wi==$week){
$w[0] = array_merge($w[0], array($time));
$match_found = true;
break;
}
}
if(!$match_found) $weeks[$week][] = array($time);
}
// Now order the times in the sessions in the weeks
foreach($weeks as &$w){
foreach($w as &$s) sort($s);
}
// Now break up sessions by gaps/breaks
$breaking = true;
while($breaking){
$breaking = false;
foreach($weeks as &$w){
foreach($w as &$s){
foreach($s as $ti=>&$t){
if($ti>0 && $t!=$s[$ti-1]+1){
// A break was found
$new_times = array_splice($s, $ti);
$s = array_splice($s, 0, $ti);
$w[] = $new_times;
$breaking = true;
break;
}
}
}
}
}
//print_r($weeks);
foreach($weeks as $wi=>&$w){
echo 'Week '.$wi.' has '.count($w)." session(s):\n";
foreach($w as $si=>&$s)
{
echo "\tSession ".($si+1).":\n";
echo "\t\tStart Time: ".$s[0]."\n";
echo "\t\tEnd Time: ".((int)($s[count($s)-1])+1)."\n";
}
}
Given $TimesReq = array(9,4,11,4,13,4,8,4,7,2,12,4,16,4,18,4,20,4,17,4); the code will produce as output:
Week 4 has 4 session(s):
Session 1:
Start Time: 8
End Time: 10
Session 2:
Start Time: 11
End Time: 14
Session 3:
Start Time: 16
End Time: 19
Session 4:
Start Time: 20
End Time: 21
Week 2 has 1 session(s):
Session 1:
Start Time: 7
End Time: 8
Hope that helps.
Re,
One photo with exposure being 1/640 has the EXIF field of "ExposureTime" eq. "15625/10000000". I am not sure why some photos display this value in a readable format (e.g., "1/100"), but I need to convert this "15625" back to "1/640". How? :)
Thanks.
It's simple mathematics: simply divide the top and bottom of the fraction by the top value.
15625 / 10000000
= (15625/15625) / (10000000/15625)
= 1 / 640
In PHP, you can do it like this:
$exposure = "15625/10000000";
$parts = explode("/", $exposure);
$exposure = implode("/", array(1, $parts[1]/$parts[0]));
echo $exposure;
I improved upon ZZ Coders implementation with a few sanity checks and special cases. It seems to work well with my images with several special cases thrown at it. Please let me know if there are any issues and we'll improve it.
// Exposure Time
$exif = exif_read_data($fullPath, 'IFD0', true);
$arrExposureTime = explode('/', $exif['EXIF']['ExposureTime']);
// Sanity check for zero denominator.
if ($arrExposureTime[1] == 0) {
$ExposureTime = '<sup>1</sup>/? sec';
// In case numerator is zero.
} elseif ($arrExposureTime[0] == 0) {
$ExposureTime = '<sup>0</sup>/' . $arrExposureTime[1] . ' sec';
// When denominator is 1, display time in whole seconds, minutes, and/or hours.
} elseif ($arrExposureTime[1] == 1) {
// In the Seconds range.
if ($arrExposureTime[0] < 60) {
$ExposureTime = $arrExposureTime[0] . ' s';
// In the Minutes range.
} elseif (($arrExposureTime[0] >= 60) && ($arrExposureTime[0] < 3600)) {
$ExposureTime = gmdate("i\m:s\s", $arrExposureTime[0]);
// In the Hours range.
} else {
$ExposureTime = gmdate("H\h:i\m:s\s", $arrExposureTime[0]);
}
// When inverse is evenly divisable, show reduced fractional exposure.
} elseif (($arrExposureTime[1] % $arrExposureTime[0]) == 0) {
$ExposureTime = '<sup>1</sup>/' . $arrExposureTime[1]/$arrExposureTime[0] . ' sec';
// If the value is greater or equal to 3/10, which is the smallest standard
// exposure value that doesn't divid evenly, show it in decimal form.
} elseif (($arrExposureTime[0]/$arrExposureTime[1]) >= 3/10) {
$ExposureTime = round(($arrExposureTime[0]/$arrExposureTime[1]), 1) . ' sec';
// If all else fails, just display it as it was found.
} else {
$ExposureTime = '<sup>' . $arrExposureTime[0] . '</sup>/' . $arrExposureTime[1] . ' sec';
}
This is the code I use to normalize the exposure,
if (($bottom % $top) == 0) {
$data = '1/'.round($bottom/$top, 0).' sec';
} else {
if ($bottom == 1) {
$data = $top.' sec';
} else {
$data = $top.'/'.$bottom.' sec';
}
}
It handles most exposures correctly but I see some weird ones once a while.
You can use Euclid's algorithm to find the greatest common divisor, which will help you reduce the fraction.