我正在读取时间跨度数据,对于所有非零时间单位,将显示一个值和单位。例如,如果(正好)过去了 1 天 1 分钟,则字符串将显示为:
1 d, 1 m
如果经过 1 小时 50 毫秒,字符串将显示为:
1 h, 50 ms
如果经过了 1 天、2 小时、3 分钟、20 秒和 479 毫秒,则字符串将显示为:
1 d, 2 h, 3 m, 20 s, 479 ms
如果只过去了 47 毫秒,则字符串将显示为:
47 ms
我在捕获每个单位的数量时想出的匹配该类型模式的正则表达式是:
(?:([\d]+) d, )?(?:([\d]+) h, )?(?:([\d]+) m, )?(?:([\d]+) s, )?(?:([\d]+) ms)?
它有效,只要我有一个标记为成功匹配的捕获组,我就可以轻松获取特定单元的值,以便构造一个
TimeSpan
结构。
我已经在 RegexBuddy 中运行了该正则表达式(使用“C# (.NET 2.0-7.0)”引擎)并且它似乎有效(切换到 RegexBuddy 中的 Python 引擎给出了相同的结果),但在我的 .NET 7.0 程序中,它匹配了不应该匹配的数据,而且它在返回的
MatchCollection
中提供了比我预期更多的元素。
如果我给它一个字符串,例如
"1 d, 12 m, 25 s, 751 ms"
,那么 Regex.IsMatch
返回 true
(如预期)并且 Regex.Matches
有两个元素:索引 0 处的元素有六个 Groups
,其中填充了我所期望的内容,索引为 1 的那个也有 6 个 Groups
,但基本上什么也没有填充。我只期望 MatchCollection
中的一个元素。
一个更大的问题是,到目前为止,正则表达式也匹配我向它抛出的任何内容。它匹配:
hello
22ms
等等
当我说“匹配”时,我的意思是
Regex.IsMatch
返回 true
。对于 hello
,Regex.Matches
返回具有 7 个元素的 MatchCollection
,全部为空,全部显示 Success
的 true
属性。
起初我想:嗯,除了可选标记之外什么都没有,所以,它有点像一个空的正则表达式,但在 RegexBuddy 中,相同的正则表达式并不匹配所有内容:它只匹配(突出显示)我所期望的内容。
下面的代码可以轻松地重复该问题。到目前为止,无论我给
str
赋予什么值,它都会“匹配”(regex.IsMatch(str)
总是返回 true
),这似乎是不正确的,特别是考虑到 RegexBuddy 没有表现出这种行为。
public static TimeSpan StackOverflow(string str)
{
var regex = new Regex("(?:([\\d]+) d, )?(?:([\\d]+) h, )?(?:([\\d]+) m, )?(?:([\\d]+) s, )?(?:([\\d]+) ms)?",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (false == regex.IsMatch(str))
{
throw new ArgumentException($"'{str}' is not a recognized pattern");
}
var matchCollection = regex.Matches(str);
Match match = matchCollection[0];
return new TimeSpan(match.Groups[1].Success ? int.Parse(match.Groups[1].Value) : 0,
match.Groups[2].Success ? int.Parse(match.Groups[2].Value) : 0,
match.Groups[3].Success ? int.Parse(match.Groups[3].Value) : 0,
match.Groups[4].Success ? int.Parse(match.Groups[4].Value) : 0,
match.Groups[5].Success ? int.Parse(match.Groups[5].Value) : 0);
}
我刚刚使用相同的正则表达式在 regex101.com 上测试了
hello
,它似乎反映了我的 .NET 程序显示的内容 hello
:它显示了 6 个匹配项,全部在“空字符串”上。
我的猜测是问题出在正则表达式上,我的直觉与上面已经相关,问题在于有一个由所有可选标记组成的正则表达式。我最初想要完成(但未能完成)的是一个正则表达式,该表达式将与所描述的模式匹配,但至少要匹配其中一个数量/单位。如有任何帮助,我们将不胜感激。
谢谢。
将问题分为两个:匹配和验证。
我相信 RegexBuddy 有一些自己的规则需要应用,这导致结果与 C# 代码(和 regex101)的结果不同。不过,我不会在这个答案中对其进行逆向工程。
让我们从两个问题开始:
答案:正则表达式引擎从字符串的第一个位置/字符启动;它会尝试匹配您的正则表达式,如果不顺利,它将在下一个位置重试。因此,它在位置 1 处获得了一场比赛,该位置是空的,然后在位置 2 处获得了另一场比赛,依此类推。
为了确保正则表达式匹配整个字符串而不留下任何前导或尾随字符(通常也称为完全匹配),您需要anchors:
^
(表示开始)和$
,这意味着结束。请注意,在多行模式下 (RegexOptions.Multiline
),它们具有不同的含义。
因此,更新后的正则表达式应如下所示:
^
<original>
$
。这不再匹配hello
。这是第一步。
如果您曾经尝试过使用正则表达式验证密码,您至少会遇到这种先行模式:
^ # start-of-string
(?=.*[0-9]) # a digit must occur at least once
(?=.*[a-z]) # a lower case letter must occur at least once
(?=.*[A-Z]) # an upper case letter must occur at least once
(?=.*[@#$%^&+=]) # a special character must occur at least once
(?=\S+$) # no whitespace allowed in the entire string
.{8,} # anything, at least eight places though
$ # end-of-string
同样适用于您的:
(?=.)
,表示“至少一个字符”。
^(?=.)(?:([\d]+) d, )?(?:([\d]+) h, )?(?:([\d]+) m, )?(?:([\d]+) s, )?(?:([\d]+) ms)?$
在 regex101.com 上尝试一下。
附录1 d, 1 m
不匹配。这是因为
,
部分不是可选的。将它们替换为
(?:$|, )
(字符串结尾,或逗号和空格)。弄清楚这个小修改是如何工作的,留给读者作为练习。另请注意,
[\d]
与
\d
完全相同,并且
(?!$)
可以用来代替
(?=.)
。