You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中Regex启用Multiline仍无法识别多行及重复匹配异常

C#正则表达式捕获扑克手牌文本的问题解决

问题场景

在C#中使用Regex解析扑克手牌文本时遇到以下问题,相关代码与目标文本如下:

正则表达式代码

string pattern = @"([\w\d]+) Hand #([\d]+): Tournament #([\d]+), ([$€])([.\d]+)\+[$€]([.\d]+).+\s\(([\d]+\/[\d]+)\).+([\d]+\/[\d]+\/[\d]+ )([\d:]+) ET" +
                                @"(\n|\r|\r\n)Table '([\s\d]+)' (.+) Seat #(\d) is the button" +
                                @"((?:(?:\n|\r|\r\n)^Seat ([\d]+): (.+) \(([\d]+) in chips\))*)";

MatchCollection matches = Regex.Matches(_hh, pattern, RegexOptions.Multiline);

目标文本示例

PokerStars Hand #232702710836: Tournament #3332581238, $9.22+$0.78 USD Hold'em No Limit - Level IV (40/80) - 2021/12/31 22:34:19 ET
Table '3332581238 1' 9-max Seat #3 is the button
Seat 1: mpolishuk (2018 in chips)
Seat 3: Kevin2049 (1154 in chips)
Seat 4: IPray2Buddha (1030 in chips)
Seat 5: Sakura2892 (1499 in chips)
Seat 7: Lillien66 (2141 in chips)
Seat 9: owlie45 (5658 in chips)
mpolishuk: posts the ante 10
Kevin2049: posts the ante 10
IPray2Buddha: posts the ante 10
Sakura2892: posts the ante 10
Lillien66: posts the ante 10
owlie45: posts the ante 10
IPray2Buddha: posts small blind 40
Sakura2892: posts big blind 80
*** HOLE CARDS ***
Dealt to IPray2Buddha [6c 6h]
Lillien66: folds
owlie45: folds
mpolishuk: folds
Kevin2049: folds
IPray2Buddha: calls 40
Sakura2892: raises 1409 to 1489 and is all-in
IPray2Buddha: calls 940 and is all-in
Uncalled bet (469) returned to Sakura2892
*** FLOP *** [8d 7c Ts]
*** TURN *** [8d 7c Ts] [6s]
*** RIVER *** [8d 7c Ts 6s] [Ah]
*** SHOW DOWN ***
IPray2Buddha: shows [6c 6h] (three of a kind, Sixes)
Sakura2892: shows [Kd Qh] (high card Ace)
IPray2Buddha collected 2100 from pot
*** SUMMARY ***
Total pot 2100 | Rake 0
Board [8d 7c Ts 6s Ah]
Seat 1: mpolishuk folded before Flop (didn't bet)
Seat 3: Kevin2049 (button) folded before Flop (didn't bet)
Seat 4: IPray2Buddha (small blind) showed [6c 6h] and won (2100) with three of a kind, Sixes
Seat 5: Sakura2892 (big blind) showed [Kd Qh] and lost with high card Ace
Seat 7: Lillien66 folded before Flop (didn't bet)
Seat 9: owlie45 folded before Flop (didn't bet)

遇到的核心问题

  • 已启用RegexOptions.Multiline,但正则无法识别\n换行符与^行首锚点
  • 重复匹配Seat行的表达式仅能捕获第一个条目,使用*或重复书写表达式均无效

问题分析与解决办法

1. 换行符与锚点失效问题

原因

  • C#逐字字符串(@"")中,\n会被解析为字面量字符,而非转义换行符;原表达式中(\n|\r|\r\n)写法冗余且未正确适配逐字字符串的转义规则
  • RegexOptions.Multiline下^匹配行首,但如果换行符匹配错误,^无法定位到Seat行的开头位置

解决

  • 用\r?\n代替(\n|\r|\r\n),简洁兼容Windows(\r\n)与Unix(\n)换行格式
  • 确保^锚点前的换行匹配正确,逐字字符串中\r?\n无需额外转义,可直接使用

2. 重复匹配仅捕获第一个Seat条目问题

原因

正则表达式中,重复的捕获组(如(...)*)只会保留最后一次匹配的结果,无法存储所有重复条目,这是正则的原生限制

解决(推荐拆分匹配,更易维护)

将匹配逻辑拆分为两部分:先捕获手牌头部信息,再单独匹配所有Seat行

// 匹配手牌头部信息(Hand、Tournament、Table、Button)
string headerPattern = @"^([\w\d]+) Hand #(\d+): Tournament #(\d+), ([€$])([\d.]+)\+[€$]([\d.]+).+\s\((\d+\/\d+)\).+(\d+\/\d+\/\d+ )(\d+:+) ET\r?\nTable '([\s\d]+)' (.+) Seat #(\d) is the button";
Match headerMatch = Regex.Match(_hh, headerPattern, RegexOptions.Multiline);

// 匹配所有Seat行
string seatPattern = @"^Seat (\d+): (.+) \((\d+) in chips\)$";
MatchCollection seatMatches = Regex.Matches(_hh, seatPattern, RegexOptions.Multiline);

// 处理头部信息
if (headerMatch.Success)
{
    string site = headerMatch.Groups[1].Value;
    long handId = long.Parse(headerMatch.Groups[2].Value);
    long tournamentId = long.Parse(headerMatch.Groups[3].Value);
    string currencySymbol = headerMatch.Groups[4].Value;
    decimal buyin = decimal.Parse(headerMatch.Groups[5].Value);
    decimal rake = decimal.Parse(headerMatch.Groups[6].Value);
    string levelBlinds = headerMatch.Groups[7].Value;
    string date = headerMatch.Groups[8].Value.Trim();
    string time = headerMatch.Groups[9].Value;
    string tableName = headerMatch.Groups[10].Value;
    string tableType = headerMatch.Groups[11].Value;
    int buttonSeat = int.Parse(headerMatch.Groups[12].Value);
    // 按需存储头部数据
}

// 处理每个Seat的玩家信息
foreach (Match seatMatch in seatMatches)
{
    int seatNum = int.Parse(seatMatch.Groups[1].Value);
    string playerName = seatMatch.Groups[2].Value;
    int chipCount = int.Parse(seatMatch.Groups[3].Value);
    // 按需存储玩家数据
}

备选方案(单个正则捕获后拆分)

若需用单个正则,可先捕获所有Seat行的文本块,再拆分解析:

string fullPattern = @"^([\w\d]+) Hand #(\d+): Tournament #(\d+), ([€$])([\d.]+)\+[€$]([\d.]+).+\s\((\d+\/\d+)\).+(\d+\/\d+\/\d+ )(\d+:+) ET\r?\nTable '([\s\d]+)' (.+) Seat #(\d) is the button\r?\n((?:Seat \d+: .+ \(\d+ in chips\)\r?\n)*)";
Match fullMatch = Regex.Match(_hh, fullPattern, RegexOptions.Multiline);

if (fullMatch.Success)
{
    // 处理头部信息...
    string seatsTextBlock = fullMatch.Groups[12].Value;
    // 拆分Seat行并逐个解析
    string[] seatLines = seatsTextBlock.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
    foreach (string line in seatLines)
    {
        Match seatMatch = Regex.Match(line, @"Seat (\d+): (.+) \((\d+) in chips\)");
        if (seatMatch.Success)
        {
            // 处理单个Seat数据
        }
    }
}

内容的提问来源于stack exchange,提问作者HungryCroco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:13:37