0

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).

Community
  • 1
  • 1
loretoparisi
  • 15,724
  • 11
  • 102
  • 146
  • Not sure if you want the 3rd column to be like the 4th, or the 4th column to be like the 3rd. And why such constraints? – Mat Mar 16 '16 at 19:29
  • Note that after you pass 'Z' var_dump($char) is a string(2) instead of string(1). It might be an implementation quirk of how php handles strings internally that you end up with a two-byte strings from a char, and definitely something I don't completely grasp. Maybe I'm wrong? – sal Mar 16 '16 at 19:41
  • @Mat I want the output of the pre increment `++$char` using only `ord() and `chr()` functions, so not using the `+` operator. That said this implies to have the 3rd column exactly equals the 4th column, so having 2 bytes as well when passing `Z`, so that having `AA`, `AB`, etc. – loretoparisi Mar 16 '16 at 20:40
  • 1
    Understood now your request. My answer below was for the opposite case, which IMO matches better a "next-char" request. It might help us here understand better why you need to implement the `++` operator *on a string/char* by making double function calls (ord/chr) instead of for example creating a lookup table. – sal Mar 17 '16 at 00:41
  • @sal right. So I have an algorithm in `php` with respect some conditions: a specific `O(n)`, no `++`, `+` operators (due to no overload of `+`, `-`, etc). I have used `php.js` node module to port to `javascript` converting everything to javascript `automatically`. So I cannot make changes that does not guarantee exactly the same conditions. Look up tables could be the right options, but in that case I will change how it works: that is generating the sequence of symbols in the `range(A,z)` but modulo: `$next=chr( ( ((ord($code) - ord('A')) + 1) % 26) + ord('A') );`...(continue) – loretoparisi Mar 17 '16 at 10:29
  • @sal ...for the last byte and some modulo `%26` for the first byte in order to have the `AA, AB...BA...YZ` (see EDIT) exactly how it happens with `++$char` but using modulo operators. The last byte (first byte) is simple as `$byte_one=chr( ( ((ord($code) - ord('A')) + 1) % 26) + ord('A') );`, I'm missing the first byte `$byte_two` modulo calculation using this way... – loretoparisi Mar 17 '16 at 10:46
  • @RyanVincent overthinking for sure! yes and no the other, I mean I needed a set of unique symbols based on `[A-Z]` range extendable to two bytes so that the symbols must match `/[A-Z]?[A-Z]/`. – loretoparisi Mar 17 '16 at 15:14
  • Why not implementing a class that does that exactly? I don't know if you can override the `++` operator in php, but at least you get something cleaner. – sal Mar 17 '16 at 22:18
  • 1
    maybe interesting? a class that implements the functions asked for. [demonstration at eval.in](https://eval.in/538572). Worth posting as an answer? – Ryan Vincent Mar 18 '16 at 10:42
  • @RyanVincent amazing! thank you, that is a comprehensive approach. Please post as answer so I will be glad to accept it as the solution! – loretoparisi Mar 18 '16 at 13:23

2 Answers2

1

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:

  • using an array for each digit that had a range of 1 .. 26.
  • convert the numerical digit in the array to display characters using an array lookup.

Working demonstration at eval.in

For convenience, I put it in PHP class:

I try and make the code understandable...

The base26 class:

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

Examples:

// ---------------------------------------------
// 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 ...
Ryan Vincent
  • 4,483
  • 7
  • 22
  • 31
0

As I observed in my note, after you reach/pass the value of 'Z', your $char variable becomes a 2 char string effectively. I cannot comment on details about this, but the following would be a probably not too expensive generic solution to your request: always use the "last" character of the string.

  $cmax=ord('A');
  $char='A';
  foreach (range('A', 'z') as $c) {
    $next=chr((((ord($c) - $cmax) + 1) % 26) + $cmax);
    ++$char;
    echo ord($c)." ".$c." ".$next." ".$char[strlen($char)-1]."\n";
  }

https://eval.in/537739

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 A
91 [ B B
92 \ C C
93 ] D D
94 ^ E E
95 _ F F
96 ` G G
97 a H H
98 b I I
99 c J J
100 d K K
101 e L L
102 f M M
103 g N N
104 h O O
105 i P P
106 j Q Q
107 k R R
108 l S S
109 m T T
110 n U U
111 o V V
112 p W W
113 q X X
114 r Y Y
115 s Z Z
116 t A A
117 u B B
118 v C C
119 w D D
120 x E E
121 y F F
122 z G G
sal
  • 3,515
  • 1
  • 10
  • 21
  • So I want to have the the same behavior of `$char` when using a `++` pre-increment operator, but not using `+` pre increment. Only `ord() and `chr()`. Why? Since in my environment I cannot use `++`... This means that I need to have `AA`, `AB` as well in the 3rd column, exactly like in the 4th column when using a `range('A', 'z')`. – loretoparisi Mar 16 '16 at 20:44