80

I would like to write a function that takes in 3 characters and increments it and returns the newly incremented characters as a string.

I know how to increase a single letter to the next one but how would I know when to increase the second letters and then stop and then increase the first letter again to have a sequential increase?

So if AAA is passed, return AAB. If AAZ is passed return ABA (hard part).

I would appreciate help with the logic and what php functions will be useful to use.

Even better, has some done this already or there is a class available to do this??

Thanks all for any help

Ramratan Gupta
  • 1,056
  • 3
  • 17
  • 39
Abs
  • 56,052
  • 101
  • 275
  • 409
  • I don't understand. AAA=>AAB but AAZ=>ABA. How do you decide which letters to increment? You said increment the second letters, so I take it you mean all but the first one? Shouldn't it be AAA=>ABB if you increment all but the first letter by one? – Raoul Duke Aug 25 '10 at 15:01
  • 3
    @Raoul Duke: it's base 26 with no numbers, just letters. – Matt Ellen Aug 25 '10 at 15:04
  • @Raoul - just run $X = 'AAA'; for($i = 1; $i < 1024; $i++) { echo $X++,'
    '; } to see what is meant.
    – Mark Baker Aug 25 '10 at 15:07

8 Answers8

164

Character/string increment works in PHP (though decrement doesn't)

$x = 'AAZ';
$x++;
echo $x;  // 'ABA'
gronostaj
  • 2,231
  • 2
  • 23
  • 43
Mark Baker
  • 209,507
  • 32
  • 346
  • 385
24

You can do it with the ++ operator.

$i = 'aaz';
$i++;
print $i;

aba

However this implementation has some strange things:

for($i = 'a'; $i < 'z'; $i++) print "$i ";

This will print out letters from a to y.

for($i = 'a'; $i <= 'z'; $i++) print "$i ";

This will print out lettes from a to z and it continues with aa and ends with yz.

tamasd
  • 5,747
  • 3
  • 25
  • 31
  • 12
    It's not strange, it's just unintuitive. If `$i == 'z'` the condition is still true and `for` loop will continue, setting `$i` to `'aa'`. It's only when `$i` reaches `'za'` that the condition becomes false. – CJ Dennis Aug 27 '14 at 12:14
21

As proposed in PHP RFC: Strict operators directive:

Using the increment function on a string will throw a TypeError when strict_operators is enabled.

Even though the RFC's state is Withdrawn, PHP will sooner or later go that direction of adding operator strictness. Therefore, you should not be incrementing strings.

a-z/A-Z ranges

If you know your letters will stay in range a-z/A-Z (not surpass z/Z), you can use the solution that converts letter to ASCII code, increments it, and converts back to letter.

Use ord() a chr():

$letter = 'A';
$letterAscii = ord($letter);
$letterAscii++;
$letter = chr($letterAscii); // 'B'
  1. ord() converts the letter into ASCII num representation
  2. that num representation is incremented
  3. using chr() the number gets converted back to the letter

As discovered in comments, be careful. This iterates ASCII table so from Z (ASCII 90), it does not go to AA, but to [ (ASCII 91).

Going beyond z/Z

If you dare to go further and want z became aa, this is what I came up with:

final class NextLetter
{
    private const ASCII_UPPER_CASE_BOUNDARIES = [65, 91];
    private const ASCII_LOWER_CASE_BOUNDARIES = [97, 123];

    public static function get(string $previous) : string
    {
        $letters = str_split($previous);
        $output = '';
        $increase = true;

        while (! empty($letters)) {
            $letter = array_pop($letters);

            if ($increase) {
                $letterAscii = ord($letter);
                $letterAscii++;
                if ($letterAscii === self::ASCII_UPPER_CASE_BOUNDARIES[1]) {
                    $letterAscii = self::ASCII_UPPER_CASE_BOUNDARIES[0];
                    $increase = true;
                } elseif ($letterAscii === self::ASCII_LOWER_CASE_BOUNDARIES[1]) {
                    $letterAscii = self::ASCII_LOWER_CASE_BOUNDARIES[0];
                    $increase = true;
                } else {
                    $increase = false;
                }

                $letter = chr($letterAscii);
                if ($increase && empty($letters)) {
                    $letter .= $letter;
                }
            }

            $output = $letter . $output;
        }

        return $output;
    }
}

I'm giving you also 100% coverage if you intend to work with it further. It tests against original string incrementation ++:

    /**
     * @dataProvider letterProvider
     */
    public function testIncrementLetter(string $givenLetter) : void
    {
        $expectedValue = $givenLetter;

        self::assertSame(++$expectedValue, NextLetter::get($givenLetter));
    }

    /** 
     * @return iterable<array-key, array<string>>
     */
    public static function letterProvider() : iterable
    {
        yield ['A'];
        yield ['a'];
        yield ['z'];
        yield ['Z'];
        yield ['aaz'];
        yield ['aaZ'];
        yield ['abz'];
        yield ['abZ'];
    }
simPod
  • 11,498
  • 17
  • 86
  • 139
  • I've tried it, but it doesn't go from `z` to `aa` but to `{` and what not weird characters.. – ArendE Jul 19 '19 at 15:47
  • 1
    Hmm, good point, I have not ever gone so far. I've editted the answer. Giving feedback helps others too, thanks ;) I'll try to write working solution without incrementing strings and post it here anyway. – simPod Jul 19 '19 at 15:55
4

To increment or decrement in the 7bits 128 chars ASCII range, the safest:

$CHAR = "l";
echo chr(ord($CHAR)+1)." ".chr(ord($CHAR)-1);
/* m k */

So, it is normal to get a backtick by decrementing a, as the ascii spec list

Print the whole ascii range:

for ($i = 0;$i < 127;$i++){
    echo chr($i);
}
/* !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ */

More infos about ANSI 7 bits ASCII: man ascii


To increment or decrement in the 8-bits extended 256 chars UTF-8 range.

This is where it starts to differ regarding the host machine charset. but those charsets are all available on modern machines. From php, the safest is to use the php-mbstring extension: https://www.php.net/manual/en/function.mb-chr.php

Extended ASCII (EASCII or high ASCII) character encodings are eight-bit or larger encodings that include the standard seven-bit ASCII characters, plus additional characters. https://en.wikipedia.org/wiki/Extended_ASCII

More info, as example: man iso_8859-9

   ISO 8859-1    West European languages (Latin-1)
   ISO 8859-2    Central and East European languages (Latin-2)
   ISO 8859-3    Southeast European and miscellaneous languages (Latin-3)
   ISO 8859-4    Scandinavian/Baltic languages (Latin-4)
   ISO 8859-5    Latin/Cyrillic
   ISO 8859-6    Latin/Arabic
   ISO 8859-7    Latin/Greek
   ISO 8859-8    Latin/Hebrew
   ISO 8859-9    Latin-1 modification for Turkish (Latin-5)
   ISO 8859-10   Lappish/Nordic/Eskimo languages (Latin-6)
   ISO 8859-11   Latin/Thai
   ISO 8859-13   Baltic Rim languages (Latin-7)
   ISO 8859-14   Celtic (Latin-8)
   ISO 8859-15   West European languages (Latin-9)
   ISO 8859-16   Romanian (Latin-10)

Example, we can find the symbol in ISO 8859-7:

244   164   A4     €     EURO SIGN

To increment or decrement in the 16 bits UTF-16 Unicode range:

Here is a way to generate the whole unicode charset, by generating html entities and converting to utf8. Run it online

for ($x = 0; $x < 262144; $x++){
  echo html_entity_decode("&#".$x.";",ENT_NOQUOTES,"UTF-8");
}

Same stuff, but the range goes up to (16^4 * 4)!

echo html_entity_decode('&#33;',ENT_NOQUOTES,'UTF-8');
/* ! */
echo html_entity_decode('&#34;',ENT_NOQUOTES,'UTF-8');
/* " */

To retrieve the unicode symbol,using the base10 decimal representation of the character.

echo html_entity_decode('&#8364;',ENT_NOQUOTES,'UTF-8');
/* € */

The same symbol, using the base16 hexadecimal representation:

echo html_entity_decode('&#'.hexdec("20AC").';',ENT_NOQUOTES,'UTF-8');
/* € */

First 32 bits are reserved for special control characters, output garbage �����, but have a meaning.

NVRM
  • 11,480
  • 1
  • 88
  • 87
1

You are looking at a number representation problem. This is base24 (or however many numbers your alphabet has). Lets call the base b.

Assign a number to each letter in alphabet (A=1, B=2, C=3).

Next, figure out your input "number": The representation "ABC" means A*b^2 + B*b^1 + C*b^0 Use this formula to find the number (int). Increment it.

Next, convert it back to your number system: Divide by b^2 to get third digit, the remainder (modulo) by b^1 for second digit, the remainder (modulo) by `b^0^ for last digit.

This might help: How to convert from base10 to any other base.

Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
  • 1
    right. I can never really remember how many letters the alphabet has. It also depends on how you count (some people ommit "j" and "v" just doesn't seem like a real letter to me... – Daren Thomas Aug 25 '10 at 18:03
0
 <?php 
$values[] = 'B';
$values[] = 'A';
$values[] = 'Z';
foreach($values as $value ){
  if($value == 'Z'){ 
       $value = '-1';
    }
$op = ++$value;
echo $op;
}
?>
0

You could use the ASCII codes for alpha numerics. From there you increment and decrement to get the previous/next character.

You could split your string in single characters and then apply the transformations on these characters.

Just some thoughts to get you started.

Andreas
  • 5,305
  • 4
  • 41
  • 60
-3

I have these methods in c# that you could probably convert to php and modify to suit your needs, I'm not sure Hexavigesimal is the exact name for these though...

#region Hexavigesimal (Excel Column Name to Number)
public static int FromHexavigesimal(this string s)
{
    int i = 0;
    s = s.Reverse();
    for (int p = s.Length - 1; p >= 0; p--)
    {
        char c = s[p];
        i += c.toInt() * (int)Math.Pow(26, p);
    }

    return i;
}

public static string ToHexavigesimal(this int i)
{
    StringBuilder s = new StringBuilder();

    while (i > 26)
    {
        int r = i % 26;
        if (r == 0)
        {
            i -= 26;
            s.Insert(0, 'Z');
        }
        else
        {
            s.Insert(0, r.toChar());
        }

        i = i / 26;
    }

    return s.Insert(0, i.toChar()).ToString();
}

public static string Increment(this string s, int offset)
{
    return (s.FromHexavigesimal() + offset).ToHexavigesimal();
}

private static char toChar(this int i)
{
    return (char)(i + 64);
}

private static int toInt(this char c)
{
    return (int)c - 64;
}
#endregion

EDIT

I see by the other answers that in PHP you can use ++ instead, nice!

CaffGeek
  • 21,856
  • 17
  • 100
  • 184
  • 1
    This code is unnecessarily complex. See http://en.wikipedia.org/wiki/Hexavigesimal#Conversion_algorithm_.28alphabet-only_system.29 for a simpler algorithm. – Jim Balter Jun 11 '14 at 10:35