8

ほとんどの場合、数字以外の「アカウント番号」を表すフィールドがあります。これらの「数値」の自動インクリメントを行う必要があります。明らかに数学を行うには理想的ではありません。私たちがうまくいくと判断したルールは、一番右の数字のグループを見つけて、それらを 1 ずつ自動インクリメントし、再構築された文字列を返すことです (これにより 1 文字長くなる場合でも)。

数値の例は次のとおりです。

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

私は C#/.NET 4.0 を使用しています。タグとして Regex をリストしましたが、これは必須ではありません。このソリューションは、正規表現である必要はありません。

これを行う良い方法について何か考えはありますか?理想的なパフォーマンスは大きな問題ではありません。すべてが正規表現にまとめられていない限り、このための明確で理解しやすい/維持するコードが必要です。

ありがとう!

4

8 に答える 8

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

正規表現の説明: (文字列の先頭) または (非数字) のいずれかから開始して、0 個以上の非数字が後に続く 1 つ以上の数字に一致し、次に文字列の末尾に一致します。

もちろん、抽出された数値の先頭に 0 があると、問題が発生します。これは読者への演習として残しておきます。

MatchEvaluator を使用すると (@LB の回答で示唆されているように)、これはやや軽くなります。

Regex.Replace(
    src,
    @"(?<=(\D|^))\d+(?=\D*$)",
    m => (int.Parse(m.Value)+1).ToString())
于 2012-05-04T16:45:28.757 に答える
3

私の理解が正しければ、特定の文字列内で最も右にある数字に 1 を追加したいと考えています。

他の人が示唆したように 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;
    }
于 2012-05-04T16:49:37.933 に答える
2

次のことをお勧めします。

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());
}
于 2012-05-04T16:53:20.903 に答える
2

1桁の数字を置き換えたくない場合。

string input = "GS3R2C1234g";
var output = Regex.Replace(input, @"\d{2,}$*", m => (Convert.ToInt64(m.Value) + 1).ToString());
于 2012-05-04T16:50:11.203 に答える
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 を使用して最後に検索を開始し、すべての一致を見つけて最後の一致を取得するよりも効率的です。
  • "\d" の代わりに "[0-9]" を使用して、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");
于 2012-05-04T17:01:57.720 に答える
1

次のような正規表現を使用できます。

(\d*)

これにより、 Matches メソッドを使用してすべての数値がグループ化されます。その後、最後のグループを取得し、そのグループから変更を行うことができます。

次に、一致するインデックスと長さを使用して文字列を再構築できます。

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

エラーチェックは入れていません。お任せします。

于 2012-05-04T16:40:50.787 に答える
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 の MatchEvaluator FTW としてのラムダ式の使用!

  • @spenderの回答から、先読み - (?=\D*$)- は、最後の数字グループのみが一致することを保証します(ただし、後読み - (?<=(\D|^))- は必要ありません)。

  • @JeffMoser で使用される RightToLeft オプションを使用すると、最初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
于 2012-05-04T20:34:37.547 に答える
0

を使ってみることができますString.Split。次のようなものを使用できます。

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

次に、配列をループして最後の数値を検索し ( から までループしNameSplit.length1で見つかった最初の数値Int32.TryParse)、その数値をインクリメントしてから、配列を で再び連結することができString.Concatます。

RegExより効率は悪いかもしれませんが、RegExが分からない人には分かりやすいと思います..

于 2012-05-04T16:57:49.217 に答える