PHP: Generate ordered IDs that has no zeros and ones - php

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.

Related

How to find a digit within an integer in PHP; for example to find the position of 23 in 123456

I need to find the digits 23 within another integer, 123456.
I have written a function that finds the mod of another number, hence eventually finds the desired output:
<?php
$digit = 23;
$anotherNumber = 123456;
while($anotherNumber > 0) {
$reminder = $anotherNumber % 10;
if ($reminder == $digit) {
$pos++;
}
$anotherNumber /= 10;
print $pos;
}
?>
I get the output 6 5 4 3 2 1 0 0 0 0 0 0 0 0 0 whereas I need the position of the integer.
You're not really trying to treat these numbers as numbers; you want to treat them as strings. So, you should do that explicitly.
$digit = 23;
$anotherNumber = 123456;
$position = strpos((string) $anotherNumber, (string) $digit);
Note the warnings in the manual entry for strpos() (emphasis added):
Returns the position of where the needle exists relative to the beginning of the haystack string (independent of offset). Also note that string positions start at 0, and not 1.
Returns FALSE if the needle was not found.

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

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';

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.

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