how to check if a number starts with two allowed numbers php? - php

How to validate that the numeric string starts the first two numbers only with the following list allowed:
01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, and 30.
$number = 1234567890;
if(ctype_digit($number)){
if (strlen($number) <= 10) {
echo "The registered number is correct";
} else {
echo "The number must have 10 digits";
}
} else {
echo "Number must be digit / numeric only.";
}
What I want to add to this functionality is to validate that said number that is stored in $number must start with the first two numbers that are in the list in order to continue.

Another way is to temporarily cast the number into a string and make use of the regular expression of ^([01]\d|2[0-4]|30) to check whether the number string starts with the specific values you mentioned in the above. You can use PHP's preg_match function to help check whether the string matches the regular expression.
So your code becomes:
if(ctype_digit($number)){
if (strlen($number) <= 10 and preg_match('/^([01]\d|2[0-4]|30)/', (string)$number)) {
echo "The registered number is correct";
} else {
echo "The number must have 10 digits or it begins with incorrect values";
}
} else {
echo "Number must be digit / numeric only.";
}
Regular expression explanation:
^: Matches the pattern from the start of the string
[01]\d: Matches 00, 01, 02, ... , 10, 11, ... , 19
2[0-4]: Matches 20, 21, ... , 24
30: Matches 30
([01]\d|2[0-4]|30): Match either [01]\d, 2[0-4] or 30
preg_match function returns 1 if the regular expression pattern is matched successfully.

If your allowed starting numbers are stored in an array, you could extract the 2 first numbers from $number using substr() and then check with in_array().

You can use regular expressions for this, but something to note: a number starting with a 0 will be interpreted as octal and converted to base 10. To avoid this, you must quote the number:
$number = '055231';
// Fill the list of allowed numbers
$allowed = array_map(function($number) {
// Fill each element to length 2 with '0' to the left
return str_pad($number, 2, '0', STR_PAD_LEFT);
}, range(1, 24));
$allowed[] = 30;
if (preg_match('/^\d{2,10}$/', $number)) {
// At this point we know it contains only numbers
// Extract first two characters
$start = substr($number, 0, 2);
// Check if it's in the allowed list
if (in_array($start, $allowed)) {
echo "The registered number is correct";
} else {
echo "The number is incorrect";
}
} else {
echo "Number must be digit / numeric only.";
}
Regex breakdown:
^ and $ are delimiters. The string must match exactly.
\d. Digit character class. Matches only numbers.
{2,10}. Previous group must be of length between 2 and 10 (since it must start with two digits).
Function reference:
range
array_map
str_pad
in_array
substr

A possible alternate solution could be to hardcode the valid prefixes and check if the first 2 characters of the string is one of them using array_key_exists:
$valid_prefixes = array_fill_keys(array("01", "02", "03", "04", "05",
"06", "07", "08", "09", "10",
"11", "12", "13", "14", "15",
"16", "17", "18", "19", "20",
"21", "22", "23", "24", "30"), true);
function starts_with_valid_prefix($str) {
global $valid_prefixes;
return strlen($str) >= 2 && array_key_exists(substr($str, 0, 2), $valid_prefixes);
}

Related

Split Number by a number format

I truly thinking too long to find the logic/ algorithm to do this thing. Then I just use if else but I know this is bad because there will be too much statement.
I have number format group below to split the input number :
01 (Get 14 digits after this number format)
3101 (Get 6 digits after this number format)
3102 (Get 6 digits)
3202 (Get 6 digits)
13 (Get 6 digits)
15 (Get 6 digits)
11 (Get 6 digits)
21 (Get the rest)
Some of rules :
01 always in first sequence
21 always in last sequence
other number format except 01 and 21 can be in any sequence position.
The same prefix number format cannot be repeat
example, Input Number : 010069008517306731020020001319100421191004091395
The Result Should be :
01 : 00690085173067
3102 : 002000
13 : 191004
21 : 191004091395
Currently I only use IF ELSE statement to get the digits after.
This is my pieces of code using PHP. This code can only handle that example input above. There will be possibility of other sequence number format as per rules, but it will difficult if only use if else statement like this.
$first = substr($input, 0, 2);
if ($first == 01) {
$itemCode = substr($input, 2, 14); // get the 6 digits after 01
$second = substr($input, 16, 4);
if ($second == 3102) {
$quantity = substr($input, 20, 6); // get the 6 digits after 3102
$third = substr($input, 26, 2);
if ($third == 13) {
$packedDate = substr($input, 28, 6); // get the 6 digits after 13
$fourth = substr($input, 34, 2);
if ($fourth == 21) {
$serialNumber = substr($scanner, 36); // get the rest number after 21
}
}
}
}
Is there any good way to solve this thing?
If the prefixes won't repeat, you can use preg_match_all to match the prefixes with each of their trailing digits, using array_combine to create arrays of digits indexed by their prefixes:
$input = '010069008517306731020020001319100421191004091395';
if (preg_match_all('/(01)(\d{14})|(310[12]|3202|1[135])(\d{6})|(21)(\d+)/', $input, $matches)) {
$numbers = array_filter(array_combine($matches[1], $matches[2]) +
array_combine($matches[3], $matches[4]) +
array_combine($matches[5], $matches[6]));
print_r($numbers);
}
else {
echo "Invalid input!";
}
Output:
Array
(
[01] => 00690085173067
[3102] => 002000
[13] => 191004
[21] => 191004091395
)
Demo on 3v4l.org

Validate rating variable, Before processing

I have a rating variable that is sent to PHP using Ajax, I want to validate that variable.
The rating starts from 0.5 to 5, So the possible accepted values would be 0.5, 1, 1.5, 2, .. 5.
I'm trying to check it using Regular Expressions:
$var = 0.5;
$pattern = '/^[0-9]|[0-9.][0-9]$/';
if( ! preg_match($pattern, $var) ){
echo 'Doesn't match!';
}else{
echo 'Matches!';
}
But that would check any number, Or decimal like 1, 0.2, 10000, 1000.99.
So how to limit that to check only if it's 0.5, 1, 1.5, .. 5?
Just look for numbers 0-4 and the optional .5, or a 5.
^(?:[0-4](\.5)?|5)$
Demo: https://regex101.com/r/Bcsl6d/1/
...or if 0 is not valid, do the same as we did with 5.
^(?:0\.5|[1-4](\.5)?|5)$
Explanation:
Your regexs:
^[0-9]|[0-9.][0-9]$
^[1-9]|[1-9.][1-9]$
are just looking for a number numbers, or a number or . and a number. You need the . outside the character class an to be optional, then you need the number after the . to only be a 5 and also optional. The 0-9 also is too large, you really want 1-5, but you can't do an optional .5 after that range because it would make 5.5 valid.
You also could just the 2 PHP functions for this range and in_array. The third parameter of range is the step to increase the values by.
$values = range(.5, 5, .5);
if(in_array('5', $values)) {
echo 'valid number';
} else{
echo 'invalid';
}
https://3v4l.org/1LfAT
Or since you just have ten possible values, for all those who have to look at the code later and actually attempt to make sense of it:
$valid = ['0.5', '1', '1.5', '2', ...];
if (!in_array($var, $valid)) {
// invalid value given.
}
There is no need to use a convoluted regular expression to validate something so simple.
You may use this regex to match numbers from .5 to 5 with trailing .0 as optional e.g. 4.0 and 4 both will be validated:
^(?:[1-4](?:\.[05])?|0?\.5|5(?:\.0)?)$
RegEx Demo
RegERx Details:
^: Start
(?:: Start non-capture group
[1-4](?:\.[05])?: Match numbers from 1.0 to 4.5 with trailing .0 optional
|: OR
0?\.5: Match 0.5 or .5
|: OR
5(?:\.0)?: Match 5 or 5.0
): End non-capture group
$: End

PHP: Generate ordered IDs that has no zeros and ones

I want to create ordered numbers from 2 to whatever with no zero's and ones.
e.g 1, 10, 11, 15, 41, 50, 60, 61, etc these are invalid number
My try:
for($i=0, $ii=1000; $i<$ii; $i++){
if($i%11){
continue;
}
}
but this does not work
You can treat the numbers as strings and search for the chars "0" and "1".
<?php
for($i=0; $i<100; $i++){
if(strpos((string) $i, '0') !== false) continue; // contains 0
if(strpos((string) $i, '1') !== false) continue; // contains 1
echo $i . "\n";
}
Please note that strpos() returns the position of the first occurence of the search string (aka needle) within the given string (aka haystack) or false if no match is found. This is why you have to compare the result of strpos() with !== false as a result of 0 would be considered to be false too if just using == false.
See http://php.net/manual/en/function.strpos.php for a detailed documentation with examples.
Generate an integer, convert it to an octal string, and then increase the value of each digit by 2. I'm not a PHP programmer, so I'll give you the pseudocode.
foo = 1
octFoo = decoct(foo)
// at this point, octFoo is equal to 1
// Now go through each character in octFoo and increase it by 2.
// So 0 becomes 2, 1 becomes 3, etc.
Given the number 8, octFoo would be 10, and the final result would be 32.
You could do this without the intermediate octal step if you wanted to write your own integer-to-string routine. Rather than using the digits 0 through 7, use the digits 2 through 9.

Converting large numbers into letters (and back again)

Is there a term for the idea of storing large numbers as letters? For example let's say I have the (relatively small) number 138201162401719 and I want to shrink the number of characters (I know this does not help with saving disk space) to the fewest possible number of characters. There are 26 letters in the English alphabet (but i count them as 25 since we need a zero letter). If I start splitting up my large number into pieces that are each 25 or less I get:
13, 8, 20, 11, 6, 24, 0, 17, 19
If I then count the numbers of the alphabet a=0, b=1, c=2, d=3... I can convert this to:
NIULGYART
So I went from 15 digits long (138201162401719) to 9 characters long (NIULGYART). This could of course be easily converted back to the original number as well.
So...my first question is "Does this have a name" and my second "Does anyone have PHP code that will do the conversion (in both directions)?"
I am looking for proper terminology so that I can do my own research in Google...though working code examples are cool too.
This only possible if you're considering to store your number before processing as a string. Because you can't store huge number as integers. You will lost the precision (13820116240171986468445 will be stored as 1.3820116240172E+22) so the alot of digits are lost.
If you're considering storing the number as a string this will be your answer:
Functions used: intval, chr and preg_match_all.
<?php
$regex = '/(2[0-5])|(1[0-9])|([0-9])/';
$numberString = '138201162401719';
preg_match_all($regex, $numberString, $numberArray, PREG_SET_ORDER);
echo($numberString . " -> ");
foreach($numberArray as $value){
$character = chr (intval($value[0]) + 65);
echo($character);
}
?>
Demo
This is the result:
138201162401719 -> NIULGYART
Here's how I would do it:
Store the big number as a string and split it into an array of numbers containing one digit each
Loop through the array extract 2-digit chunks using substr()
Check if the number is less than 26 (in which case, it is an alphabet) and add them to an array
Use array_map() with chr() to create a new array of characters from the above array
Implode the resulting array to get the cipher
In code:
$str = '138201162401719';
$arr = str_split($str);
$i = 0; // starting from the left
while ($i < count($arr)) {
$n = substr($str, $i, 2);
$firstchar = substr($n, 0, 1);
if ($n < 26 && $firstchar != 0) {
$result[] = substr($str, $i, 2);
$i += 2; // advance two characters
} else {
$result[] = substr($str, $i, 1);
$i++; // advance one character
}
}
$output = array_map(function($n) {
return chr($n+65);
}, $result);
echo implode($output); // => NIULGYART
Demo.
As an alternative, you could convert the input integer to express it in base 26, instead of base 10. Something like (pseudocode):
func convertBase26(num)
if (num < 0)
return "-" & convertBase26(-num) // '&' is concatenate.
else if (num = 0)
return "A"
endif
output = "";
while (num > 0)
output <- ('A' + num MOD 26) & output // Modulus operator.
num <- num DIV 26 // Integer division.
endwhile
return output
endfunc
This uses A = 0, B = 1, up to Z = 25 and standard place notation: 26 = BA. Obviously a base conversion is easily reversible.
strtr() is a magnificent tool for this task! It replaces the longest match as is traverses the string.
Code: (Demo)
function toAlpha ($num) {
return strtr($num, range("A", "Z"));
}
$string = toAlpha("138201162401719");
echo "$string\n";
$string = toAlpha("123456789012345");
echo "$string\n";
$string = toAlpha("101112131415161");
echo "$string\n";
$string = toAlpha("2625242322212019");
echo "$string";
Output:
NIULGYART
MDEFGHIJAMDEF
KLMNOPQB
CGZYXWVUT
Just flip the lookup array to reverse the conversion: https://3v4l.org/YsFZu
Merged: https://3v4l.org/u3NQ5
Of course, I must mention that there is a vulnerability with converting a sequence of letters to numbers and back to letters. Consider BB becomes 11 then is mistaken for eleven which would traslate to L when converted again.
There are ways to mitigate this by adjusting the lookup array, but that may not be necessary/favorable depending on program requirements.
And here is another consideration from CodeReview.
I have been trying to do the same thing in PHP without success.
Assuming I'm using the 26 letters of the English alphabet, starting with A = 0 down to Z as 25:
I find the highest power of 26 lower than the number I am encoding. I divide it by the best power of 26 I found. Of the result I take away the integer, convert it to a letter and multiply the decimals by 26. I keep doing that until I get a whole number. It's ok to get a zero as it's an A, but if it has decimals it must be multiplied.
For 1 billion which is DGEHTYM and it's done in 6 loops obviously. Although my answer demonstrates how to encode, I'm afraid it does not help doing so on PHP which is what I'm trying to do myself. I hope the algorithm helps people out there though.

Custom date and number format string (with padding and offsets)

I tried to create a customizable number according to a defined mask.
This is my rules to make a mask
You may enter any numbering mask. In this mask, the following tags could be used: {000000} corresponds to a number which will be incremented on each customer.
Enter as many zeros as the desired length of the counter.
The counter will be completed by zeros from the left in order to have as many zeros as the mask.
{000000+000} same as previous but an offset corresponding to the number to the right of the + sign is applied starting on first .
{000000#x} same as previous but the counter is reset to zero when month x is reached (x between 1 and 12).
If this option is used and x is 2 or higher, then sequence {yy}{mm} or {yyyy}{mm} is also required.
{dd} day (01 to 31).
{mm} month (01 to 12).
{yy}, {yyyy} or {y} year over 2, 4 or 1 numbers.
All other characters in the mask will remain intact.
Spaces are not allowed.
Example on customer created on 2007-03-01:
ABC{yy}{mm}-{000000} will give ABC0701-000099,
{0000+100}-ZZZ/{dd}/XXX will give 0199-ZZZ/31/XXX
So my current mask is C{000000}
<?php
$mask = "C{000000}";
$number = 100;
if (preg_match('/\{(0+)([#\+][0-9]+)?([#\+][0-9]+)?\}/i',$mask,$regType)){
$masktype=$regType[1];
$masktype_value=substr(preg_replace('/^TE_/','',$number),0,strlen($regType[1]));//get n first characters of code where n is length in mask
$masktype_value=str_pad($masktype_value,strlen($regType[1]),"#",STR_PAD_RIGHT);
$maskwithonlyymcode=$mask;
$maskwithonlyymcode=preg_replace('/\{(0+)([#\+][0-9]+)?([#\+][0-9]+)?\}/i',$regType[1],$maskwithonlyymcode);
$maskwithonlyymcode=preg_replace('/\{dd\}/i','dd',$maskwithonlyymcode);
$maskwithonlyymcode=preg_replace('/\{(c+)(0*)\}/i',$maskrefclient,$maskwithonlyymcode);
$maskwithonlyymcode=preg_replace('/\{(t+)\}/i',$masktype_value,$maskwithonlyymcode);
$maskwithnocode=$maskwithonlyymcode;
$maskwithnocode=preg_replace('/\{yyyy\}/i','yyyy',$maskwithnocode);
$maskwithnocode=preg_replace('/\{yy\}/i','yy',$maskwithnocode);
$maskwithnocode=preg_replace('/\{y\}/i','y',$maskwithnocode);
$maskwithnocode=preg_replace('/\{mm\}/i','mm',$maskwithnocode);
print "maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode."\n<br>";
}
?>
But it is not working it is printing
maskwithonlyymcode=C000000 maskwithnocode=C000000
My desired output is C000001 - C000100.
What is missing in this code?
I do not understand your code much, so I was not able to fix it, but what about:
<?
function process_mask($mask, $number, $date)
{
while (preg_match("/\{(.+?)\}/", $mask, $match))
{
$outter_code = $match[0];
$inner_code = $match[1];
if (preg_match("/^(0+)(\+(\d+))?$/", $inner_code, $match2))
{
$number2 = $number;
if (!empty($match2[3]))
{
$number2 += intval($match2[3]);
}
$replacement = str_pad($number2, strlen($match2[1]), "0", STR_PAD_LEFT);
}
else
{
switch ($inner_code)
{
case "dd":
$replacement = date("d", $date);
break;
case "mm":
$replacement = date("m", $date);
break;
case "y":
$replacement = substr(date("Y", $date), 3);
break;
case "yy":
$replacement = date("y", $date);
break;
case "yyyy":
$replacement = date("Y", $date);
break;
default:
trigger_error("Unrecognised code $inner_code");
return NULL;
}
}
$mask = str_replace($outter_code, $replacement, $mask);
}
return $mask;
}
function test_mask($mask)
{
$date = mktime(0, 0, 0, 4, 19, 2013);
echo str_pad($mask, 25)." => ".process_mask($mask, 100, $date)."\n";
}
test_mask("C{000}");
test_mask("C{000000}");
test_mask("C{000000+10}");
test_mask("ABC{yy}{mm}-{000000}");
test_mask("{0000+100}-ZZZ/{dd}/XXX");
?>
Outputs:
C{000} => C100
C{000000} => C000100
C{000000+10} => C000110
ABC{yy}{mm}-{000000} => ABC1304-000100
{0000+100}-ZZZ/{dd}/XXX => 0200-ZZZ/19/XXX
I absolutely do not undertand your rules about resetting counters. Based on what date do you want to reset the numbers? Current date? Do you keep some counter per customer (you have not explained what the number is)? Why resetting it on certain month? Wouldn't it be more meaningful to reset it in intervals? Like every month (implementation-wise, it would make sense then to keep separate counter for every month, so that the formatting logic is current time-independent). Some example may help understanding this.
Also for date formatting, I would suggest you to stick with PHP date formatting and do not invent your own.
I would suggest you to use pattern like this instead (It's actually bit .NET-like):
{(#[+offset]|php-date-format-string)[:length]}
So (for number = 999 and date = 2013-04-19):
C{#:4} => C0999
C{#+10:4} => C1009
C{#:6} => C000999
C{#:4}/{Y} => C0999/2013
C{#:4}/{Y:4} => C0999/2013
C{#:4}/{Y:2} => C0999/13
C{#:4}/{Y:1} => C0999/3
C{#:4}/{m} => C0999/03
C{#:4}/{Y}{m} => C0999/201303
C{#:4}/{Ym} => C0999/201303
Code for this would be way simpler, more extensible and flexible.

Categories