从字符串中获取最后一组数字,进行数学运算,重建为字符串?

时间:2012-05-04 16:35:15

标签: c# regex .net-4.0

我有一个代表“帐号”的字段,大部分时间都不是数字。我需要对这些“数字”进行一些自动递增。显然不适合做数学。我们决定适用于我们的规则是,我们希望找到最右边的数字组并自动将它们递增一个并返回重建的字符串(即使这使得它成为一个字符)长)。

这些数字的一些例子是:

  • AC1234 - > AC1235
  • GS3R2C1234 - > GS3R2C1235
  • 1234 - > 1235
  • A-1234 - > A-1235
  • AC1234g - > AC1235g
  • GS3R2C1234g - > GS3R2C1235g
  • 1234g - > 1235克
  • A-1234g - > A-1235克
  • 999 - > 1000
  • GS3R2C9999g - > GS3R2C10000g

我正在使用C#/ .NET 4.0。我将Regex列为标签,但这不是必需的。此解决方案无需使用正则表达式。

有关这种方法的好方法吗?理想的性能不是主要问题。除非它全部包含在正则表达式中,否则我宁愿拥有清晰易懂的代码。

谢谢!

8 个答案:

答案 0 :(得分:5)

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
}

解释正则表达式:从(字符串的开头)或(非数字)开始,匹配一个或多个数字,后跟零个或多个非数字,然后是字符串的结尾。

当然,如果提取的数字有前导零,那么事情就会出错。我会将此作为练习留给读者。

使用MatchEvaluator(正如@LB在他/她的回答中所建议的那样),这会变得更轻一些:

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

答案 1 :(得分:3)

如果我理解正确,你想在一个特定字符串中最右边的数字加一个。

你可以像其他人建议的那样使用正则表达式,但是由于你正在尝试做一些非常具体的事情,所以Regex证明比执行算法更慢。

您可以针对Regex解决方案对此进行测试,并亲眼看看这会更快:

  

我跑了100万次并用秒表计时。

结果:

  

正则表达式 - 10,808,533个刻度

     

我的方式 - 253,355个小时

快了大约40倍!!!

结论: 具体问题的具体解决方案。

  

我的方式很快。

这是代码:

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

答案 2 :(得分:2)

假设您不想替换1位数字。

string input = "GS3R2C1234g";
var output = Regex.Replace(input, @"\d{2,}$*", m => (Convert.ToInt64(m.Value) + 1).ToString());

答案 3 :(得分:1)

您可以使用这样的正则表达式:

(\d*)

这将使用Matches method对所有数字进行分组。然后,您可以获取最后一组并从该组进行修改。

然后您可以使用匹配索引和长度来重建字符串。

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

我没有进行错误检查。我会把它留给你

答案 4 :(得分:1)

我建议如下:

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

答案 5 :(得分:1)

如果你想要一个简单的正则表达式拼接结果:

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

注意:

  • 它使用RegexOptions.RightToLeft在结尾处开始搜索,并且更有效地找到所有匹配并获取最后一个匹配。
  • 使用“[0-9]”代替“\ d”以避免Turkey Test问题。

如果你想使用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());
}

对于它的价值,我最初不小心误读了这个并认为你想要携带位(即“A3G999 - > A4G000”)。这更有趣,需要携带状态:

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

测试用例:

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

答案 6 :(得分:1)

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

输出:

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

注释:

  • @LB使用lambda表达式作为MatchEvaluator FTW!

  • 从@ spender的回答中,前瞻 - (?=\D*$) - 确保只匹配最后一组数字(但不需要后视 - (?<=(\D|^)))。

  • @JeffMoser使用的RightToLeft选项允许它匹配最后一组数字 first ,但是没有静态Replace方法允许您(1)指定RegexOptions,(2)使用MatchEvaluator,(3)限制替换次数。您必须首先实例化一个Regex对象:

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

输出:

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

答案 7 :(得分:0)

您可以尝试使用String.Split。你可以使用类似的东西:

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

然后你可以在数组上循环以找到最后一个数字(从NameSplit.length1的循环,Int32.TryParse找到的第一个数字),递增该数字,然后连接数组再次与String.Concat合作。

它的效率可能不如RegEx,但我认为对那些不了解RegEx的人来说会更容易理解。