Reputation: 13511
I have a field representing an "Account Number" that is anything but a number most of the time. I need to do some auto-incrementing of these "numbers". Clearly non-ideal for doing math with. The rule that we've decided works for us is that we want to find the right-most group of numbers and auto-increment them by one and return the rebuilt string (even if this makes it one character longer).
Some examples of the numbers are:
I'm working with C#/.NET 4.0. I listed Regex as a tag but that isn't a requirement. This solution need not be in Regular Expressions.
Any thoughts on a good way to do this? Ideal performance isn't a major concern. I'd rather have clear and easy-to-understand/maintain code for this unless it's all wrapped up in a Regex.
Thanks!
Upvotes: 8
Views: 4951
Reputation: 120400
var src = "ap45245jpb1234h";
var match = Regex.Match(src, @"(?<=(\D|^))\d+(?=\D*$)");
if(match.Success)
{
var number = int.Parse(match.Value) + 1;
var newNum=string.Format(
"{0}{1}{2}",
src.Substring(0,match.Index),
number,
src.Substring(match.Index + match.Length));
newNum.Dump(); //ap45245jpb1235h
}
Explaining the regex: starting either from (the start of the string) or (a non-digit), match one or more digits that are followed by zero or more non-digits then the end of the string.
Of course, if the extracted number has leading zeros, things will go wrong. I'll leave this as an exercise to the reader.
Using a MatchEvaluator (as suggested by @LB in their answer) this becomes somewhat lighter:
Regex.Replace(
src,
@"(?<=(\D|^))\d+(?=\D*$)",
m => (int.Parse(m.Value)+1).ToString())
Upvotes: 6
Reputation: 75222
string[] src = { "AC1234", "GS3R2C1234", "1234", "A-1234", "AC1234g",
"GS3R2C1234g", "1234g", "A-1234g", "999", "GS3R2C9999g" };
foreach (string before in src)
{
string after = Regex.Replace(before, @"\d+(?=\D*$)",
m => (Convert.ToInt64(m.Value) + 1).ToString());
Console.WriteLine("{0} -> {1}", before, after);
}
output:
AC1234 -> AC1235
GS3R2C1234 -> GS3R2C1235
1234 -> 1235
A-1234 -> A-1235
AC1234g -> AC1235g
GS3R2C1234g -> GS3R2C1235g
1234g -> 1235g
A-1234g -> A-1235g
999 -> 1000
GS3R2C9999g -> GS3R2C10000g
notes:
@LB's use of lambda expression as MatchEvaluator FTW!
From @spender's answer, the lookahead - (?=\D*$)
- ensures that only the last group of digits is matched (but the lookbehind - (?<=(\D|^))
- isn't needed).
The RightToLeft option as used by @JeffMoser allows it to match the last group of digits first, but there's no static Replace
method that allows you to (1) specify RegexOptions, (2) use a MatchEvaluator, and (3) limit the number of replacements. You have to instantiate a Regex object first:
string[] src = { "AC1234", "GS3R2C1234", "1234", "A-1234", "AC1234g",
"GS3R2C1234g", "1234g", "A-1234g", "999", "GS3R2C9999g" };
foreach (string before in src)
{
Regex r = new Regex(@"\d+", RegexOptions.RightToLeft);
string after = r.Replace(before, m => (Convert.ToInt64(m.Value) + 1).ToString(), 1);
Console.WriteLine("{0} -> {1}", before, after);
}
output:
AC1234 -> AC1235
GS3R2C1234 -> GS3R2C1235
1234 -> 1235
A-1234 -> A-1235
AC1234g -> AC1235g
GS3R2C1234g -> GS3R2C1235g
1234g -> 1235g
A-1234g -> A-1235g
999 -> 1000
GS3R2C9999g -> GS3R2C10000g
Upvotes: 1
Reputation: 20043
If you want a simple Regex that stitches together the result:
private static readonly Regex _ReverseDigitFinder = new Regex("[0-9]+", RegexOptions.RightToLeft);
public static string IncrementAccountNumber(string accountNumber) {
var lastDigitsMatch = _ReverseDigitFinder.Match(accountNumber);
var incrementedPart = (Int64.Parse(lastDigitsMatch.Value) + 1).ToString();
var prefix = accountNumber.Substring(0, lastDigitsMatch.Index);
var suffix = accountNumber.Substring(lastDigitsMatch.Index + lastDigitsMatch.Length);
return prefix + incrementedPart + suffix;
}
Notes:
If you want to use LINQ:
private static readonly Regex _ReverseAccountNumberParser = new Regex("(?<digits>[0-9]+)|(?<nonDigits>[^0-9]+)", RegexOptions.RightToLeft);
public static string IncrementAccountNumber(string accountNumber) {
bool hasIncremented = false;
return String.Join("",
_ReverseAccountNumberParser
.Matches(accountNumber)
.Cast<Match>()
.Select(m => {
var nonDigits = m.Groups["nonDigits"].Value;
if(nonDigits.Length > 0) {
return nonDigits;
}
var digitVal = Int64.Parse(m.Groups["digits"].Value);
if(!hasIncremented) {
digitVal++;
}
hasIncremented = true;
return digitVal.ToString();
})
.Reverse());
}
For what it's worth, I accidentally misread this initially and thought you wanted carry bits (i.e. "A3G999 -> A4G000"). This is more interesting and requires carry state:
public static string IncrementAccountNumberWithCarry(string accountNumber) {
bool hasIncremented = false;
bool needToCarry = false;
var result = String.Join("",
_ReverseAccountNumberParser
.Matches(accountNumber)
.Cast<Match>()
.Select(m => {
var nonDigits = m.Groups["nonDigits"].Value;
if (nonDigits.Length > 0) {
return nonDigits;
}
var oldDigitVal = m.Groups["digits"].Value;
var digitVal = Int64.Parse(oldDigitVal);
if(needToCarry) {
digitVal++;
}
if (!hasIncremented) {
digitVal++;
hasIncremented = true;
}
var newDigitVal = digitVal.ToString();
needToCarry = newDigitVal.Length > oldDigitVal.Length;
if(needToCarry) {
newDigitVal = newDigitVal.Substring(1);
}
return newDigitVal;
})
.Reverse());
if(needToCarry) {
result = "1" + result;
}
return result;
}
Test cases:
Debug.Assert(IncrementAccountNumber("AC1234") == "AC1235");
Debug.Assert(IncrementAccountNumber("GS3R2C1234") == "GS3R2C1235");
Debug.Assert(IncrementAccountNumber("1234") == "1235");
Debug.Assert(IncrementAccountNumber("A-1234") == "A-1235");
Debug.Assert(IncrementAccountNumber("AC1234g") == "AC1235g");
Debug.Assert(IncrementAccountNumber("GS3R2C1234g") == "GS3R2C1235g");
Debug.Assert(IncrementAccountNumber("1234g") == "1235g");
Debug.Assert(IncrementAccountNumber("A-1234g") == "A-1235g");
Debug.Assert(IncrementAccountNumber("999") == "1000");
Debug.Assert(IncrementAccountNumber("GS3R2C9999g") == "GS3R2C10000g");
Debug.Assert(IncrementAccountNumberWithCarry("GS3R2C9999g") == "GS3R3C0000g");
Debug.Assert(IncrementAccountNumberWithCarry("999") == "1000");
Upvotes: 1
Reputation: 14502
If I understand you correctly, you would like to add one to the number which is right-most within a certain string.
You could use Regex as others suggested, but since you are trying to do something very specific, Regex will prove slower than implementing an algorithm just for what you do.
You can test this against the Regex solution, and see for yourself that this will be a lot faster:
I ran both 1 million times and timed it with Stopwatch.
Results:
Regex - 10,808,533 ticks
My way - 253,355 ticks
About 40 times faster!!!
Conclusion: Specific solutions for specific problems.
My way is A LOT faster.
And here's the code:
// Goes through a string from end to start, looking for the last digit character.
// It then adds 1 to it and returns the result string.
// If the digit was 9, it turns it to 0 and continues,
// So the digit before that would be added with one.
// Overall, it takes the last numeric substring it finds in the string,
// And replaces it with itself + 1.
private static unsafe string Foo(string str)
{
var added = false;
fixed (char* pt = str)
{
for (var i = str.Length - 1; i >= 0; i--)
{
var val = pt[i] - '0';
// Current char isn't a digit
if (val < 0 || val > 9)
{
// Digits have been found and processed earlier
if (added)
{
// Add 1 before the digits,
// Because if the code reaches this,
// It means it was something like 999,
// Which should become 1000
str = str.Insert(i + 1, "1");
break;
}
continue;
}
added = true;
// Digit isn't 9
if (val < 9)
{
// Set it to be itself + 1, and break
pt[i] = (char)(val + 1 + '0');
break;
}
// Digit is 9. Set it to be 0 and continue to previous characters
pt[i] = '0';
// Reached beginning of string and should add 1 before digits
if (i == 0)
{
str = str.Insert(0, "1");
}
}
}
return str;
}
Upvotes: 3
Reputation: 655
You can try using String.Split
. You could use something like:
NameSplit=AccountNumber.split(new Char[] {'a','b','....'z'});
Then you could loop on the array to find the last number (loop from NameSplit.length
to 1
, first numeric found by Int32.TryParse
), increment that number, and then concatenate the array together again with String.Concat
.
It would probably be less efficient than RegEx, but I think it would be easier to understand for people who don't understand RegEx..
Upvotes: 0
Reputation: 15881
I suggest the following:
string IncrementAccountNumber(string accountNumber)
{
var matches = Regex.Matches(accountNumber, @"\d+");
var lastMatch = matches[matches.Count - 1];
var number = Int32.Parse(lastMatch.Value) + 1;
return accountNumber.Remove(lastMatch.Index, lastMatch.Length).Insert(lastMatch.Index, number.ToString());
}
Upvotes: 2
Reputation: 67065
You could use a regex like this:
(\d*)
This will group all of the numbers using the Matches method. You can then get the last group and do your modification from that group.
Then you can use the match Index and Length to rebuild your string.
string input = "GS3R2C1234g";
string pattern = @"(\d*)";
var matches = Regex.Matches(input, pattern);
var lastMatch = matches[matches.Length - 1];
var value = int.Parse(lastMatch.Value);
value++;
var newValue = String.Format("{0}{1}{2}"input.Substring(0,lastMatch.Index),
value, input.Substring(lastMatch.Index+lastMatch.Length));
I have not put error checking in. I will leave that up to you
Upvotes: 1
Reputation: 116108
Assuming you don't want to replace 1 digit numbers.
string input = "GS3R2C1234g";
var output = Regex.Replace(input, @"\d{2,}$*", m => (Convert.ToInt64(m.Value) + 1).ToString());
Upvotes: 2