Validate rating variable, Before processing - php

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

Related

Validate A Game Board, And Fast

I am creating a multiplayer Battleship game using an Apache/PHP server (yes, I know Node would be much better for this, but I'll get around to learning it later). Anyways, I am at the point in which both players are uploading their game boards to start the game. While my client side JavaScript would obviously properly compile and validate boards before sending them off to the server, that is still vulnerable to cheating, so the server must also double check. However, on the server, speed and efficiency is everything.
As of now, this is the process my server follows:
Server receives the board as a JSON encoded multidimensional array via an AJAX request.
$board = json_decode($_REQUEST["board"]);
Server validates the structure of the passed input.
$validate = array(gettype($board) == "array", count($board) == 10);
for($i = 0; $i < count($board); $i++) {
array_push($validate, count($board[$i]) == 10);
for($ii = 0; $ii < count($board[$i]); $ii++) {
array_push($validate, gettype($board[$i][$ii]) == "integer");
}
}
if(in_array(0, $validate)) throwError();
In the array, numbers zero through five represent blank, aircraft carrier, battleship, cruiser, submarine, and destroyer tiles respectively. I count the proper quantity of each.
$valueCount = array_count_values(array_merge($board[0], $board[1], $board[2], $board[3], $board[4], $board[5], $board[6], $board[7], $board[8], $board[9]));
$template = array("0"=>83,"1"=>5, "2"=>4, "3"=>3, "4"=>3, "5"=>2);
if($template != $valueCount) throwError();
I need to ensure that ship tiles are only vertical or horizontal lines.
$shipsValid = array(false, false, false, false, false);
$transpose = array_map(null, $board[0], $board[1], $board[2], $board[3], $board[4], $board[5], $board[6], $board[7], $board[8], $board[9]);
for($i = 0; $i < 9; $i++) {
$temp1 = array_count_values($board[$i]);
$temp2 = array_count_values($transpose[$i]);
if($temp1["1"] == 5 || $temp2["1"] == 5) shipsValid[0] = true;
if($temp1["2"] == 4 || $temp2["2"] == 4) shipsValid[1] = true;
if($temp1["3"] == 3 || $temp2["3"] == 3) shipsValid[2] = true;
if($temp1["4"] == 3 || $temp2["4"] == 3) shipsValid[3] = true;
if($temp1["5"] == 2 || $temp2["5"] == 2) shipsValid[4] = true;
}
if(in_array(0, $shipsValid)) throwError();
I need to ensure ships are continuous with no gaps.
??????????????????????????
With enough work, I could have completed step five, but it would have been grossly inefficient, looping through everything repeatedly. So, in conclusion, how can I make what I have designed more efficient, and how I complete the final step (5) to validating the uploaded board?
Example Board (Valid):
"[[0,1,1,1,1,1,0,0,0,0],
[0,0,0,0,0,0,0,2,0,0],
[0,0,0,0,0,0,0,2,0,0],
[0,0,0,0,0,0,0,2,0,0],
[0,3,3,3,0,0,0,2,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,5,0,0,0,4,4,4,0],
[0,0,5,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0]]"
Example Board (Invalid, for many reasons):
"[[0,1,1,0,1,1,0,0,0,1],
[0,0,0,0,0,0,0,2,0,0],
[0,0,0,0,0,0,0,2,0,0],
[0,0,0,0,0,0,0,2,0,0],
[0,6,6,6,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,4,4,4,4,4],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0]]"
This solution is ugly and not clearly explained inline, but it does set the time record here with an average of 47.99473047 µs to validate a correct board on my server. I do apologize for the excessive use of inline logic structures; its kinda my coding style.
function quit() {
echo "invalid request";
exit();
}
$board = json_decode($_REQUEST["board"]);
if(gettype($board) != "array"|| count($board) != 10) quit();
foreach($board as $row) {
if(gettype($row) != "array"|| count($row) != 10) quit();
foreach($row as $cell) if(gettype($cell) != "integer") quit();
}
$strand = array_merge($board[0], $board[1], $board[2], $board[3], $board[4], $board[5], $board[6], $board[7], $board[8], $board[9]);
$fleet = array(array_keys($strand, 0), array_keys($strand, 1), array_keys($strand, 2), array_keys($strand, 3), array_keys($strand, 4), array_keys($strand, 5));
if(count($fleet[0]) != 83 || count($fleet[1]) != 5 || count($fleet[2]) != 4 || count($fleet[3]) != 3 || count($fleet[4]) != 3 || count($fleet[5]) != 2 || count($fleet) != 6) quit();
foreach($fleet as $ship) if($ship != $fleet[0]) for($i = 0; $i < count($ship); $i++) if($ship[0] + 10 * $i != $ship[$i] && $ship[$i] - $ship[0] != $i) quit();
echo "success";
exit();
This process first checks that the array is properly structured (10 by 10 with integer values). It then combines all the arrays into one long array and fetches the keys of the values 0 through 5. I then check that the proper amount of each number (for each ship) occurs in the keys. Lastly, I check that keys for 1 through 5 occur sequentially or by tens, which would mean that they are horizontal or vertical.
It feels kinda weird answering my own question, but I'm open to other ideas and optimizations on top of my own.
Version 2: Now with the highest priority on speed of validation.(no json_decode() call needed)
The following are my two valid test $board strings. Between the two of them, all ships occur in both orientations.
Test #1:
[[0,1,1,1,1,1,0,0,0,0],[0,0,0,0,0,0,0,2,0,0],[0,0,0,0,0,0,0,2,0,0],[0,0,0,0,0,0,0,2,0,0],[0,3,3,3,0,0,0,2,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,5,0,0,0,4,4,4,0],[0,0,5,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0]]
Test #2:
[[0,1,0,0,0,0,0,0,0,0],[0,1,0,0,2,2,2,2,0,0],[0,1,0,0,0,0,0,0,0,0],[0,1,0,0,0,0,0,0,0,0],[0,1,3,0,0,0,0,0,0,0],[0,0,3,0,0,0,0,0,0,0],[0,0,3,0,0,0,0,0,0,0],[0,0,5,5,0,0,4,0,0,0],[0,0,0,0,0,0,4,0,0,0],[0,0,0,0,0,0,4,0,0,0]]
The method to follow is a simple preg_match() call in php. The technique uses lookaheads to validate each ship, followed by a fullstring match that validates the json structure.
This technique is most commonly used when validating passwords, where a string must be generally validated for length and/or valid characters, but also checked for minimum occurrences of characters or ranges of characters (e.g. password must be a minimum of 8 characters and include a lowercase, uppercase, number, and a symbol).
The brilliance in my method is not just that it is a single call, but that it can also be seamlessly called to validate the data in javascript as well.
Now for the patterns (seatbelts on, please):
Pattern #1: "Longer but Faster"
(1590 characters) (Test#1: 371 steps; Test#2 446 steps)
/(?=^[^1]*(?:1,1,1,1,1|1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1)[^1]*$)(?=^[^2]*(?:2,2,2,2|2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2)[^2]*$)(?=^[^3]*(?:3,3,3|3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3)[^3]*$)(?=^[^4]*(?:4,4,4|4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4)[^4]*$)(?=^[^5]*(?:5,5|5\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+5)[^5]*$)^\[\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\]\]$/
Pattern #2: "Shorter but Slower"
(306 characters) (Test#1: 576 steps; Test#2 698 steps)
/(?=^[^1]*(?:1,1,1,1,1|1(?:(?:\D+[^1]){9}\D+1){4})[^1]*$)(?=^[^2]*(?:2,2,2,2|2(?:(?:\D+[^2]){9}\D+2){3})[^2]*$)(?=^[^3]*(?:3,3,3|3(?:(?:\D+[^3]){9}\D+3){2})[^3]*$)(?=^[^4]*(?:4,4,4|4(?:(?:\D+[^4]){9}\D+4){2})[^4]*$)(?=^[^5]*(?:5,5|5(?:\D+[^5]){9}\D+5)[^5]*$)(?:(?:^\[|)\[[0-5](?:,[0-5]){9}\](?:,|\]$)){10}/
The 4 pillars of a good regex pattern...
The reason the first pattern is faster is because I have removed all inessential non-capture groups. The cost, in terms of "Brevity", is drastic. "Readability" is extremely poor in both of patterns. Both patterns maintain the same "Accuracy", but the first pattern wins on "Efficiency" by some margin.
As for the pattern components...
All lookaheads have the same basic structure and obey the game rules/requirements:
(?= #lookahead, but don't "consume" any characters in the process
^ #from the start of the string
[^5]* #quickly, greedily match all characters that are not a 5
(?: #non-capture group to isolate the "alternatives"
5,5 #literally match a 2-peg row
| # or
5\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+5 #match two 5 pegs that have nine non-5 pegs between them
) #end non-capture group
[^5]* #quickly, greedily match all characters that are not a 5 (ensure there are no more 5's in the string)
$ #all the way to the end of the string
) #end the lookahead
The general board structure & pegs matching:
^ #from the start of the string
\[ #match the opening outer bracket
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\], #match a row of 0, 1, 2, 3, 4, or 5's
\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\] #match a row of 0, 1, 2, 3, 4, or 5's
\] #match the closing outer bracket
$ #to the end of the string
PHP Code: (Demo)
$pattern='/(?=^[^1]*(?:1,1,1,1,1|1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+[^1]\D+1)[^1]*$)(?=^[^2]*(?:2,2,2,2|2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+[^2]\D+2)[^2]*$)(?=^[^3]*(?:3,3,3|3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+[^3]\D+3)[^3]*$)(?=^[^4]*(?:4,4,4|4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+[^4]\D+4)[^4]*$)(?=^[^5]*(?:5,5|5\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+[^5]\D+5)[^5]*$)^\[\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\],\[[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5],[0-5]\]\]$/';
echo preg_match($pattern,$_REQUEST["board"]) ? 'success' : 'invalid';

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.

Echo decimal value without point zero ( .0 ) at end

I am using
Decimal (5,1)
to store my data.
When value is 12.2, 66.4... evething is ok.
But the NOT decimal value : 5, 10 , 20 .... when echo will be 5.0, 10.0 ...
How can I echo them look like :
1, 2.2, 2.5, 10, 10.5, 10.6, 11
Gordon Linoff's answer may do the trick, but here is the PHP way to do so :
echo (intval($var) == $var) ? intval($var) : $var;
Or, if you don't like ternary conditions :
if (intval($var) == $var) {
echo intval($var);
} else {
echo $var;
}
You need to look at the value and decide how to format it. The type specification says "keep one digit after the decimal place", and MySQL diligently does that.
There are lots of ways. Here is one:
select (case when val = cast(val as int) then format(val, 0)
else format(val, 10) end)
Format adds commas, which (to me) seems desirable for numbers greater than 1,000. But there are other methods if you don't like that.
A generic solution working for any number of fractional digits:
remove all trailing zeroes and if the last character is a period, remove it too:
TRIM(TRAILING '.' FROM (TRIM(TRAILING '0' FROM (CAST(col AS VARCHAR(40))))))

Can the for loop be eliminated from this piece of PHP code?

I have a range of whole numbers that might or might not have some numbers missing. Is it possible to find the smallest missing number without using a loop structure? If there are no missing numbers, the function should return the maximum value of the range plus one.
This is how I solved it using a for loop:
$range = [0,1,2,3,4,6,7];
// sort just in case the range is not in order
asort($range);
$range = array_values($range);
$first = true;
for ($x = 0; $x < count($range); $x++)
{
// don't check the first element
if ( ! $first )
{
if ( $range[$x - 1] + 1 !== $range[$x])
{
echo $range[$x - 1] + 1;
break;
}
}
// if we're on the last element, there are no missing numbers
if ($x + 1 === count($range))
{
echo $range[$x] + 1;
}
$first = false;
}
Ideally, I'd like to avoid looping completely, as the range can be massive. Any suggestions?
Algo solution
There is a way to check if there is a missing number using an algorithm. It's explained here. Basically if we need to add numbers from 1 to 100. We don't need to calculate by summing them we just need to do the following: (100 * (100 + 1)) / 2. So how is this going to solve our issue ?
We're going to get the first element of the array and the last one. We calculate the sum with this algo. We then use array_sum() to calculate the actual sum. If the results are the same, then there is no missing number. We could then "backtrack" the missing number by substracting the actual sum from the calculated one. This of course only works if there is only one number missing and will fail if there are several missing. So let's put this in code:
$range = range(0,7); // Creating an array
echo check($range) . "\r\n"; // check
unset($range[3]); // unset offset 3
echo check($range); // check
function check($array){
if($array[0] == 0){
unset($array[0]); // get ride of the zero
}
sort($array); // sorting
$first = reset($array); // get the first value
$last = end($array); // get the last value
$sum = ($last * ($first + $last)) / 2; // the algo
$actual_sum = array_sum($array); // the actual sum
if($sum == $actual_sum){
return $last + 1; // no missing number
}else{
return $sum - $actual_sum; // missing number
}
}
Output
8
3
Online demo
If there are several numbers missing, then just use array_map() or something similar to do an internal loop.
Regex solution
Let's take this to a new level and use regex ! I know it's nonsense, and it shouldn't be used in real world application. The goal is to show the true power of regex :)
So first let's make a string out of our range in the following format: I,II,III,IIII for range 1,3.
$range = range(0,7);
if($range[0] === 0){ // get ride of 0
unset($range[0]);
}
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
echo $str;
The output should be something like: I,II,III,IIII,IIIII,IIIIII,IIIIIII.
I've come up with the following regex: ^(?=(I+))(^\1|,\2I|\2I)+$. So what does this mean ?
^ # match begin of string
(?= # positive lookahead, we use this to not "eat" the match
(I+) # match I one or more times and put it in group 1
) # end of lookahead
( # start matching group 2
^\1 # match begin of string followed by what's matched in group 1
| # or
,\2I # match a comma, with what's matched in group 2 (recursive !) and an I
| # or
\2I # match what's matched in group 2 and an I
)+ # repeat one or more times
$ # match end of line
Let's see what's actually happening ....
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^
(I+) do not eat but match I and put it in group 1
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^
^\1 match what was matched in group 1, which means I gets matched
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^ ,\2I match what was matched in group 1 (one I in thise case) and add an I to it
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^^ \2I match what was matched previously in group 2 (,II in this case) and add an I to it
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^^^ \2I match what was matched previously in group 2 (,III in this case) and add an I to it
We're moving forward since there is a + sign which means match one or more times,
this is actually a recursive regex.
We put the $ to make sure it's the end of string
If the number of I's don't correspond, then the regex will fail.
See it working and failing. And Let's put it in PHP code:
$range = range(0,7);
if($range[0] === 0){
unset($range[0]);
}
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
if(preg_match('#^(?=(I*))(^\1|,\2I|\2I)+$#', $str)){
echo 'works !';
}else{
echo 'fails !';
}
Now let's take in account to return the number that's missing, we will remove the $ end character to make our regex not fail, and we use group 2 to return the missed number:
$range = range(0,7);
if($range[0] === 0){
unset($range[0]);
}
unset($range[2]); // remove 2
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
preg_match('#^(?=(I*))(^\1|,\2I|\2I)+#', $str, $m); // REGEEEEEX !!!
$n = strlen($m[2]); //get the length ie the number
$sum = array_sum($range); // array sum
if($n == $sum){
echo $n + 1; // no missing number
}else{
echo $n - 1; // missing number
}
Online demo
EDIT: NOTE
This question is about performance. Functions like array_diff and array_filter are not magically fast. They can add a huge time penalty. Replacing a loop in your code with a call to array_diff will not magically make things fast, and will probably make things slower. You need to understand how these functions work if you intend to use them to speed up your code.
This answer uses the assumption that no items are duplicated and no invalid elements exist to allow us to use the position of the element to infer its expected value.
This answer is theoretically the fastest possible solution if you start with a sorted list. The solution posted by Jack is theoretically the fastest if sorting is required.
In the series [0,1,2,3,4,...], the n'th element has the value n if no elements before it are missing. So we can spot-check at any point to see if our missing element is before or after the element in question.
So you start by cutting the list in half and checking to see if the item at position x = x
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
Yup, list[4] == 4. So move halfway from your current point the end of the list.
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
Uh-oh, list[6] == 7. So somewhere between our last checkpoint and the current one, one element was missing. Divide the difference in half and check that element:
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
In this case, list[5] == 5
So we're good there. So we take half the distance between our current check and the last one that was abnormal. And oh.. it looks like cell n+1 is one we already checked. We know that list[6]==7 and list[5]==5, so the element number 6 is the one that's missing.
Since each step divides the number of elements to consider in half, you know that your worst-case performance is going to check no more than log2 of the total list size. That is, this is an O(log(n)) solution.
If this whole arrangement looks familiar, It's because you learned it back in your second year of college in a Computer Science class. It's a minor variation on the binary search algorithm--one of the most widely used index schemes in the industry. Indeed this question appears to be a perfectly-contrived application for this searching technique.
You can of course repeat the operation to find additional missing elements, but since you've already tested the values at key elements in the list, you can avoid re-checking most of the list and go straight to the interesting ones left to test.
Also note that this solution assumes a sorted list. If the list isn't sorted then obviously you sort it first. Except, binary searching has some notable properties in common with quicksort. It's quite possible that you can combine the process of sorting with the process of finding the missing element and do both in a single operation, saving yourself some time.
Finally, to sum up the list, that's just a stupid math trick thrown in for good measure. The sum of a list of numbers from 1 to N is just N*(N+1)/2. And if you've already determined that any elements are missing, then obvously just subtract the missing ones.
Technically, you can't really do without the loop (unless you only want to know if there's a missing number). However, you can accomplish this without first sorting the array.
The following algorithm uses O(n) time with O(n) space:
$range = [0, 1, 2, 3, 4, 6, 7];
$N = count($range);
$temp = str_repeat('0', $N); // assume all values are out of place
foreach ($range as $value) {
if ($value < $N) {
$temp[$value] = 1; // value is in the right place
}
}
// count number of leading ones
echo strspn($temp, '1'), PHP_EOL;
It builds an ordered identity map of N entries, marking each value against its position as "1"; in the end all entries must be "1", and the first "0" entry is the smallest value that's missing.
Btw, I'm using a temporary string instead of an array to reduce physical memory requirements.
I honestly don't get why you wouldn't want to use a loop. There's nothing wrong with loops. They're fast, and you simply can't do without them. However, in your case, there is a way to avoid having to write your own loops, using PHP core functions. They do loop over the array, though, but you simply can't avoid that.
Anyway, I gather what you're after, can easily be written in 3 lines:
function highestPlus(array $in)
{
$compare = range(min($in), max($in));
$diff = array_diff($compare, $in);
return empty($diff) ? max($in) +1 : $diff[0];
}
Tested with:
echo highestPlus(range(0,11));//echoes 12
$arr = array(9,3,4,1,2,5);
echo highestPlus($arr);//echoes 6
And now, to shamelessly steal Pé de Leão's answer (but "augment" it to do exactly what you want):
function highestPlus(array $range)
{//an unreadable one-liner... horrid, so don't, but know that you can...
return min(array_diff(range(0, max($range)+1), $range)) ?: max($range) +1;
}
How it works:
$compare = range(min($in), max($in));//range(lowest value in array, highest value in array)
$diff = array_diff($compare, $in);//get all values present in $compare, that aren't in $in
return empty($diff) ? max($in) +1 : $diff[0];
//-------------------------------------------------
// read as:
if (empty($diff))
{//every number in min-max range was found in $in, return highest value +1
return max($in) + 1;
}
//there were numbers in min-max range, not present in $in, return first missing number:
return $diff[0];
That's it, really.
Of course, if the supplied array might contain null or falsy values, or even strings, and duplicate values, it might be useful to "clean" the input a bit:
function highestPlus(array $in)
{
$clean = array_filter(
$in,
'is_numeric'//or even is_int
);
$compare = range(min($clean), max($clean));
$diff = array_diff($compare, $clean);//duplicates aren't an issue here
return empty($diff) ? max($clean) + 1; $diff[0];
}
Useful links:
The array_diff man page
The max and min functions
Good Ol' range, of course...
The array_filter function
The array_map function might be worth a look
Just as array_sum might be
$range = array(0,1,2,3,4,6,7);
// sort just in case the range is not in order
asort($range);
$range = array_values($range);
$indexes = array_keys($range);
$diff = array_diff($indexes,$range);
echo $diff[0]; // >> will print: 5
// if $diff is an empty array - you can print
// the "maximum value of the range plus one": $range[count($range)-1]+1
echo min(array_diff(range(0, max($range)+1), $range));
Simple
$array1 = array(0,1,2,3,4,5,6,7);// array with actual number series
$array2 = array(0,1,2,4,6,7); // array with your custom number series
$missing = array_diff($array1,$array2);
sort($missing);
echo $missing[0];
$range = array(0,1,2,3,4,6,7);
$max=max($range);
$expected_total=($max*($max+1))/2; // sum if no number was missing.
$actual_total=array_sum($range); // sum of the input array.
if($expected_total==$actual_total){
echo $max+1; // no difference so no missing number, then echo 1+ missing number.
}else{
echo $expected_total-$actual_total; // the difference will be the missing number.
}
you can use array_diff() like this
<?php
$range = array("0","1","2","3","4","6","7","9");
asort($range);
$len=count($range);
if($range[$len-1]==$len-1){
$r=$range[$len-1];
}
else{
$ref= range(0,$len-1);
$result = array_diff($ref,$range);
$r=implode($result);
}
echo $r;
?>
function missing( $v ) {
static $p = -1;
$d = $v - $p - 1;
$p = $v;
return $d?1:0;
}
$result = array_search( 1, array_map( "missing", $ARRAY_TO_TEST ) );

PHP - Length of a number containing 0's to the left

I'm trying to validate a number by it's length. This number has to have 4 digits so it passes the validation. The problem is when this number has 0's to it's left, like 0035.
Right now I'm at this:
echo (strlen ((string) 0025 ));
Which gives a total of 2, but I want this to count the 0's to it's left, so it gives me a total of 4.
Clearly the cast of the integer to string is not working, how can i do this?
You can't do that way, a left zero means the number is octal and not decimal, you can use sprintf() to do that.
Example:
echo strlen(sprintf("%04d", 25));
Live Test:
http://codepad.viper-7.com/VQr7Xz
Comment Answer:
I don't want to add the 0s to the number, i want to detect if the
number has 0s. If the number received is 25, it's not a valid number.
If it is 0025 it is valid. What i want is to validate only numbers
with 4 digits. – Cláudio Ribeiro
Cláudio, numbers have infinite left zeros, although a user has explicitly type 2 or 3 left zeros there are more hidden left zeros, it's a math basic, this is why it's impossible to know how many left zeros the user has typed if you receive an integer variable. If the variable has a constant size and you want to know how many left zeros it has you can do this:
<?php
$int = 25;
echo 4 - strlen($int);
Live test: http://codepad.viper-7.com/fT2jSn
But if you the variable has variable length it must be a string type instead of a numeric type.
An example where the variable received is a string:
<?php
$strs = array("0025","000035","01","2");
foreach($strs as $str)
{
preg_match("/^0+/", $str, $matches);
echo strlen(#$matches[0]);
echo "<br>";
}
Live Test: http://codepad.viper-7.com/BTRTgR
That should work:
$str = "0025";
if( is_numeric($str) && strlen($str) == 4)
{
echo "pass";
}
If it's a number, not a string, the number doesn't have digits. It has a value. You can format that value into a string with 4 digits which is left padded with 0s. But to validate whether a number has 4 digits is nonsense, since the number value has no formatting. The value only becomes "4 digits" when you format it as base 10 number. Until then the value is a value which can be expressed in a multitude of bases and has a different number of "digits" in all of them.
You either want to format the number to a 0-padded 4 digit string, or you want to check whether the value is between 0 and 9999 (or 1000 and 9999 if it has to be exactly "4 digits").
if (0 <= $num && $num <= 9999) {
$numStr = sprintf('%04d', $num);
} else {
trigger_error('Number out of range');
}

Categories