preg_split to unknown number of list() elements - php

I have a PHP script that I wrote probably 10 years ago. I don't remember what version PHP was on at the time but my script worked just fine without complaint from the interpreter. Now, I've had to move my scripts to a new web host and, under PHP 7.x, the interpreter complains loudly about a certain line of this script and I'm looking for an elegant way to get it to shut up.
The offending line is:-
list($degrees, $minutes, $seconds) = preg_split("/ /", $coord);
The $coord variable contains a GPS coordinate in one of three forms: "degrees minutes seconds", "degrees decimal-minutes", or "decimal-degrees". So, the preg_split() may return 1, 2, or 3 elements. If it returns only 1 or 2 elements, the interpreter complains loudly about the undefined references to $seconds and/or $minutes. I see that there is a LIMIT parameter that I could specify for preg_split() that gives it a maximum number of elements to return but there doesn't seem to be a complimentary parameter to tell it the MINIMUM number of elements to return. Any suggestions welcome.
Sample coords: '-97.74019' or '-97 44.411' or '-97 44 24.7'

Totally agree with Anant, but you can do it in bit more elegant way:
<?php
$coordArray = preg_split('/ /', $coord);
$degrees = $coordArray[0] ?? 0;
$minutes = $coordArray[1] ?? 0;
$seconds = $coordArray[2] ?? 0;

The line gives a E_NOTICE ("Notice: Undefined offset: X in Command line code on line 1"). You can either change the error reporting level to not include E_NOTICE or just hide disable the error reporting for this particular line with the # operator. There is no harm in using # here. Unmatched variables will be assigned NULL (in 5 and 7):
$coord = "x y";
#list($degrees, $minutes, $seconds) = preg_split("/ /", $coord);
var_dump($degrees, $minutes, $seconds);
Gives:
string(1) "x"
string(1) "y"
NULL
I wouldn't generally recommend it, but to suppress all E_NOTICE error notices, you can unset it in the error_reporting setting:
ini_set("error_reporting", E_ALL&~E_NOTICE);

You can convert that code like below:-
$d_m_s_array = preg_split("/ /", $coord);
$degrees = (!empty($d_m_s[0]) ? $d_m_s[0] :0;
$minutes = (!empty($d_m_s[1]) ? $d_m_s[1] :0;
$seconds = (!empty($d_m_s[2]) ? $d_m_s[2] :0;
Note:-
Also your original code will generate a notice only (if error reporting is on for that too). Look here:- https://eval.in/707979
It will not stop the program execution, but you can+have-to resolve this notice by the above code

Wrap the variably-sized array with array_pad as demonstrated below:
list($degrees, $minutes, $seconds) = array_pad( preg_split("/ /", $coord), 3, null);
Your intuition is right that you need a MINIMUM number of elements to return, and that is exactly what array_pad will do for you.

Related

generate a random string with with exactly 6 digits (show first 0)

I am trying to generate a string with exactly 6 random numbers in it. My current code:
$test = sprintf('%6d', rand(1, 1000000));
With this code I get a string that sometimes has an empty value at the beginning like " 53280". I would want to have it produce "053280" in that case. How to achieve this?
You should add a 0 in your conversion specification to indicate that you want zero-padding:
$test = sprintf('%06d', rand(1, 1000000));
// ^-- here
The conversion specifications are documented on the sprintf manual page.
If you don't want to use sprintf (some dont!), an alternative way to do it would be:
$test = str_pad(mt_rand(1, 999999),6,0,STR_PAD_LEFT);
Example output:
736523
024132
003145
Using mt_rand here because its a better random number function (not perfect, but better than just rand). Also adjusted to 999999 since 1000000 could possibly produce a 7 digit number.
Doing a benchmark of 10000 iterations on the three answers provided (Sean, Mine, Aslan), these are the results in speed:
Sean's Method: 0.005
My Method: 0.006
Aslan's Method: 0.009
So you would be better off going with Sean's method.
You can just replace the empty character with 0.
$test = str_replace(" ", "0", sprintf('%6d', rand(1, 1000000)));

Undefined offset error (PHP/Regex)

The script below assigns numerical ID's to paragraphs (e.g. [p id="1"]) in articles extracted from my database, except for the last paragraph, which is [p id="last].
$c = 1;
$r = preg_replace_callback('/(<p( [^>]+)?>)/i', function ($res) {
global $c;
return '<p'.$res[2].' id="'.intval($c++).'">';
}, $text);
$r = preg_replace('/(<p.*?)id="'.($c-1).'"(>)/i', '\1id="Last"\2', $r);
$text = $r;
It works, but when I have my error reporting on, I get the following error Undefined offset: 2. It isn't critical, but it's kind of a nuisance when I'm testing my pages. Any idea how I can kill it?
I've improved the regex by:
Removing a group /<p( [^>]+)?>/i
Changing ( [^>]+)? to ([^>]*). This way you don't have an optional group, but the characters inside this group is optional. Which means you will always have this group.
Just a preference, I change the delimiters ~<p([^>]*)>~i
Now let's attack the php code:
$text = '<p>test</p> another <p class="test">test</p> and another one <p style="color:red">';
$c = 1;
$r = preg_replace_callback('~<p([^>]*)>~i', function($res) use (&$c){
return '<p'.$res[1].' id="'.$c++.'">';
}, $text);
var_dump($r, $c);
Note that I used a closure use (&$c) with a reference &. This way we can update $c.
Online demo

preg_match to find a word that ends in a certain character

I am trying to make a small program that searches within a timer (with this format 00d 00h 00m 00s) and returns the days into one variable, the hours into another, etc.
This is some of my code:
$time1 = "Left: 11d 21h 50m 06s <\/div>"
preg_match_all("/ .*d/i", $time1, $timematch); // Day
$time1day = $timematch[1]; // Saves to variable
preg_match_all("/ .*h/i", $time1, $timematch); // Hour
$time1hour = $timematch[1]; // Saves to variable
preg_match_all("/ .*m/i", $time1, $timematch); // Minute
$time1minute = $timematch[1]; // Saves to variable
preg_match_all("/ .*s/i", $time1, $timematch); // Second
$time1second = $timematch[1]; // Saves to variable
My regex isn't correct but I'm not sure what it should be. Any ideas?
By the way, I am using PHP4.
This regex will do the trick:
(\d+)d (\d+)h (\d+)m (\d+)s
Each value (day, hour, minute, second) will be captured in a group.
About your regex: I don't know what do you mean by "isn't correct", but I guess it's probably failing because your regex is greedy instead of lazy (more info). Try using lazy operators, or using more specific matches (\d instead of ., for example).
EDIT:
I need them to be separate variables
After matching, they will be put in different locations in the resulting array. Just assign them to variables. Check out an example here.
If you have trouble understanding the resulting array structure, you may want to use the PREG_SET_ORDER flag when calling preg_match_all (more information here).
If the format is always in the order you show, I wouldn't regex it. The following should get your job done:
$time1= "Left: 11d 21h 50m 06s <\/div>";
$timeStringArray = explode(" ",$timeString);
$time1day = str_replace("d","",$timeStringArray[1]);
$time1hour = str_replace("h","",$timeStringArray[2]);
$time1minute = str_replace("m","",$timeStringArray[3]);
$time1second = str_replace("s","",$timeStringArray[4]);
If the pattern is always this, two digits plus the time letter, you can do this:
$time1 = "Left: 11d 21h 50m 06s <\/div>";
preg_match_all("/(\d{2})[dhms]/", $time1, $match);
print_r($match);
UPDATE: this function can work with 1 or 2 digits, and match all the params.
$time1 = "Left: 11d 21h 50m 06s <\/div>";
$time2 = "Left: 21h 50m 5s";
$time3 = "Left: 9m 15s";
function parseTime($str) {
$default = array('seconds', 'minutes', 'hours', 'days');
preg_match_all("/(\d{1,2})[dhms]/", $str, $time);
if (!isset($time[1]) || !is_array($time[1])) {
return null;
}
$default = array_slice($default, 0, count($time[1]));
return array_combine($default, array_reverse($time[1]));
}
print_r(parseTime($time1));
print_r(parseTime($time2));
print_r(parseTime($time3));

Echo text directly after variable

I have 2 variables named $min and $sec.
If $min = 5 and $sec = 15 I want to print this "5m 15s", echo "$min m $sec s";
But that gives "5 m 15 s". How do I get rid of the blank space?
You can use braces to do this, same as many shells:
$min = 7;
$sec = 2;
echo "${min}m ${sec}s";
The output of that is:
7m 2s
The braces serve to delineate the variable name from the following text in situations where the "greedy" nature of variables would cause problems.
So, while $minm tries to give you the contents of the minm variable, ${min}m will give you the contents of the min variable followed by the literal m.
echo $min."m ".$sec."s"; is one way of doing it
u can try this
echo $min."m ".$sec."s ";
edit>
the output is
5m 15s

Whats the cleanest way to convert a 5-7 digit number into xxx/xxx/xxx format in php?

I have sets of 5, 6 and 7 digit numbers. I need them to be displayed in the 000/000/000 format. So for example:
12345 would be displayed as 000/012/345
and
9876543 would be displayed as 009/876/543
I know how to do this in a messy way, involving a series of if/else statements, and strlen functions, but there has to be a cleaner way involving regex that Im not seeing.
sprintf and modulo is one option
function formatMyNumber($num)
{
return sprintf('%03d/%03d/%03d',
$num / 1000000,
($num / 1000) % 1000,
$num % 1000);
}
$padded = str_pad($number, 9, '0', STR_PAD_LEFT);
$split = str_split($padded, 3);
$formatted = implode('/', $split);
You asked for a regex solution, and I love playing with them, so here is a regex solution!
I show it for educational (and fun) purpose only, just use Adam's solution, clean, readable and fast.
function FormatWithSlashes($number)
{
return substr(preg_replace('/(\d{3})?(\d{3})?(\d{3})$/', '$1/$2/$3',
'0000' . $number),
-11, 11);
}
$numbers = Array(12345, 345678, 9876543);
foreach ($numbers as $val)
{
$r = FormatWithSlashes($val);
echo "<p>$r</p>";
}
OK, people are throwing stuff out, so I will too!
number_format would be great, because it accepts a thousands separator, but it doesn't do padding zeroes like sprintf and the like. So here's what I came up with for a one-liner:
function fmt($x) {
return substr(number_format($x+1000000000, 0, ".", "/"), 2);
}
Minor improvement to PhiLho's suggestion:
You can avoid the substr by changing the regex to:
function FormatWithSlashes($number)
{
return preg_replace('/^0*(\d{3})(\d{3})(\d{3})$/', '$1/$2/$3',
'0000' . $number);
}
I also removed the ? after each of the first two capture groups because, when given a 5, 6, or 7 digit number (as specified in the question), this will always have at least 9 digits to work with. If you want to guard against the possibility of receiving a smaller input number, run the regex against '000000000' . $number instead.
Alternately, you could use
substr('0000' . $number, -9, 9);
and then splice the slashes in at the appropriate places with substr_replace, which I suspect may be the fastest way to do this (no need to run regexes or do division), but that's really just getting into pointless optimization, as any of the solutions presented will still be much faster than establishing a network connection to the server.
This would be how I would write it if using Perl 5.10 .
use 5.010;
sub myformat(_;$){
# prepend with zeros
my $_ = 0 x ( 9-length($_[0]) ) . $_[0];
my $join = $_[1] // '/'; # using the 'defined or' operator `//`
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Tested with:
$_ = 11111;
say myformat;
say myformat(2222);
say myformat(33333,';');
say $_;
returns:
000/011/111
000/002/222
000;033;333
11111
Back-ported to Perl 5.8 :
sub myformat(;$$){
local $_ = #_ ? $_[0] : $_
# prepend with zeros
$_ = 0 x ( 9-length($_) ) . $_;
my $join = defined($_[1]) ? $_[1] :'/';
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Here's how I'd do it in python (sorry I don't know PHP as well). I'm sure you can convert it.
def convert(num): #num is an integer
a = str(num)
s = "0"*(9-len(a)) + a
return "%s/%s/%s" % (s[:3], s[3:6], s[6:9])
This just pads the number to have length 9, then splits the substrings.
That being said, it seems the modulo answer is a bit better.

Categories