Parsing time format - php

How do I calculate the input to time in seconds, when the input is in different formats as shown in the example output below?
With this format, all parts are optional:
5d 4h 3m 2s -> 5 days, 4 hours, 3 minutes and 2 seconds
4h 2s -> 4 hours and 2 seconds
3m -> 3 minutes
With this format, parts are sometimes optional:
05: 04: 03: 02 -> 5 days, 4 hours, 3 minutes and 2 seconds
4:03:02 -> 4 hours, 3 minutes and 2 seconds
05: 00: 03: 02 -> 5 days, 3 minutes and 2 seconds
02 -> 2 seconds
I'd like to get this output:
> php time.php 1d 6h 20m 2s
> 109.202 seconds
> php time.php 3:50
> 230 seconds
> php time.php 4: 23: 45: 02
> 431.102 seconds
> php time.php 23m
> 1.380 seconds
So far I made it possible to convert input seconds to a time format, but after 5 hours of trying to make the above question work I kind of gave up:
<?php
$setTime = $argv[1];
function format_time($t,$f=':') {
return sprintf("%02d%s%02d%s%02d", floor($t/3600), $f, ($t/60)%60, $f, $t%60);
}
echo format_time($setTime);
?>

You could use preg_match_all to split the input into pairs of numbers and suffixes and then iterate those to count the number of seconds:
function seconds($input) {
preg_match_all("/(\d+)(\S)/", "$input:", $matches, PREG_SET_ORDER);
$letters = "smhd";
$durations = ["s" => 1, "m" => 60, "h" => 60*60, "d" => 60*60*24];
$seconds = 0;
foreach (array_reverse($matches) as list($all, $num, $code)) {
$i = strpos($letters, $code);
if ($i === false) $code = $letters[$i = 0];
$letters = substr($letters, $i+1);
$seconds += $durations[$code] * $num;
}
return $seconds;
}
function test($input) {
$seconds = number_format(seconds($input));
echo "$input => $seconds seconds\n";
}
test("1d 6h 20m 2s"); // 109.202 seconds
test("3:50"); // 230 seconds
test("4: 23: 45: 02"); // 431.102 seconds
test("23m"); // 1.380 seconds
3v4l.org demo

The following might be total overkill, but it also might illustrate how you can approach this problem if you want your code to be extendable and easy to reuse:
<?php
declare(strict_types=1);
error_reporting(-1);
ini_set('display_errors', 'On');
interface TimeStringParser
{
public function accepts(string $input): bool;
public function parse(string $input): int;
}
final class InputWithUnits implements TimeStringParser
{
public function accepts(string $input): bool
{
foreach (preg_split('/\s+/', $input) as $chunk) {
if (! preg_match('/^\d+(d|h|m|s)$/', $chunk)) {
return false;
}
}
return true;
}
public function parse(string $input): int
{
if (! $this->accepts($input)) {
throw new \InvalidArgumentException('Invalid input.');
}
$result = 0;
if (preg_match_all('/((?<value>\d+)(?<unit>d|h|m|s))/', $input, $matches)) {
foreach ($matches['unit'] as $i => $unit) {
$value = (int) $matches['value'][$i];
switch ($unit) {
case 'd': $result += $value * 86400; break;
case 'h': $result += $value * 3600; break;
case 'm': $result += $value * 60; break;
case 's': $result += $value * 1; break;
}
}
}
return $result;
}
}
final class InputWithoutUnits implements TimeStringParser
{
public function accepts(string $input): bool
{
foreach (explode(':', $input) as $chunk) {
if (! preg_match('/^\d+$/', trim($chunk))) {
return false;
}
}
return true;
}
public function parse(string $input): int
{
if (! $this->accepts($input)) {
throw new \InvalidArgumentException('Invalid input.');
}
$multiplier = [1, 60, 3600, 86400];
$result = 0;
foreach (array_reverse(explode(':', $input)) as $chunk) {
$value = (int) trim($chunk);
$result += $value * array_shift($multiplier);
}
return $result;
}
}
final class ParserComposite implements TimeStringParser
{
private $parsers;
public function __construct(TimeStringParser ...$parsers)
{
$this->parsers = $parsers;
}
public function accepts(string $input): bool
{
foreach ($this->parsers as $parser) {
if ($parser->accepts($input)) {
return true;
}
}
return false;
}
public function parse(string $input): int
{
foreach ($this->parsers as $parser) {
if ($parser->accepts($input)) {
return $parser->parse($input);
}
}
throw new \InvalidArgumentException('Invalid input.');
}
}
$parser = new ParserComposite(
new InputWithUnits(),
new InputWithoutUnits()
);
$testCases = [
'5d 4h 3m 2s',
'4h 2s',
'3m',
'05: 04: 03: 02',
'4:03:02',
'05: 00: 03: 02',
'02',
'23m',
'2e-5'
];
foreach ($testCases as $testCase) {
if ($parser->accepts($testCase)) {
printf("%-'.20s: %8d\n", $testCase, $parser->parse($testCase));
}
else {
printf("%-'.20s: unsupported\n", $testCase);
}
}
https://3v4l.org/qAYqD

Related

Dictionary from CSV (PHP)

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 (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.
Desired output:
getDaysUnderTempDictionary(-10);
Array
(
[2019] => 3.88
[2020] => 0.21
[2021] => 13.92
)
If I were doing this, I would read all the data into an array in raw form
then produce whatever other structures I needed.
I am going to write this as a class:
class TempCalculator {
private $data;
private $arr;
const YEAR = 0;
const TEMP = 4;
public function __construct($filename) {
$this->data = array_map('str_getcsv', file($filename));
}
public function getHoursBelowTemperature($temperature) : array {
$this->arr = array();
foreach ($this->data as $row) {
$year = $row[self::YEAR];
$temperatureValue = floatval($row[self::TEMP]);
if ($temperatureValue < $temperature) {
if (!array_key_exists($year, $this->arr)) {
$this->arr[$year] = 0;
}
$this->arr[$year]++;
}
}
// Walk the array and divide all the hours by 24
array_walk($this->arr, function (&$value) {
$value /= 24;
});
return $this->arr;
}
public getDaysUnderTemp($year, $temp) : float {
$hours = 0;
foreach ($this->data as $row) {
if ($row[self::YEAR] == $targetYear && $row[self::TEMP] <= $targetTemp) {
$hours ++;
}
}
return $hours / 24;
}
}
You can call it like this:
$tempCalculator = new TempCalculator('temperatures.csv');
$result = $tempCalculator->getHoursBelowTemperature(0);
print_r($result);
$result2 = $tempCalculator->getDaysUnderTemp(1.5);
print_r($result);

How get a (fixed) value based on number range

I have a number, e.g. $humidity
I need to check value of this number and get a fixed value if number is in a predetermined range.
E.g.
if ($humidity<30) {
return 'dry';
}
if ($humidity>=30 && $humidity <=40) {
return 'wet'
}
if ($humidty>40 && $humidity<70) {
return 'confortable'
}
And so on.
Is there another possibility to don't use 4/5 different if ?
As long as you process the values in order, you don't need both the upper and lower values of each range. Then you can utilize short-circuiting and just put everything in a loop:
function nameOf($humidity)
{
$list = [
30 => 'dry',
40 => 'wet',
70 => 'comfortable',
];
foreach ($list as $value => $name) {
if ($humidity < $value) {
return $name;
}
}
return 'default';
}
I think switch is great for this usage:
$result = null;
switch(true) {
case $humidity < 30:
$result = 'dry';
break;
case $humidity >= 30 && $humidity < 40:
$result = 'wet';
break;
case $humidty > 40 && $humidity < 70:
$result = 'comfortable';
break;
}
return $result;
you can create an range array for your temps and then array_walk that to find the right range.
$h=40;
$harr = [ 0 => 'dry', 1 => 'wet', 2 => 'confy'];
$range = array_flip(range(0, 100, 30));
array_walk($range, 'findHumidity', $range);
var_dump($harr[$result]);
function findHumidity($value, $key, $r){
global $h; //if you using a class and properties you can ignore these two lines
global $result;
$normalRange = array_flip($r);
if($h> $key && $normalRange[$value+1]!=null && $h<= $normalRange[$value+1]){
$result = $value;
}
}
Working example: https://3v4l.org/fWLDu (a simplified version: https://3v4l.org/ROjWk)
or you can define the range array manually like here: https://3v4l.org/B0oTU

PHP find median using heap vs sort

I was looking for a quick way to calculate the median of a list of numbers and came across this:
function array_median($array) {
// perhaps all non numeric values should filtered out of $array here?
$iCount = count($array);
if ($iCount == 0) {
return null;
}
// if we're down here it must mean $array
// has at least 1 item in the array.
$middle_index = floor($iCount / 2);
sort($array, SORT_NUMERIC);
$median = $array[$middle_index]; // assume an odd # of items
// Handle the even case by averaging the middle 2 items
if ($iCount % 2 == 0) {
$median = ($median + $array[$middle_index - 1]) / 2;
}
return $median;
}
This approach using sort() makes sense and is certainly the obvious approach. However, I was curious if a median heap would be faster. What was surprising was that when I implemented a simple median heap it is consistently significantly slower than the above method.
My simple MedianHeap class:
class MedianHeap{
private $lowerHeap;
private $higherHeap;
private $numbers = [];
public function __construct($numbers = null)
{
$this->lowerHeap = new SplMaxHeap();
$this->higherHeap = new SplMinHeap();
if (count($numbers)) {
$this->insertArray($numbers);
}
}
public function insertArray ($numbers) {
foreach($numbers as $number) {
$this->insert($number);
}
}
public function insert($number)
{
$this->numbers[] = $number;
if ($this->lowerHeap->count() == 0 || $number < $this->lowerHeap->top()) {
$this->lowerHeap->insert($number);
} else {
$this->higherHeap->insert($number);
}
$this->balance();
}
protected function balance()
{
$biggerHeap = $this->lowerHeap->count() > $this->higherHeap->count() ? $this->lowerHeap : $this->higherHeap;
$smallerHeap = $this->lowerHeap->count() > $this->higherHeap->count() ? $this->higherHeap : $this->lowerHeap;
if ($biggerHeap->count() - $smallerHeap->count() >= 2) {
$smallerHeap->insert($biggerHeap->extract());
}
}
public function getMedian()
{
if (!count($this->numbers)) {
return null;
}
$biggerHeap = $this->lowerHeap->count() > $this->higherHeap->count() ? $this->lowerHeap : $this->higherHeap;
$smallerHeap = $this->lowerHeap->count() > $this->higherHeap->count() ? $this->higherHeap : $this->lowerHeap;
if ($biggerHeap->count() == $smallerHeap->count()) {
return ($biggerHeap->top() + $smallerHeap->top())/2;
} else {
return $biggerHeap->top();
}
}
}
And then the code to benchmark:
$array = [];
for($i=0; $i<100000; $i++) {
$array[] = mt_rand(1,100000) / mt_rand(1,10000);
}
$t = microtime(true);
echo array_median($array);
echo PHP_EOL . 'Sort Median: ' . (microtime(true) - $t) . ' seconds';
echo PHP_EOL;
$t = microtime(true);
$list = new MedianHeap($array);
echo $list->getMedian();
echo PHP_EOL . 'Heap Median: '. (microtime(true) - $t) . ' seconds';
Is there something in PHP that makes using heaps for this inefficient somehow or is there something wrong with my implemenation?

php numbers like (10M, ..)

I would like to work with numbers like 10M (which represents 10 000 000), and 100K etc. Is there a function that already exists in PHP, or do I have to write my own function?
I'm thinking something along the lines of :
echo strtonum("100K"); // prints 100000
Also, taking it one step further and doing the opposite, something to translate and get 100K from 100000?
You could whip up your own function, because there isn't an builtin function for this. To give you an idea:
function strtonum($string)
{
$units = [
'M' => '1000000',
'K' => '1000',
];
$unit = substr($string, -1);
if (!array_key_exists($unit, $units)) {
return 'ERROR!';
}
return (int) $string * $units[$unit];
}
Demo: http://codepad.viper-7.com/2rxbP8
Or the other way around:
function numtostring($num)
{
$units = [
'M' => '1000000',
'K' => '1000',
];
foreach ($units as $unit => $value) {
if (is_int($num / $value)) {
return $num / $value . $unit;
}
}
}
Demo: http://codepad.viper-7.com/VeRGDs
If you want to get really funky you could put all that in a class and let it decide what conversion to run:
<?php
class numberMagic
{
private $units = [];
public function __construct(array $units)
{
$this->units = $units;
}
public function parse($original)
{
if (is_numeric(substr($original, -1))) {
return $this->numToString($original);
} else {
return $this->strToNum($original);
}
}
private function strToNum($string)
{
$unit = substr($string, -1);
if (!array_key_exists($unit, $this->units)) {
return 'ERROR!';
}
return (int) $string * $this->units[$unit];
}
private function numToString($num)
{
foreach ($this->units as $unit => $value) {
if (is_int($num / $value)) {
return $num / $value . $unit;
}
}
}
}
$units = [
'M' => 1000000,
'K' => 1000,
];
$numberMagic = new NumberMagic($units);
echo $numberMagic->parse('100K'); // 100000
echo $numberMagic->parse(100); // 100K
Although this may be a bit overkill :)
Demo: http://codepad.viper-7.com/KZEc7b
For second one
<?php
# Output easy-to-read numbers
# by james at bandit.co.nz
function bd_nice_number($n) {
// first strip any formatting;
$n = (0+str_replace(",","",$n));
// is this a number?
if(!is_numeric($n)) return false;
// now filter it;
if($n>1000000000000) return round(($n/1000000000000),1).' trillion';
else if($n>1000000000) return round(($n/1000000000),1).' billion';
else if($n>1000000) return round(($n/1000000),1).' million';
else if($n>1000) return round(($n/1000),1).' thousand';
return number_format($n);
}
?>

php calculator class

I am stuck in resolving a PHP script.
I want to calculate a result from a set of instructions. Instructions comprise of a keyword and a number that are separated by a space per line. Instructions are loaded from file and results are output to the screen. Any number of Instructions can be specified. Instructions are operators (add, divide, subtract, multiply). The instructions will ignore mathematical precedence. The last instruction should be “apply” and a number (e.g., “apply 3”). The calculator is then initialised with that number and the previous instructions are applied to that number.
[Input]
add 2
multiply 3
apply 3
[Output]
15
this is what i have tried but i cant get the logic to complete the methods
class Calculator {
public $result = 0;
public $queue = Array();
public parseString($text) {
// parse input string
$cmds = explode(" ", $text);
foreach($cmds as $cmd) {
$cmd = trim($cmd);
if(!$cmd) continue; // blank or space, ignoring
$this->queue[] = $cmd;
}
// lets process commands
$command = false;
foreach($this->queue as $index => $cmd) {
if(is_number($cmd)) {
// if it's number fire previous command if exists
if(!$command || !method_exists($this, $command)) {
throw new Exception("Unknown command $command");
}
$this->$command($index, $cmd);
}else{
$command = $cmd;
}
}
}
public function apply($index, $number) {
// manipulate $result, $queue, $number
}
public function add($index, $number) {
// manipulate $result, $queue, $number
}
public function substract($index, $number) {
// manipulate $result, $queue, $number
}
}
$calculator = new Calculator();
$calculator->parseString('...');
how can i call or switch the add,divide,multiply,substract and how to distinguish and trigger apply word
any kind of help will be appreciated.
You should process the apply first and then cut it out of your queue array. Before you start looping through with the commands, simply test for the apply command and run it first. This simplifies the whole process.
After many minutes of experimentation and chatting, has been resolved.
<?php
error_reporting(E_ALL);
class Calculator {
public $result = 0;
public $queue = array();
public function parseString($text) {
// parse input string
$split = explode(" ", $text); //This gets your input into new lines
for ($i = 0; $i < count($split); $i += 2) $pairs[] = array($split[$i], $split[$i+1]);
foreach ($pairs as $bits) {
if ($bits[0] == "apply") {
$this->apply($bits[1]); //Set result equal to apply.
$this->queue[] = "output";
} else {
$this->queue[] = $bits; //Set the queue item as an array of (command, value).
}
}
//var_dump($this->queue);
//die;
// lets process commands
foreach ($this->queue as $index => $cmd) {
if ($cmd == "output") {
echo "Answer: " .$this->result;
return;
} else {
switch($cmd[0]) {
case "add":
$this->add($cmd[1]);
break;
case "subtract":
$this->subtract($cmd[1]);
break;
case "multiply":
$this->multiply($cmd[1]);
break;
case "divide":
$this->divide($cmd[1]);
break;
default:
echo "Unknown command!";
break;
}
}
}
}
public function apply($number) {
// manipulate $result, $queue, $number
$this->result = $number;
}
public function add($number) {
// manipulate $result, $queue, $number
$this->result += $number;
}
public function subtract($number) {
// manipulate $result, $queue, $number
$this->result -= $number;
}
public function multiply($number) {
// manipulate $result, $queue, $number
$this->result *= $number;
}
public function divide($number) {
// manipulate $result, $queue, $number
$this->result /= $number;
}
}
?>
Try using the array_shift and array_pop functions:
//pop the apply num off end off the queue
$result= array_pop($this->queue);
//now pop the apply operator off end of the queue
$operator = array_pop($this->queue);
//now get the first operator and numbers using array_shift
$operator = array_shift($this->queue); //get first operator
$num = array_shift($this->queue); //get first number
//loop perform operation on result using number till done.
while($num !== null)
{
$result = $operator($result, $num);
$operator = array_shift($this->queue);
$num = array_shift($this->queue);
}

Categories