PAYATHON 2026

仅替换每个新索引前两行中的 string

支付老李

结论

可以用计数器处理。遍历 List<string> 时,同时记录当前数字索引和组内行号。索引变化后,将组内行号重置为 0。随后,在每组第 1、2 行中,把字段位置上的 description 分别替换为 Type 和 Process。

替换时应限定字段位置,并匹配完整单词 description,不要直接对整行做无条件字符串替换。

C# 实现

下面的代码假设相同数字索引的记录连续排列:

using System.Collections.Generic;
using System.Text.RegularExpressions;

var lines = new List<string>
{
    "1  description   Payment",
    "1  description   Order",
    "1  ID   0222ed48-ddc1-3e19-4770-616c14935ca1",
    "",
    "2  description   Refund",
    "2  description   Partial",
    "2  description   Contacted?",
    "2  ID    87f11531-0493-4f5a-86d2-c8be0c23678f",
    "",
    "3  description   Refund",
    "3  description   Partial",
    "3  ID    87f11531-0493-4f5a-86d2-c8be0c23678f"
};

var indexPattern = new Regex(@"^\s*(?<index>\d+)\b");
var descriptionPattern = new Regex(
    @"^(?<prefix>\s*\d+\s+)description\b",
    RegexOptions.IgnoreCase
);

var result = new List<string>(lines.Count);

string? currentIndex = null;
int rowInGroup = 0;

foreach (string line in lines)
{
    Match indexMatch = indexPattern.Match(line);

    // 空行或不以数字索引开头的行原样保留
    if (!indexMatch.Success)
    {
        result.Add(line);
        continue;
    }

    string index = indexMatch.Groups["index"].Value;

    if (index != currentIndex)
    {
        currentIndex = index;
        rowInGroup = 0;
    }

    rowInGroup++;

    string? replacement = rowInGroup switch
    {
        1 => "Type",
        2 => "Process",
        _ => null
    };

    if (replacement != null)
    {
        string updatedLine = descriptionPattern.Replace(
            line,
            match => match.Groups["prefix"].Value + replacement,
            1
        );

        result.Add(updatedLine);
    }
    else
    {
        result.Add(line);
    }
}

foreach (string line in result)
{
    Console.WriteLine(line);
}

输出结果:

1  Type   Payment
1  Process   Order
1  ID   0222ed48-ddc1-3e19-4770-616c14935ca1

2  Type   Refund
2  Process   Partial
2  description   Contacted?
2  ID    87f11531-0493-4f5a-86d2-c8be0c23678f

3  Type   Refund
3  Process   Partial
3  ID    87f11531-0493-4f5a-86d2-c8be0c23678f

为什么要记录索引和组内行号

如果只在计数器超过 2 后重置,一旦各索引下的行数不同,计数就会错位。计数器应该在数字索引变化时重置:

if (index != currentIndex)
{
    currentIndex = index;
    rowInGroup = 0;
}

这样,无论一个索引下有 2 行、3 行还是更多行,遇到新索引后都会重新计数,前两行也能得到正确处理。

如果要处理前两个 description

前面的代码处理的是每个索引下的前两行。如果实际需求是替换每个索引下最先出现的两个 description,即使中间夹着 ID 等其他行也要继续查找,那么计数器应只在匹配到 description 时递增:

string? currentIndex = null;
int descriptionCount = 0;

foreach (string line in lines)
{
    Match indexMatch = indexPattern.Match(line);

    if (!indexMatch.Success)
    {
        result.Add(line);
        continue;
    }

    string index = indexMatch.Groups["index"].Value;

    if (index != currentIndex)
    {
        currentIndex = index;
        descriptionCount = 0;
    }

    Match descriptionMatch = descriptionPattern.Match(line);

    if (!descriptionMatch.Success)
    {
        result.Add(line);
        continue;
    }

    descriptionCount++;

    string replacement = descriptionCount switch
    {
        1 => "Type",
        2 => "Process",
        _ => "description"
    };

    string updatedLine = descriptionPattern.Replace(
        line,
        match => match.Groups["prefix"].Value + replacement,
        1
    );

    result.Add(updatedLine);
}

注意事项

  • 按索引变化重置计数器的写法要求相同索引的行连续出现。如果索引 1 的记录可能稍后再次出现,应改用 Dictionary<string, int>,分别保存每个索引的计数。
  • 正则表达式会保留数字索引后的原始空白,不会破坏现有的对齐格式。
  • 如果示例中的 **Type** 和 **Process** 只是 Markdown 加粗标记,实际字符串应写成 Type 和 Process。如果星号本身也是数据的一部分,则把替换值改为 "**Type**" 和 "**Process**"。
  • 如果字段名区分大小写,可以移除 RegexOptions.IgnoreCase。

备注:内容仅供参考。