Iterating through a column in a CSV file (PHP) - php

I need to write a function that takes a year and a temperature as an input and returns how many days in a given year the temperature was equal to or below the given temperature. Since the data is about hours, not days, need to find the number of hours and divide it by 24.
Example: getDaysUnderTemp(2019, -10) returns 13.92.
CSV file looks like this (year, month, day, hour, temperature):
2019,1,1,0,0.1
2019,1,1,1,0.4
2019,1,1,2,0.8
2019,1,1,3,1.3
2019,1,1,4,1.8
...
2020,1,1,0,-3.9
So far my code looks like this (I'm new to php):
function getDaysUnderTemp(int $targetYear, float $targetTemp): float {
$inputFile = fopen("data/temperatures-debug.csv", "r");
while (!feof($inputFile)) {
$file = fgetcsv($inputFile);
$hours = intval($file[3]);
if (intval($file[0]) === $targetYear and floatval($file[4]) <= $targetTemp) {
???
return ??? / 24;
}
fclose($inputFile);
}
return "error";
}
The problem is I don't know how to iterate through a column which represents the hours of a specific year, and then add them all together.

you are not far from the solution.
function getDaysUnderTemp($targetYear, $targetTemp): float
{
$hours = 0;
if ((($handle = fopen("data.csv", "r")) !== FALSE)) {
while (($data = fgetcsv($handle)) !== FALSE) {
// [0] -> year [1] -> month [2] -> day [3] -> hours [4] -> temp
if ($data[0] == $targetYear && $data[4] <= $targetTemp) {
$hours += $data[3];
}
}
fclose($handle);
}
return $hours / 24;
}
The logic you had was correct, I just refactored the code and fixed some small issues:
Your function is bound to return a float value but since in your code you are returning the string "error" when a match is not found It would have thrown an exception at some point. I am now returning $hours / 24 so now the problem is solved;
After each iteration, you close the pointer to file by calling fclose($inputFile), this can cause some problems since the feof function needs a valid stream not closed to work properly. Now the fclose function is called only once when the work is done;
For printing the results I suggest you to use the printf function as follows:
printf("%.2f", getDaysUnderTemp(2019, -10);

Related

Create a dictionary (array) from CSV data [duplicate]

This question already has an answer here:
Iterating through a column in a CSV file (PHP)
(1 answer)
Closed 9 hours ago.
I need to write a function that takes temperature as an input and returns a dictionary with years as keys and number of days as values.
CSV file looks like this (year, month, day, hour, temperature):
2019,1,1,0,0.1
2019,1,1,1,0.4
2019,1,1,2,0.8
2019,1,1,3,1.3
2019,1,1,4,1.8
...
2020,1,1,0,-3.9
The number of days is calculated by another function which I already have. It takes a year and a temperature and returns how many days in a given year the temperature was equal to or below the given temperature. Since the data is about hours, not days, the number of hours is found and then divided by 24.
The function:
function getDaysUnderTemp(int $targetYear, float $targetTemp): float {
$file = fopen("data/temperatures-filtered.csv", "r");
$hours = 0;
while ($data = fgetcsv($file)) {
if ($data[0] == $targetYear and $data[4] <= $targetTemp) {
$hours ++;
}
}
fclose($file);
return $hours / 24;
}
So as an example getDaysUnderTemp(2019, -10) returns 13.92.
This is a function I am asking about as I'm not sure how it might be done:
function getDaysUnderTempDictionary(float $targetTemp): array {
$file = fopen("data/temperatures-filtered.csv", "r");
while ($data = fgetcsv($file)) {
???
}
fclose($file);
return [];
}
The problem is I don't understand how an already written function could be implemented in this new one, and then create a required dictionary from all this data. Without using classes.
Desired output of getDaysUnderTempDictionary(-10):
Array
(
[2019] => 3.88
[2020] => 0.21
[2021] => 13.92
)
Instead of just storing hours as a single variable, store it in an array indexed by year. Once done with the CSV, loop over the array and divide each by 24, same as you did before.
This code has a mock CSV file for demo purposes, but is otherwise the same as yours:
function getDaysUnderTempDictionary(float $targetTemp): array {
//This is just for mocking a CSV file
$dataString = <<<EOT
2019,1,1,0,0.1
2019,1,1,1,0.4
2019,1,1,2,0.8
2019,1,1,3,1.3
2019,1,1,4,1.8
2020,1,1,0,-3.9
EOT;
$stream = fopen('php://memory', 'r+');
fwrite($stream, $dataString);
rewind($stream);
$years = [];
while ($data = fgetcsv($stream)) {
$year = $data[0];
if ($data[4] <= $targetTemp) {
if(!isset($years[$year])){
$years[$year] = 0;
}
$years[$year]++;
}
}
foreach($years as $year => $hours){
$years[$year] = $hours / 24;
}
return $years;
}
Demo: https://3v4l.org/RWFPK

How to subtract time from time array using php?

Hello seniors I have a question related to some PHP script.
I have an array containing time => ['12:10', '4:16', '2:5'] and have one html form containing input field of type 'number'.
I want when I enter some value in my form input field for example I enter 7, so after submitting the form in back-end the number 7 which i enter in input field is subtracted from the array which I mentioned above and I will get the result array like:
['5:10', '4:16', '2:5']
I have tried something like that but not able to implement my logic
$val = array(1, 0, 2, 1, 1);
$subtract = 3.5;
foreach ($val as $key => $item) {
if ($subtract >= $item) {
$subtract -= $item;
$val[$key] = 0;
} else {
$val[$key] -= $subtract;
$subtract = 0;
}
}
Any kind of help is highly appreciated
You can use Carbon library for date/time manipulation:
<?php
use Carbon\Carbon;
$times = ['17:46', '03:05', '21:56'];
$timeshift = 3;
$new_times = array_map(
fn($t) => Carbon::createFromFormat('H:i', $t)
->subHours($timeshift)
->format('H:i'),
$times
);
Test Carbon library online
No need for library, just convert your first array to seconds: 1 hour = 3600 ; 1 minute = 60 ; 12:10 is 12 x 3600 + 10 x 60, then you do the same thing to your $_POST value, then use gmdate() to retrieve the original format of your array
$myTimes=array('12:10', '4:16', '2:5');
//do the math
$splittedTime = explode(":", $myTimes[0]); //in your case
$timeInSeconds = $splittedTime[0] * 3600 + $splittedTime[1] * 60 ;
//do the same thing to your your $_POST value if needed or simply
$totalReduceby = 7 * 3600;
// get new total of seconds
$newTime= $timeInSeconds - $totalReduceby;
$result = ltrim(gmdate("H:i", $newTime),0); //ltrim to remove the leading 0
$myTimes=array($result, '4:16', '2:5');
//print_r($myTimes);
time => ['12:10', '4:16', '2:5']
[...]
the number 7 which i enter in input field is subtracted from the array
I will get the result array like: ['5:10', '4:16', '2:5']
Your example is a little ambiguous. Do you only want to subtract the field value from the first element of the array, always? Or only from those elements which are greater than the submitted value?
It's pretty straightforward to subtract minutes from a mm:ss time string; simplest is probably to generalize so that the amount to subtract is also allowed to be mm:ss instead of always being a whole number of minutes. I would just explode both of them, turn them into total seconds (minutes*60+seconds), subtract those, and then turn back into mm:ss. Both conversions might be worth their own functions:
function mmssToSeconds($timeStr) {
if (str_contains($timeStr, ':')) {
list($min, $sec) = explode(':', $timeStr);
} else {
list($min, $sec) = array($timeStr, 0);
}
if ($min < 0) {
return 60*$min - $sec;
} else {
return 60*$min + $sec;
}
}
function secondsToMmss($seconds) {
$abs = abs($seconds);
$sgn = $seconds / $abs;
$min = floor($abs / 60);
$sec = $abs % 60;
return ($sgn < 0 ? '-' : '').sprintf('%d:%02d', $min, $sec);
}
And then the subtraction is easy:
function subtractMinutes($from, $delta) {
return secondsToMmss(mmssToSeconds($from) - mmssToSeconds($delta));
}
If you want to subtract from each element that is big enough, you could use a loop like this:
foreach ($ary['time'] as $i => $t) {
if ((int)$t > $subtract) {
$ary['time'][$i] = subtractMinutes($t, $subtract);
}
}
The comparison works because the cast from string to int ignores everything after the first non-digit, so '12:10' just becomes 12, which is > 7.

Checking array for duplicate of 2 objects

I have a small program sunning that reads in a csv file with customers and their bookings and places them into a nested array, I have already sorted some of the data, but now need to make sure that there isn't any customers booking for the same date and time (also is it possible to check that the time is at least 30 min apart, all the times are in 30 min increments) I've tried a simple in_array method and got nothing tried a few other ways and didn't get any where, any ideas how I could do this. $date[5] is the dates and $data[6] is the time, the data is all strings.
if (($h = fopen("{$filename}", "r")) !== FALSE) {
//each line in the file is converted into its own individual array
while (($data = fgetcsv($h, 1000, ",")) !==FALSE) {
//check if anyone has tried to book for unavailable dates, weekends, last day of the month etc
if (!in_array($data[5], $Adates)) {
$contact[] = $data;
}
//this checks if the person is trying to make a booking before 9am or after 6pm
elseif ((($data[6]) < "09:00") || (($data[6]) > "18:00")) {
$contact[] = $data;
} else {
$College1[] = $data;
}
if (in_array($data[5], $College1)
&& in_array($data[6],$College1)) {
$College2[] = $College1;
}
Heres a sample of some of the test data i used (there is over 30 customers) :
T1,Aleshia,Tomkiewicz,atomkiewicz#hotmail.com,01835-703597,03/11/2020,11:30,P256
T2,Evan,Zigomalas,evan.zigomalas#gmail.com,01937-864715,12/11/2020,15:00,P146
T3,France,Andrade,france.andrade#hotmail.com,01347-368222,30/11/2020,08:30,P68
In regards to your code, I guess you could improve your elseif manipulating the time string, getting just the hour and comparing that number as an int.
elseif ((($data[6]) < "09:00") || (($data[6]) > "18:00"))
$hrBooked = (int) explode(':', $data[6])[0]; // This should be placed before the main if
elseif (( $hrBooked < 9) || $hrBooked > 18))
Looking at your code, I think you'll benefit from using the DateTime PHP class for all your checks and comparisons.

How to run php file for only 10 times a day without cron?

I am new to php so please mind if it easy question. I have a php script, I want it to be executed only 10 times a day and not more than that. I don't want to use cron for this. Is there any way to do this in php only?
Right now I have set a counter which increases by one every time any one runs the script and loop it to 10 times only. if it exceeds it it shows an error message.
function limit_run_times(){
$counter = 1;
$file = 'counter.txt';
if(file_exists($file)){
$counter += file_get_contents($file);
}
file_put_contents($file,$counter);
if($counter > 11 ){
die("limit is exceeded!");
}
}
I want some efficient way to do this so everyday the script is only executed for 10 times and this is applicable for everyday i.e this counter gets refreshed to 0 everyday or is there any other efficient method.
I would rather recommend that you use a database instead - its cleaner and more simple to maintain.
However, it is achievable with file-handling as well. The file will be of format 2019-05-15 1 (separated by tab, \t). Fetch the contents of the file and split the values by explode(). Then do your comparisons and checks, and return values accordingly.
function limit_run_times() {
// Variable declarations
$fileName = 'my_log.txt';
$dailyLimit = 10;
$content = file_get_contents($fileName);
$parts = explode("\t", $content);
$date = $parts[0];
$counter = $parts[1] + 1;
// Check the counter - if its higher than 10 on this date, return false
if ($counter > $dailyLimit && date("Y-m-d") === $date) {
die("Daily executing limit ($dailyLimit) exceeded! Please try again tomorrow.");
}
// We only get here if the count is $dailyLimit or less
// Check if the date is today, if so increment the counter by 1
// Else set the new date and reset the counter to 1 (as it is executed now)
if (date("Y-m-d") !== $date) {
$counter = 1;
$date = date("Y-m-d");
}
file_put_contents($fileName, $date."\t".$counter);
return true;
}

Finding the nearest data point and sunshine data in php

I am in a big problem.The problem is following
1)I have a .dat file(dat file used to store the data ).It is a 25 MB file which contains
over 200K rows of latitude and longitudes.Example of one of such row is
-59.083 -26.583 9.4 5.2 3.3 4.3 8.1 6.6 5.3 8.4 8.3 10.0 9.1 5.1
It statrs form the latitude ,longitude,sunshine data (hours) in january,fabruary..and so on to decmber.
I have more than 200K rows like above.
My task is to calculate the sunshine hours in a particulr latitude and longitude.Suppose i have a latitude =3.86082 and longitude=100.50509 ;.So my task would be to find average sunshine hours per month on this latitude and longitude.but the second problem is that i am not going to have exact match of given latitude and longitude with the ones i have in the file.So first of all i have to find the nearest point and then i have to calculate the sunshine hours.
I am using the following code to calculate the nearest point.But it is taking a huge time beacuse of the bulk of data in the file
$file_name='grid_10min_sunp.dat';
$handle = fopen($file_name, "r");
$lat1=13.86082;
$lan1=100.50509;
$lat_lon_sunshines = make_sunshine_dict($file_name);
$closest = 500;
for($c=0;$c<count($lat_lon_sunshines);$c++)
{
$lat2=$lat_lon_sunshines[$c]['lat'];
$lan2=$lat_lon_sunshines[$c]['lan'];
$sunshines=$lat_lon_sunshines[$c]['sunshine'];
$lat_diff = abs(round((float)($lat1), 4)-$lat2);
if ($lat_diff < $closest)
{
$diff = $lat_diff + abs(round((float)($lan1), 4)-$lan2);
if($diff < $closest)
{
$closest = $diff;
$sunshinesfinal=$sunshines;
}
}
$sunshines='';
}
print_r($sunshinesfinal);die;
the function make_sunshine_dict($file_name ) also goes throgh each line of the file and prepares an array as following
$sunshines_dict = array();
$f = file_get_contents($file_name);
$handle = fopen($file_name, "r");
while($buffer = fgets($handle))
{
$tok = strtok($buffer, " \n\t");
$lat=$tok;
$latArray[]=$tok;
$tok = strtok(" \n\t");
$months = '';
$months = array();
for ($k = 0; $tok !== false; $k+=1)
{
if($k==0)
{
$lan=$tok;
$lanArray[]=$tok;
}
if($k!=0)
{
$months[] = $tok ;
"month $k : ".$months[$k]."<br>";
}
$tok = strtok(" \n\t");
}
$data[$kkk]['lat']=$lat;
$data[$kkk]['lan']=$lan;
foreach($months as $m=>$sunshine)
{
$sunshines=array();
$sumD = 0;
$iteration= 31;
for($n=1;$n<=$iteration;$n++)
{
$J = ($m+1)*$n;
$P = asin(.39795*cos(.2163108 + 2*atan(.9671396*tan(.00860*($J-186)))));
$value=(sin(0.8333*pi/180) + sin($lat*pi/180)*sin($P))/(cos($lat*pi/180)*cos($P));
/* $value ? ($value > 1 and 1) : $value;
$value ? ($value < -1 and -1): $value;*/
$D = 24 - ((24/pi) * acos($value));
$sumD = $sumD + $D;
}
$sunshinesdata=(($sumD/30)*(float)($sunshine)*.01);
$data[$kkk]['sunshine'][$m]=$sunshinesdata;
$sunshines='';
}
}
return $data;
Please help and please let me know if you require more information
And please remenber i can not use default php function for sunshine information here beacsue i am also taking cloud cover and other factors into consideration
A lot of the code looks wrong (in terms of just figuring out the sunshine hours). Although, I'll admit, I'm lost as to what you are doing in the make_sunshine_dict function.
In terms of helping you speed things up, you are reading your file twice:
$f = file_get_contents($file_name);
$handle = fopen($file_name, "r");
Also, you can map Lat/Long coordinates into a grid. In other words, make a 2 dimensional array where each row and each column represents 1 degree of latitude and 1 degree of longitude, respectively. As you read in your file, dump entries into the correct lat/long bucket in the grid. When you need to find the location closest to a given lat/lon then you only need to compare that location against the points in its bucket.
Load the .dat into a SQL database every x interval in a Cron job and then just query the database like you always would.

Categories