Converting large numbers into letters (and back again) - php

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.

Related

Given a number, foreach 3rd digit do something

Lets say i have a number like below:
$number = "20160513123"
So what i want to do is,
Starting from 2, for each third digit i want to multiply and find the sum.
So basically i want to get the number 2,6,1,2 from the string
and multiply each number by 3 and then calculate the sum of all the multiplication.
Like so:
2 * 3,
6 * 3,
1 * 3,
2 * 3,
Then i want to get the sum of all of the above. How is that possible using PHP?
You could use the range function to step through your characters:
$number = "20160513123";
$sum = 0;
foreach (range(0, strlen($number), 3) as $i) {
$sum += 3*$number[$i];
}
echo $sum;
Output:
33
If you like functional programming, then this may be according to your liking:
$sum = 3 * array_sum(array_map(
function($i) use ($number) { return $number[$i]; },
range(0, strlen($number), 3)
));
Or, with the use of str_split, you can get chunks of 3 characters:
$sum = array_sum(array_map(
function($s) { return 3*$s[0]; },
str_split($number, 3)
));
NB: Note that you can either multiply each number with 3 separately, or just the sum.
Compact solution
One final alternative. It uses array_chunk and array_column to filter out the desired digits (it seems PHP has functions for most anything):
$sum = 3 * array_sum(array_column(array_chunk(str_split($number), 3), 0));
Take your pick ;-)
The solution using str_split function(to get an array of digits):
$number = "20160513123";
$total_sum = 0;
foreach (str_split($number) as $k => $n) {
if ($k % 3 == 0) $total_sum += $n * 3; // considering each third digit
}
print_r($total_sum); // 33
CasimiretHippolyte's tiny, tiny one-liner
(you're not going to find a smaller one-liner for this question):
echo array_sum(preg_split('/.\K..?/',$number))*3;
This explodes $number after this first character by every 2 (or 1) character to follow, adds up the remaining numbers and multiplies them. Just clever awesome!
CasimiretHippolyte's previous solution:
if(preg_match_all('/..\K./',"00$number",$matches)){
echo array_sum($matches[0])*3;
}
Though it required left-padding the string, this method used shorter/simpler regex and produced a leaner $matches array, compared to my solution below:
mickmackusa's solution:
if(preg_match_all("/(\d)(?:\d\d|\d$|$)/",$number,$matches)){
echo array_sum($matches[1])*3;
}
My regex matches all single digits that are followed by either: 2 digits,
1 digit & end of line, or end of line. This method steps past the non-capture group digits when it can, so only 1st, 4th, 7th, etc. are captured.
All codes above output:
33

PHP rand explanation using an array?

I have very little programming experience but I am going over a php book and this block of code is confusing me.
If rand generates a random integer, how does this program use ABCDEFG in the array.
Can you please explain the program thank you. I know what the result is, I am just not sure how it get it.
<?php
$array = '123456789ABCDEFG';
$s = '';
for ($i=1; $i < 50; $i++) {
$s.= $array[rand(0,strlen($array)-1)]; //explain please
}
echo $s;
?>
It's using the array index so $array[11] would equal 'C'. rand() takes a range - in your example that's from 0 to strlen($array)-1 which is the length of the string, minus 1 since it's a 0 based index.
Break it down into parts:
strlen($array) - returns the length of the string in $array, which would be 17
strlen($array) - 1 => 16
rand(0, 16) - generate a random number between 0 and 16
$array[$random_number] - get the $random_number'th element of the array
Its just taking the length of the array with strlen($array). It doesn't matter what is in the string just the length. Then its generating a random number between 0 and the length of the string minus one.
Then it takes whatever character is in that position in the array (so $array[5] would be '6', $array[12] would be 'C', etc) and appending that to string $s. It then has a for loop to repeat it 50 times.
What you end up with is a random string that is 50 characters long and contains the numbers 1-9 and letters A-G.

Atypical letter and number incrementation

I'm working on something which requires unique ID numbers which are in the format of:
[A-Z][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]
And increment as:
AAAAAA, AAAAAB, ... AAAAAZ, AAAAA0, AAAAA1, .. AAAAA9, AAAABA, AAAABB
I know I can increment letters in PHP, but how would I do letters and numbers in such a way?
Special note, baseconvert isn't an option, because it must always be exactly 6 characters and fit the noted format incrementally. Further, baseconvert starts at 0, not A, so if I do start at "621937810" (AAAAAA) the next jump will be after AAAAAZ and on to AAAAB0. It seemed like the quickest solution, but it doesn't work.
you could try base_convert from 36 to 10, increment then 10 to 36. I didn't have any trouble with the larger numbers like ZZZZZW+1, however as it says in the php manual, there could be problems with larger numbers due to float/double precision.
<?php
echo "<pre>";
//orig string
$test = 'ZZZZZW';
//convert from base36 to number.
$test = base_convert($test, 36, 10);
var_dump($test);
//increment
$test++;
var_dump($test);
//convert back (and upper case)
$test = strtoupper(base_convert($test, 10, 36));
var_dump($test);
?>
outputs:
string(6) "ZZZZZW"
string(10) "2176782332"
float(2176782333)
string(6) "ZZZZZX"
example can be see here
Other than that, you can do some custom increment or even check out the base_convert comments in the php manual on converting some larger bases and values.
Edit after clarification:
Taken from the php comments page:
<?php
function intToAlphaBaseN($n,$baseArray) {
$l=count($baseArray);
$s = '';
for ($i = 1; $n >= 0 && $i < 10; $i++) {
$s = $baseArray[($n % pow($l, $i) / pow($l, $i - 1))].$s;
$n -= pow($l, $i);
}
return $s;
}
$base=array_merge(range('A','Z'), range(0,9));
$zero = $base[0];
//an integer number
$r=rand(0, 999999);
echo "$r converts to :".str_pad(intToAlphaBaseN($r,$base), 6, $zero, STR_PAD_LEFT)."\n";
//an integer number
$r++;
echo "$r converts to :".str_pad(intToAlphaBaseN($r,$base), 6, $zero, STR_PAD_LEFT)."\n";
//an integer number
$r++;
echo "$r converts to :".str_pad(intToAlphaBaseN($r,$base), 6, $zero, STR_PAD_LEFT)."\n";
?>
working example
As for incrementing from the string ID, I would suggest either saving the integer value, increment that and convert or write a similar function to convert to integer, increment, convert back. The former probably being easier.
And as far as forcing the same 6 character format, the only time this should be a problem is when you pass the Z99999 which according to your format, is the max.
You are basically describing a 36-ary notation (as opposed to binary, decimal, or hexadecimal). I.e. the last letter represents (int value) mod 36, the second-but last letter represents (value/36)%36. Transform an integer into an array of six numbers from 0 to 35. Then map the resulting values such that 0=A, 25=Z, 26=0 and so on. Incrementing is just achieved by starting with an int value of 0, incrementing it, and transforming it into such a string after each increment. Yea you can probably also set up a 36-ary arithmetic such that you don't waste time between i and i+1, but I'd rather go with a brute force approach like that to begin with.

How can I separate a number and get the first two digits in PHP?

How can I separate a number and get the first two digits in PHP?
For example: 1345 -> I want this output=> 13 or 1542 I want 15.
one possibility would be to use substr:
echo substr($mynumber, 0, 2);
EDIT:
please not that, like hakre said, this will break for negative numbers or small numbers with decimal places. his solution is the better one, as he's doing some checks to avoid this.
First of all you need to normalize your number, because not all numbers in PHP consist of digits only. You might be looking for an integer number:
$number = (int) $number;
Problems you can run in here is the range of integer numbers in PHP or rounding issues, see Integers Docs, INF comes to mind as well.
As the number now is an integer, you can use it in string context and extract the first two characters which will be the first two digits if the number is not negative. If the number is negative, the sign needs to be preserved:
$twoDigits = substr($number, 0, $number < 0 ? 3 : 2);
See the Demo.
Shouldn't be too hard? A simple substring should do the trick (you can treat numbers as strings in a loosely typed language like PHP).
See the PHP manual page for the substr() function.
Something like this:
$output = substr($input, 0, 2); //get first two characters (digits)
You can get the string value of your number then get the part you want using
substr.
this should do what you want
$length = 2;
$newstr = substr($string, $lenght);
With strong type-hinting in new version of PHP (> PHP 7.3) you can't use substr on a function if you have integer or float. Yes, you can cast as string but it's not a good solution.
You can divide by some ten factor and recast to int.
$number = 1345;
$mynumber = (int)($number/100);
echo $mynumber;
Display: 13
If you don't want to use substr you can divide your number by 10 until it has 2 digits:
<?php
function foo($i) {
$i = abs((int)$i);
while ($i > 99)
$i = $i / 10;
return $i;
}
will give you first two digits

Advanced Php Number Formatting

I want to have a PHP number formatted with a minimum of 2 decimal places and a maximum of 8 decimal places. How can you do that properly.
Update: I'm sorry, my question is say I have number "4". I wish for it to display as "4.00" and if I have "2.000000001" then it displays as "2.00" or if I have "3.2102" it will display as such. There is a NSNumber formatter on iPhone, what is the equivalent in PHP.
This formats the $n number for 8 decimals, then removes the trailing zero, max 6 times.
$s = number_format($n, 8);
for($i=0; $i<8-2; $i++) {
if (substr($s, -1) == '0')
$s = substr($s, 0, -1);
}
print "Number = $s";
Use sprintf() to format a number to a certain number of decimal places:
$decimal_places = 4;
$format = "%.${decimal_places}f";
$formatted = sprintf($format,$number);
I don't understand why you would want to display numbers to an inconsistent degree of accuracy. I don't understand what pattern you're trying to describe in your comment, either.
But let us suppose that you want the following behaviour: you want to express the number to 8 decimal places, and if there are more than 2 trailing zeroes in the result, you want to remove the excess zeroes. This is not much more difficult to code than it is to express in English. In pseudocode:
$mystring = string representation of number rounded to 8 decimal places;
while (last character of $mystring is a 0) {
chop off last character of $mystring;
}
Check the number format function:
<?php
$num = 43.43343;
$formatted = number_format(round((float) $num, 2), 2);
?>
http://php.net/manual/en/function.number-format.php
Using preg_match just get the zero ending with and then rtim it
<?php
$nn = number_format(10.10100011411100000,13);
preg_match('/[0]+$/',$nn,$number);
if(count($number)>0){
echo rtrim($nn,$number[0]);
}
Hope it will help you.

Categories