In the php
wild wild west,
given that Most efficient way to get next letter in the alphabet using PHP is made by the pre increment ++
operator applied to a char
(i.e. one sized string):
$str = 'a';
echo ++$str; // prints 'b'
$str = 'z';
echo ++$str; // prints 'aa'
And that there is this crazyness in
for ($char='A'; $char<='Z'; $char++) {
echo $char;
}
that will print out
ABCDEFGHIJKLMNOPQRSTUVWXYZAAABACADAEAFAGAHAIAJAKALAMANAOAPAQARASATAUAVAWAXAYAZBABBBCBDBEBFBGBHBIBJBKBLBMBNBOBPBQBRBSBTBUBVBWBXBYBZCACBCCCDCECFCGCHCICJCKCLCMCNCOCPCQCRCSCTCUCVCWCXCYCZDADBDCDDDEDFDGDHDIDJDKDLDMDNDODPDQDRDSDTDUDVDWDXDYDZEAEBECEDEEEFEGEHEIEJEKELEMENEOEPEQERESETEUEVEWEXEYEZFAFBFCFDFEFFFGFHFIFJFKFLFMFNFOFPFQFRFSFTFUFVFWFXFYFZGAGBGCGDGEGFGGGHGIGJGKGLGMGNGOGPGQGRGSGTGUGVGWGXGYGZHAHBHCHDHEHFHGHHHIHJHKHLHMHNHOHPHQHRHSHTHUHVHWHXHYHZIAIBICIDIEIFIGIHIIIJIKILIMINIOIPIQIRISITIUIVIWIXIYIZJAJBJCJDJEJFJGJHJIJJJKJLJMJNJOJPJQJRJSJTJUJVJWJXJYJZKAKBKCKDKEKFKGKHKIKJKKKLKMKNKOKPKQKRKSKTKUKVKWKXKYKZLALBLCLDLELFLGLHLILJLKLLLMLNLOLPLQLRLSLTLULVLWLXLYLZMAMBMCMDMEMFMGMHMIMJMKMLMMMNMOMPMQMRMSMTMUMVMWMXMYMZNANBNCNDNENFNGNHNINJNKNLNMNNNONPNQNRNSNTNUNVNWNXNYNZOAOBOCODOEOFOGOHOIOJOKOLOMONOOOPOQOROSOTOUOVOWOXOYOZPAPBPCPDPEPFPGPHPIPJPKPLPMPNPOPPPQPRPSPTPUPVPWPXPYPZQAQBQCQDQEQFQGQHQIQJQKQLQMQNQOQPQQQRQSQTQUQVQWQXQYQZRARBRCRDRERFRGRHRIRJRKRLRMRNRORPRQRRRSRTRURVRWRXRYRZSASBSCSDSESFSGSHSISJSKSLSMSNSOSPSQSRSSSTSUSVSWSXSYSZTATBTCTDTETFTGTHTITJTKTLTMTNTOTPTQTRTSTTTUTVTWTXTYTZUAUBUCUDUEUFUGUHUIUJUKULUMUNUOUPUQURUSUTUUUVUWUXUYUZVAVBVCVDVEVFVGVHVIVJVKVLVMVNVOVPVQVRVSVTVUVVVWVXVYVZWAWBWCWDWEWFWGWHWIWJWKWLWMWNWOWPWQWRWSWTWUWVWWWXWYWZXAXBXCXDXEXFXGXHXIXJXKXLXMXNXOXPXQXRXSXTXUXVXWXXXYXZYAYBYCYDYEYFYGYHYIYJYKYLYMYNYOYPYQYRYSYTYUYVYWYXYYYZ
due to the fact that after 'Z' comes 'AA', and 'AA' is smaller than 'Z'.
and so the correct way is print the next char will be
foreach (range('A', 'Z') as $char) {
echo $char;
}
that will print out
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Assumed that we can get the next char with modulo operator and chr(ord())
$next=chr((((ord($c) - ord('A')) + 1) % 26) + ord('A'));
I need a way to do the ++$char
using ord()
and chr()
functions, and so doing
$cmax=ord('A');
$char='A';
foreach (range('A', 'z') as $c) {
++$char;
$next=chr((((ord($c) - $cmax) + 1) % 26) + $cmax);
echo ord($c)." ".$c." ".$next." ".$char."\n";
}
it will print out:
65 A B B
66 B C C
67 C D D
68 D E E
69 E F F
70 F G G
71 G H H
72 H I I
73 I J J
74 J K K
75 K L L
76 L M M
77 M N N
78 N O O
79 O P P
80 P Q Q
81 Q R R
82 R S S
83 S T T
84 T U U
85 U V V
86 V W W
87 W X X
88 X Y Y
89 Y Z Z
90 Z A AA
91 [ B AB
92 \ C AC
93 ] D AD
94 ^ E AE
95 _ F AF
96 ` G AG
97 a H AH
98 b I AI
99 c J AJ
since in $next
char are not being accumulated, just getting the next one.
So how to get the same value in the 3rd and 4th columns using only chr( ord() )
?
[EDIT]
I'm going to clarify that I need the same output of this for loop
for ($char='A'; $char<='Z'; $char++) {
echo $char."\n";
}
that is like
A
B
C
...
Y
Z
AA
AB
AC
AD
AE
AF
...
YW
YX
YY
YZ
but only using ord()
and chr()
and a modulo operator.
Now the last byte (first byte) is simply obtained by modulo %26
:
$byte_two=chr( ( ((ord($code) - ord('A')) + 1) % 26) + ord('A') );
that in a foreach
on the given range('A', 'z')
:
foreach (range('A', 'z') as $code) {
$byte_one=chr( ( ((ord($code) - ord('A')) + 1) % 26) + ord('A') );
echo ord($code)."\t".$byte_one."\n";
}
will print outs exactly the last byte there as the same byte position in ++$char
. So I think I'm missing the $byte_two
, the first byte here.
[SUGGESTED IMPLEMENTATION]
This is one of the suggested implementation, and the most simple I was able to come out using a lookup table as suggested:
function lookupNextChar($c)
{
static $lookup_table=null;
if ($lookup_table === null) {
$lookup_table=explode(",","A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,AY,AZ,BA,BB,BC,BD,BE,BF,BG,BH,BI,BJ,BK,BL,BM,BN,BO,BP,BQ,BR,BS,BT,BU,BV,BW,BX,BY,BZ,CA,CB,CC,CD,CE,CF,CG,CH,CI,CJ,CK,CL,CM,CN,CO,CP,CQ,CR,CS,CT,CU,CV,CW,CX,CY,CZ,DA,DB,DC,DD,DE,DF,DG,DH,DI,DJ,DK,DL,DM,DN,DO,DP,DQ,DR,DS,DT,DU,DV,DW,DX,DY,DZ,EA,EB,EC,ED,EE,EF,EG,EH,EI,EJ,EK,EL,EM,EN,EO,EP,EQ,ER,ES,ET,EU,EV,EW,EX,EY,EZ,FA,FB,FC,FD,FE,FF,FG,FH,FI,FJ,FK,FL,FM,FN,FO,FP,FQ,FR,FS,FT,FU,FV,FW,FX,FY,FZ,GA,GB,GC,GD,GE,GF,GG,GH,GI,GJ,GK,GL,GM,GN,GO,GP,GQ,GR,GS,GT,GU,GV,GW,GX,GY,GZ,HA,HB,HC,HD,HE,HF,HG,HH,HI,HJ,HK,HL,HM,HN,HO,HP,HQ,HR,HS,HT,HU,HV,HW,HX,HY,HZ,IA,IB,IC,ID,IE,IF,IG,IH,II,IJ,IK,IL,IM,IN,IO,IP,IQ,IR,IS,IT,IU,IV,IW,IX,IY,IZ,JA,JB,JC,JD,JE,JF,JG,JH,JI,JJ,JK,JL,JM,JN,JO,JP,JQ,JR,JS,JT,JU,JV,JW,JX,JY,JZ,KA,KB,KC,KD,KE,KF,KG,KH,KI,KJ,KK,KL,KM,KN,KO,KP,KQ,KR,KS,KT,KU,KV,KW,KX,KY,KZ,LA,LB,LC,LD,LE,LF,LG,LH,LI,LJ,LK,LL,LM,LN,LO,LP,LQ,LR,LS,LT,LU,LV,LW,LX,LY,LZ,MA,MB,MC,MD,ME,MF,MG,MH,MI,MJ,MK,ML,MM,MN,MO,MP,MQ,MR,MS,MT,MU,MV,MW,MX,MY,MZ,NA,NB,NC,ND,NE,NF,NG,NH,NI,NJ,NK,NL,NM,NN,NO,NP,NQ,NR,NS,NT,NU,NV,NW,NX,NY,NZ,OA,OB,OC,OD,OE,OF,OG,OH,OI,OJ,OK,OL,OM,ON,OO,OP,OQ,OR,OS,OT,OU,OV,OW,OX,OY,OZ,PA,PB,PC,PD,PE,PF,PG,PH,PI,PJ,PK,PL,PM,PN,PO,PP,PQ,PR,PS,PT,PU,PV,PW,PX,PY,PZ,QA,QB,QC,QD,QE,QF,QG,QH,QI,QJ,QK,QL,QM,QN,QO,QP,QQ,QR,QS,QT,QU,QV,QW,QX,QY,QZ,RA,RB,RC,RD,RE,RF,RG,RH,RI,RJ,RK,RL,RM,RN,RO,RP,RQ,RR,RS,RT,RU,RV,RW,RX,RY,RZ,SA,SB,SC,SD,SE,SF,SG,SH,SI,SJ,SK,SL,SM,SN,SO,SP,SQ,SR,SS,ST,SU,SV,SW,SX,SY,SZ,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,TS,TT,TU,TV,TW,TX,TY,TZ,UA,UB,UC,UD,UE,UF,UG,UH,UI,UJ,UK,UL,UM,UN,UO,UP,UQ,UR,US,UT,UU,UV,UW,UX,UY,UZ,VA,VB,VC,VD,VE,VF,VG,VH,VI,VJ,VK,VL,VM,VN,VO,VP,VQ,VR,VS,VT,VU,VV,VW,VX,VY,VZ,WA,WB,WC,WD,WE,WF,WG,WH,WI,WJ,WK,WL,WM,WN,WO,WP,WQ,WR,WS,WT,WU,WV,WW,WX,WY,WZ,XA,XB,XC,XD,XE,XF,XG,XH,XI,XJ,XK,XL,XM,XN,XO,XP,XQ,XR,XS,XT,XU,XV,XW,XX,XY,XZ,YA,YB,YC,YD,YE,YF,YG,YH,YI,YJ,YK,YL,YM,YN,YO,YP,YQ,YR,YS,YT,YU,YV,YW,YX,YY,YZ");
echo implode($lookup_table,',')."\n";
echo count($lookup_table)."\n";
}
$idx=( ((ord($c) - ord('A')) + 1 ) % count($lookup_table));
return $lookup_table[ $idx ];
}
To prove it, just get the next $char
from the range('A','z')
:
$sum=$n='A';
foreach (range('A','z') as $c) {
$n=lookupNextChar($c,$lookup_table);
++$sum;
echo "$c\t$n\t$sum\n";
}
and then I get
A B B
B C C
C D D
D E E
E F F
F G G
.. .. ..
X Y Y
Y Z Z
Z AA AA
[ AB AB
\ AC AC
] AD AD
^ AE AE
.. .. ..
x CD BD
y DE BE
z EF BF
that is want I wanted to achieve as output, even if using a different way.
NOTE. Of course, the lookup table was generated using the ++
operator then:
$lookup_table=array();
for ($char='A'; $char<='Z'; $char++) {
array_push($lookup_table,$char);
}
that does not imply to be used programmatically in the javascript porting (see comments).
Requirements: Allow increment of a string in the same manner as PHP (please see links in the question). At first I thought: How unusual? I still think that.
Ok, We need to produce something that will do that without using the PHP routines.
Ok, what do have?
I thought of it as a 'base26' number. I chose to implement it as:
Working demonstration at eval.in
For convenience, I put it in PHP class:
I try and make the code understandable...
class base26 { // it only does increment / addition
const
numberBase = 26;
// These are characters used where A => 1 and Z is 26
// These can be changed as you wish. They could be multibyte?
// 0 1 2 2
// 12345678901234567890123456
public static $displayChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* current number - least significant digit is digit[0]
*
* @var array $digit - integer with range 1 .. 26
*/
public $digit = array();
// initialize if nothing provided
public function __construct($letters = 'A')
{
$this->init($letters);
}
/**
* Convert letters to base26 and store in the $digit array
*
* @param string $letters
* @return void
*/
public function init($letters = 'A')
{
$this->digit = array();
$lsd = strlen($letters) - 1;
for ($idx = $lsd; $idx >= 0; $idx--) {
$this->digit[] = $this->charValue($letters[$idx]);
}
}
/**
* Increment the 'least significant digit' and then propagate the `carry`
* when it exceeds the number base limit.
*
* @param integer $int -- default to 1
* @return void
*/
public function inc($int = 1) // addition with carry - easy to understand
{
$curDigit = 0; // add to least significant digit
$carry = 0;
$this->digit[$curDigit] += $int;
while ($this->digit[$curDigit] > self::numberBase) {
$carry = 1; // next carry
$this->digit[$curDigit] %= self::numberBase; // reset digit
$curDigit++; // check next digit...
if (isset($this->digit[$curDigit])) {
$this->digit[$curDigit] += $carry;
}
else {
$this->digit[$curDigit] = $carry;
}
}
}
/**
* Convert a number to a character to display
*
* @param intger $int
*
* @return char
*/
public function toChar($int)
{
return self::$displayChars[$int - 1];
}
/**
* The value of the character in the number base range
*
* @param undefined $letter
* @return integer
*/
public function charValue($letter)
{
return stripos(self::$displayChars, $letter) + 1;
}
/**
* return the current number values as a string using the display characters
*
* @return string
*/
public function current()
{
$digitCount = count($this->digit);
$outStr = str_pad('A', count($this->digit));
$outIdx = $digitCount - 1;
for ($idx = 0; $idx < $digitCount; $idx++, $outIdx-- ) {
$outStr[$outIdx] = $this->toChar($this->digit[$idx]);
}
return $outStr;
}
}
// ---------------------------------------------
// show increment from Z -> AA
echo PHP_EOL;
$b26 = new base26('Z');
echo $b26->current() .'|';
$b26->inc();
echo $b26->current() .'|';
Output: Z|AA|
// ---------------------------------------------
// show increment from 'FY' -> FZ -> GA
echo PHP_EOL;
$b26->init('FY');
echo $b26->current() .'|';
$b26->inc();
echo $b26->current() .'|';
$b26->inc();
echo $b26->current() .'|';
Output: FY|FZ|GA|
// ---------------------------------------------
// Show it does what PHP does...
echo PHP_EOL;
$b26 = new base26();
while ($b26->current() <= 'Z') {
echo $b26->current() .'|';
$b26->inc();
}
Output: A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|AA|AB|AC|AD|AE|AF|AG|AH|AI|AJ|AK|AL|AM|AN|AO ...