从字符串中获取最后一组数字,进行数学运算,重建回字符串
本文关键字:字符串 运算 重建 数字 一组 最后 获取 | 更新日期: 2023-09-27 18:33:07
我有一个表示"帐号"的字段,大多数时候它都不是数字。我需要对这些"数字"进行一些自动递增。显然不适合做数学。我们决定对我们有用的规则是,我们希望找到最右边的数字组并自动将它们递增 1 并返回重建的字符串(即使这会使其长一个字符(。
这些数字的一些示例是:
- AC1234 -> AC1235
- GS3R2C1234 -> GS3R2C1235
- 1234年->1235 年
- A-1234 -> A-1235
- AC1234g -> AC1235g
- GS3R2C1234g -> GS3R2C1235g
- 1234克->1235克
- A-1234G -> A-1235g
- 999->1000
- GS3R2C9999g -> GS3R2C10000g
我正在使用 C#/.NET 4.0。我将正则表达式列为标签,但这不是必需的。此解决方案不必在正则表达式中。
关于做到这一点的好方法有什么想法吗?理想的性能不是主要问题。我宁愿为此提供清晰易懂/维护的代码,除非它全部包含在正则表达式中。
谢谢!
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())
理解正确,您想在某个字符串中最右边的数字上添加一个。
你可以像其他人建议的那样使用正则表达式,但由于你正在尝试做一些非常具体的事情,正则表达式将证明比仅仅为你做的事情实现算法要慢。
您可以针对正则表达式解决方案对此进行测试,并亲眼看看这会快得多:
我跑了 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;
}
假设您不想替换 1 位数字。
string input = "GS3R2C1234g";
var output = Regex.Replace(input, @"'d{2,}$*", m => (Convert.ToInt64(m.Value) + 1).ToString());
我建议如下:
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());
}
你可以使用这样的正则表达式:
('d*)
这将使用 Match 方法对所有数字进行分组。然后,您可以获取最后一个组并从该组进行修改。
然后,您可以使用匹配索引和长度来重建字符串。
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));
我没有输入错误检查。我会把它留给你
如果你想要一个简单的正则表达式来拼接结果:
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"来避免土耳其测试问题。
如果要使用 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");
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使用的从右到左选项允许它首先匹配最后一组数字,但没有静态
Replace
方法允许您 (1( 指定正则表达式选项,(2( 使用 MatchEvaluator,以及 (3( 限制替换次数。 您必须先实例化一个正则表达式对象:
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
您可以尝试使用 String.Split
.你可以使用这样的东西:
NameSplit=AccountNumber.split(new Char[] {'a','b','....'z'});
然后你可以在数组上循环查找最后一个数字(从NameSplit.length
循环到1
,Int32.TryParse
找到的第一个数字(,递增该数字,然后再次将数组与String.Concat
连接在一起。
效率低,但我认为对于不了解正则表达式的人来说更容易理解。