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);
}
2 Answers 2
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);
}
-
\$\begingroup\$ 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');
\$\endgroup\$jdt– jdt2021年11月01日 20:34:54 +00:00Commented Nov 1, 2021 at 20:34 -
\$\begingroup\$ 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. \$\endgroup\$takihama– takihama2021年11月01日 21:29:53 +00:00Commented Nov 1, 2021 at 21:29
-
\$\begingroup\$ 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. \$\endgroup\$greybeard– greybeard2021年11月01日 21:46:19 +00:00Commented Nov 1, 2021 at 21:46 -
\$\begingroup\$ @greybeard, i agree with most of what you say but fail to see how endianness is relevant here? \$\endgroup\$jdt– jdt2021年11月01日 22:33:07 +00:00Commented Nov 1, 2021 at 22:33
-
1\$\begingroup\$ 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.) \$\endgroup\$greybeard– greybeard2021年11月02日 05:19:29 +00:00Commented Nov 2, 2021 at 5:19
999
andZZZ
correctly. I would have expected1000
andAAAA
. but it shouldn't be too hard to fix =) \$\endgroup\$