Validate A Game Board, And Fast - php

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

Related

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.

PHP Maths Logic

I am trying to set a variable based on some maths logic (to wrap specific html around elements).
I worked half the problem, to hit 0, 3, 6, 9, 12
if(($i % 3) == 0) { // blah }
Now I need to hit the following numbers, 2, 5, 8, 11, 14, etc
What possible maths operation could I do to hit this sequence?
if($i % 3 == 1)
if($i % 3 == 2)
Modulo returns the remainder, so when you match the 0, you get the 3rd, 6th, 9th, etc, because 0 is left in the division.
So just check for when 1 remains and 2 remains.
Along with Tor Valamo's answer you can notice the pattern of (3 * $i) - 1
(3*1)-1 = 2
(3*2)-1 = 5
(3*3)-1 = 8
...
if((($i-2) % 3) == 0) { // blah }

Detecting if integer can be written as sum of given integers

Supposing I'm having the constants 3,5,6,9,10. How can I detect how to write $n, which is the input, as a sum of these constants with the least number of terms?
Examples
$n=10, S=10
$n=18, S=9+9
$n=24, S=9+9+6
$n=27, S=9+9+9
$n=28, S=10+9+9
Thanks
This is another Python solution, but hopefully it's easy for you to convert to PHP (I would do it myself, but I'm no PHP expert - I'm sure you could do a better job of it). I've tried not to use any advanced Python funcitons, so that it is easier for non-Python readers to understand, but if some Python syntax is not clear, please just ask.
allowed = [3, 5, 6, 9, 10]
n = 28
solutions = [ None ] * (n + 1)
solutions[0] = []
for i in range(n + 1):
if solutions[i] is None: continue
for a in allowed:
if i + a > n: continue
if solutions[i + a] is None or len(solutions[i]) + 1 < len(solutions[i + a]):
solutions[i + a] = solutions[i] + [a]
print solutions[28]
It works by starting from 0 and building up to the desired number, keeping a cache of the shortest solution seen so far for each possible total. It has a running time of O(n * a), where a is the number of different allowed values.
By the way, your answer to n=28 is wrong. It should be [9, 9, 10].
Update: here's my attempt at a PHP solution:
<?php
$allowed = array(3, 5, 6, 9, 10);
$n = 28;
$solutions = array();
$solutions[0] = array();
foreach (range(0, $n) as $i) {
if (is_null($solutions[$i])) continue;
foreach ($allowed as $a) {
if ($i + $a > $n) continue;
if (is_null($solutions[$i + $a]) ||
sizeof($solutions[$i]) + 1 < sizeof($solutions[$i + $a])) {
$solutions[$i + $a] = array_merge($solutions[$i], array($a));
}
}
}
var_dump($solutions[$n]);
?>
It gives the right answer, but please be aware that I'm not a professional PHP coder - I just looked up the equivalent functions in the PHP documentation.
This is Mark Byers' algorithm, rewritten using loop structures that are more familiar to PHP developers, and constructs that won't generate PHP notices. $C is your set of integers, $S the solutions.
$n = 28;
$C = array(3, 5, 6, 9, 10);
$S = array(array());
// if your set isn't sorted already, you have to call sort()
//sort($C);
for ($i = 0; $i <= $n; ++$i)
{
if (!isset($S[$i]))
{
continue;
}
foreach ($C as $v)
{
if ($i + $v > $n)
{
break;
}
if (!isset($S[$i + $v])
|| count($S[$i + $v]) > 1 + count($S[$i]))
{
$S[$i + $v] = $S[$i];
$S[$i + $v][] = $v;
}
}
}
print_r($S[$n]);
Two obvious approaches suggest themselves:
Write a series of linear equations,
and solve to find various solutions.
Choose one with the least number of
terms.
Trial and error, starting
with the largest terms first.
Find all possible solutions for "S=3A+5B+6C+9D+10E" then choose the one with the most 0 values for A,B,C,D,E
a rough sketch of an unscalable but correct solution (sorry, so far its only python ..):
#!/usr/bin/env python
import itertools, sys
pool = [3, 5, 6, 9, 10]
repeat, found, solutions = 1, False, set()
try: x = int(sys.argv[1])
except: x = 42
while not found:
for n in itertools.product(pool, repeat=repeat):
s = sum(n)
if s == x:
solutions.add(n)
found = True
break
repeat = repeat + 1
print solutions
would yield:
$ python 1850629.py 11
set([(5, 6)])
$ python 1850629.py 19
set([(9, 10)])
$ python 1850629.py 21
set([(3, 9, 9)])
$ python 1850629.py 42
set([(3, 9, 10, 10, 10)])
In addition to the excellent general answers already provided, bear in mind that if your set of values has certain properties, much more optimal solutions exist.
Specifically, if your solution is 'minimal' - that is, a single best solution exists for any value - then you can find the smallest number of elements using a 'greedy' algorithm: Simply add the largest value until the remainder is smaller than it, repeat with the next largest value, and so forth.
As an example, the denominations used for money in many countries are .01, .02, .05, .10, .20, .50, 1, 2, 5, .... This set is minimal, so you can just repeatedly add the largest valid denomination.
NP-complete problem
Subset sum problem

Categories