Get the last set of numbers from a string, do the math, rebuild back into a string?

I have a field representing the โ€œAccount Numberโ€ that is the most occupied by the number. I need to auto-increment these "numbers". Obviously, this is not ideal for math. The rule that we decided for us is that we want to find the right part of the numbers and automatically increase them by one and return the restored string (even if this makes it one character more).

Some examples of numbers:

  • AC1234 โ†’ AC1235
  • GS3R2C1234 โ†’ GS3R2C1235
  • 1234 โ†’ 1235
  • A-1234 โ†’ A-1235
  • AC1234g โ†’ AC1235g
  • GS3R2C1234g โ†’ GS3R2C1235g
  • 1234g โ†’ 1235g
  • A-1234g โ†’ A-1235g
  • 999 โ†’ 1000
  • GS3R2C9999g โ†’ GS3R2C10000g

I work with C # /. NET 4.0. I have listed Regex as a tag, but this is not a requirement. This solution does not have to be in regular expressions.

Any thoughts on a good way to do this? Perfect performance is not a major issue. I would rather have clear and understandable / supportive code for this if it is not completed in Regex.

Thanks!

+7
source share
8 answers
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 } 

Explanation of a regular expression: starting with (beginning of line) or (without number), corresponds to one or more digits, followed by zero or more digits, and then end of line.

Of course, if the extracted number has leading zeros, everything will go wrong. I will leave this as an exercise for the reader.

Using the MatchEvaluator (as suggested by @LB in his / her answer), this becomes somewhat easier:

 Regex.Replace( src, @"(?<=(\D|^))\d+(?=\D*$)", m => (int.Parse(m.Value)+1).ToString()) 
+5
source

If I understand you correctly, you want to add it to the number that is the most correct within a certain line.

You can use Regex at the suggestion of others, but since you are trying to do something very specific, Regex will be slower than implementing the algorithm just for what you are doing.

You can check this for a Regex solution and make sure it will be much faster:

I ran and 1 million times and timed it to a stopwatch.

Results:

Regex - 10 808 533 ticks

My way - 253 355 ticks

About 40 times faster !!!

Conclusion: Concrete solutions for specific problems.

My way is much faster.

And here is 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; } 
+3
source

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()); 
+2
source

You can use regex as follows:

 (\d*) 

This will group all the numbers using the Matches method. Then you can get the last group and make changes to this group.

Then you can use a match of index and length to restore 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 did not install error checking. I will leave it to you

+1
source

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()); } 
+1
source

If you need a simple Regex that stitches 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:

  • It uses RegexOptions.RightToLeft to start the search at the end and is more efficient to find all matches and take the last one.
  • It uses "[0-9]" instead of "\ d" to avoid the Turkey Test .

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 misunderstood it initially and thought you wanted to carry a bit (ie "A3G999 โ†’ A4G000"). This is more interesting and requires a migration 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"); 
+1
source
 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); } 

exit:

 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 using lambda expressions like MatchEvaluator FTW!

  • From @spender's answer, lookahead - (?=\D*$) - ensures that only the last group of digits matches (but lookbehind - (?<=(\D|^)) is not required).

  • The RightToLeft parameter used by @JeffMoser allows it to first match the last group of numbers, but there is no static Replace method that allows you to (1) specify RegexOptions, (2) use a MatchEvaluator, and (3) limit the number of replacements. You must first create an instance of the Regex object:

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

exit:

 AC1234 -> AC1235 GS3R2C1234 -> GS3R2C1235 1234 -> 1235 A-1234 -> A-1235 AC1234g -> AC1235g GS3R2C1234g -> GS3R2C1235g 1234g -> 1235g A-1234g -> A-1235g 999 -> 1000 GS3R2C9999g -> GS3R2C10000g 
+1
source

You can try using String.Split . You can use something like:

 NameSplit=AccountNumber.split(new Char[] {'a','b','....'z'}); 

Then you can loop through the array to find the last number (a loop from NameSplit.length to 1 , the first number found by Int32.TryParse ), increase that number and then merge the array again with String.Concat .

It will probably be less effective than RegEx, but I think it would be easier to understand people who do not understand RegEx ..

0
source

All Articles