string increment

Last night I was practicing incrementing strings. What it needs to do is increment the last character if it is a digit or letter. Special characters are ignored.

The code that I have written does the job but I have a feeling it can be accomplished in a more elegant way. Or a faster way.

Can somebody suggest faster, easier, more elegant approaches.

Below the code I have written:

public static string Increment(this String str)
{
var charArray = str.ToCharArray();
for(int i = charArray.Length - 1; i >= 0; i--)
{
if (Char.IsDigit(charArray[i]))
{
if(charArray[i] == '9')
{
charArray[i] = '0';
continue;
}

charArray[i]++;
break;
}
else if(Char.IsLetter(charArray[i]))
{
if(charArray[i] == 'z')
{
charArray[i] = 'a';
continue;
}
else if(charArray[i] == 'Z')
{
charArray[i] = 'A';
continue;
}

charArray[i]++;
break;
}
}

return new string(charArray);
}

• Is the intent to alter only the last character in the string or each character or the last known letter or digit, which may not be the last character in the string. Perhaps you could provide some examples of before and after strings. Nov 1, 2021 at 11:09
• Your code fails to increment 999 and ZZZ correctly. I would have expected 1000 and AAAA. but it shouldn't be too hard to fix =)
– jdt
Nov 1, 2021 at 19:34
• @RickDavin, It is intended to only alter the last character of the string. For example A1A becomes A1B, H98 becomes H99 etc. It is intended to only go up to the number of characters in the orignal string. So what upkajdt says is true but intended. Nov 2, 2021 at 7:04

Whenever you get a non-elegant method, first thing, try to break your logic into smaller logic

 private static bool isMaxChar(this char ch)
{
return ch == '9' || ch == 'z' || ch == 'Z';
}

public static char Increment(this char ch)
{
if (Char.IsDigit(ch))
return (char)((ch + 1 - '0') % 10 + '0');
if (Char.IsLower(ch))
return (char)((ch + 1 - 'a') % 26 + 'a');
if (Char.IsUpper(ch))
return (char)((ch + 1 - 'A') % 26 + 'A');

return ch;
}


by using the above helper methods, your code will be clearer and elegant

 public static string Increment(this String str)
{
var charArray = str.ToCharArray();
for (int i = charArray.Length - 1; i >= 0; i--)
{
char originalChar = charArray[i];
charArray[i] = charArray[i].Increment();

if (!originalChar.isMaxChar() && char.IsLetterOrDigit(originalChar))
break; // break when update the first alphanumeric char and it's not a max char
}
return new string(charArray);
}


I have written unit tests and run against the above code, and it gives the same results as your code

[Theory]
[InlineData("a", "b")]
[InlineData("z", "a")]
[InlineData("c", "d")]

[InlineData("az", "ba")]
[InlineData("a9", "b0")]

[InlineData("a1a", "a1b")]
[InlineData("AAA", "AAB")]
[InlineData("abc", "abd")]

[InlineData("H98", "H99")]
[InlineData("H99", "I00")]
[InlineData("a99", "b00")]
[InlineData("I00", "I01")]
[InlineData("azz", "baa")]

[InlineData("bl9Zz", "bm0Aa")]

[InlineData("zzz", "aaa")]
[InlineData("ZZZ", "AAA")]
[InlineData("999", "000")]
[InlineData("z99", "a00")]

[InlineData("__a__", "__b__")]
[InlineData("__z__", "__a__")]
public void stringIncremental(string input, string expected)
{
string result = input.Increment();
Assert.Equal(result, expected);

}

[Theory]
[InlineData('a', 'b')]
[InlineData('z', 'a')]
[InlineData('c', 'd')]
[InlineData('k', 'l')]

[InlineData('A', 'B')]
[InlineData('Z', 'A')]
[InlineData('G', 'H')]
[InlineData('K', 'L')]

[InlineData('1', '2')]
[InlineData('9', '0')]
[InlineData('0', '1')]
[InlineData('5', '6')]

[InlineData('%', '%')]
[InlineData('-', '-')]
[InlineData('_', '_')]
[InlineData('/', '/')]
public void charIncremental(char input, char expected)
{
char result = input.Increment();
Assert.Equal(result, expected);
}

$$$$


Update 2

Correct increment when invalid char (not digit nor letter) between valid chars.

Update

Without changing too much of code, you could solve it by using the mod operator. And by the tip from @upkajdt, it would be:

public static string Increment(string str)
{
var charArray = str.ToCharArray();
for (int i = charArray.Length - 1; i >= 0; i--)
{
if (Char.IsDigit(charArray[i]))
{
charArray[i] = (char)((charArray[i] + 1 - '0') % 10 + '0');
}
else if (Char.IsLower(charArray[i]))
{
charArray[i] = (char)((charArray[i] + 1 - 'a') % 26 + 'a');
}
else if (Char.IsUpper(charArray[i]))
{
charArray[i] = (char)((charArray[i] + 1 - 'A') % 26 + 'A');
}
else
{
continue;
}
if (charArray[i] == '0' || charArray[i] == 'a' || charArray[i] == 'A')
continue;
break;
}
return new string(charArray);
}

• I don't think this is exactly what the OP had in mind, although I'm not really sure what he trying to accomplish =) You know that in C# you can also use character constants such as charArray[i] = (char)((charArray[i] + 1 - 'a') % 26 + 'a');
– jdt
Nov 1, 2021 at 20:34
• I assumed this was what OP meant, because he said "The code that I have written does the job". Nice tip to use char constants. Nov 1, 2021 at 21:29
• The code in the question looks incrementing a big-endian mixed base-26/10 number: start at the last "digit", proceed to preceding digit when there was "a wrap-around/overflow/carry", only. The breaks you are using are not necessary` they are quintessential, on the contrary, while not indispensable. Nov 1, 2021 at 21:46
• @greybeard, i agree with most of what you say but fail to see how endianness is relevant here?
– jdt
Nov 1, 2021 at 22:33
• Relevance of endianness: You start with the least significant "digit" and propagate carries in the direction of increasing significance. When the least significant end is the last, the first is the most significant: big-endian (and loop from length down). (No relation to or relevance of the endianness of the runtime-platform.) Nov 2, 2021 at 5:19