【问题标题】:Match groups in set at most once最多匹配集合中的组一次
【发布时间】:2014-05-17 08:29:18
【问题描述】:

我想使用正则表达式来验证这样的字符串:

foo.bar=123.baz=456.qux=789.urr

(其中123、456 和789 是占位符任意数字,但foo、bar、baz、qux 和urr 是文字文本而不是占位符。我需要单独捕获bar/baz/qux 的值,所以我使用命名捕获组。

问题在于bar=a、baz=b 和qux=c 完全是可选的,可以按任意顺序出现。所以我的正则表达式需要接受这些为有效:

foo.urr
foo.bar=123.urr
foo.qux=123.bar=456.urr
foo.baz=123.bar=456.qux=789.urr

...但将这些视为无效而拒绝:

foo.bar=123.bar=456.urr
foo.qux=123.baz=456.qux=789.urr

我的正则表达式代码现在是这样的:

const String Bar = @"(?<bar>bar=(\d)+)";
const String Baz = @"(?<baz>baz=(\d)+)";
const String Qux = @"(?<qux>qux=(\d)+)";

const String Regex = @"^foo\.((" + Bar + "|" + Baz + "|" + Qux + @")\.)*urr$";

我的表达式使用* 来允许可选的Bar、Baz 和Qux 命名组以任意顺序出现,但它也允许它们出现多次,这是不允许的。

我知道我可以使用 n! 表达式对其进行暴力破解,但将来这些元素的数量可能会随着时间的推移而增加,并且无法进行硬编码。

更新:

我本来可以在我原来的问题中使用一个更好的例子,但是 bar、baz 和 qux 的值都有自己的子表达式,例如bar 接受十六进制数字,baz 接受十进制数字,qux 只接受字母字符,所以这是无效的:

foo.bar=zzz.baz=aaa.qux=123.urr

【问题讨论】:

  • 您是否有理由不能使用多个正则表达式模式和一些简单的逻辑?或者实际上是一个以xxx=000 形式捕获所有内容的正则表达式,然后检查是否在代码中不重复字符串?
  • @BenAaronson 我可以,但这会大大增加复杂性。您能否举例说明我将如何以可管理的方式使用该方法?
  • 它可能会使代码(在正则表达式之外)更复杂,但同时它会使正则表达式更容易。不要忘记正则表达式也必须被解析/应用。它不像是一条指令,只是因为代码中只有一行。
  • foo.bar=123.xxx=456.urr 是否无效(xxx 变量不在列表中)?
  • @UlugbekUmirov 是的,这是无效的,因为名称只能是bar、baz 或qux。

标签: .net regex


【解决方案1】:

您可以使用以下正则表达式:

^foo(?:\.(?<name>bar|baz|qux)=(?<value>\d+)(?!.*\k<name>))*.urr$

示例代码:

string[] lines = {
    "foo.urr",
    "foo.bar=123.urr",
    "foo.qux=123.bar=456.urr",
    "foo.baz=123.bar=456.qux=789.urr",
    "foo.bar=123.bar=456.urr",
    "foo.qux=123.baz=456.qux=789.urr"
};
foreach (string line in lines)
{
    Match m = Regex.Match(line, @"^foo(?:\.(?<name>bar|baz|qux)=(?<value>\d+)(?!.*\k<name>))*.urr$");
    Console.WriteLine("{0} : {1}", line, m.Success);
    if (m.Success)
    {
        for (int i = 0; i < m.Groups["name"].Captures.Count; i++)
            Console.WriteLine("{0} = {1}",
                              m.Groups["name"].Captures[i].Value,
                              m.Groups["value"].Captures[i].Value);
    }
}

样本输出:

foo.urr : True
foo.bar=123.urr : True
bar = 123
foo.qux=123.bar=456.urr : True
qux = 123
bar = 456
foo.baz=123.bar=456.qux=789.urr : True
baz = 123
bar = 456
qux = 789
foo.bar=123.bar=456.urr : False
foo.qux=123.baz=456.qux=789.urr : False

更新

每个组都必须有一个名称。那么我们可以做如下原则:如果给定A、B、C,那么ABC重复3次就会给出所有可能的组合,即(A?B?C?){3}。然后我们只需要负前瞻,这样如果 A 已经匹配就没有其他匹配了。

^foo(?:(?:.bar=(?<bar>\d+)(?!.*\.bar))?(?:.baz=(?<baz>\d+)(?!.*\.baz))?(?:.qux=(?<qux>\d+)(?!.*\.qux))?){0,3}.urr$

示例代码:

string[] lines = {
    "foo.urr",
    "foo.bar=123.urr",
    "foo.qux=123.bar=456.urr",
    "foo.baz=123.bar=456.qux=789.urr",
    "foo.bar=123.bar=456.urr",
    "foo.qux=123.baz=456.qux=789.urr"
};
foreach (string line in lines)
{
    Match m = Regex.Match(line, @"^foo((?:.bar=(?<bar>\d+)(?!.*\.bar))?(?:.baz=(?<baz>\d+)(?!.*\.baz))?(?:.qux=(?<qux>\d+)(?!.*\.qux))?){0,3}.urr$");
    Console.WriteLine("{0} : {1}", line, m.Success);
    if (m.Success)
    {
        Group bar = m.Groups["bar"];
        if (bar.Success)
            Console.WriteLine("bar = {0}", bar.Value);
        Group baz = m.Groups["baz"];
        if (baz.Success)
            Console.WriteLine("baz = {0}", baz.Value);
        Group qux = m.Groups["qux"];
        if (qux.Success)
            Console.WriteLine("qux = {0}", qux.Value);
    }
}

输出:

foo.urr : True
foo.bar=123.urr : True
bar = 123
foo.qux=123.bar=456.urr : True
bar = 456
qux = 123
foo.baz=123.bar=456.qux=789.urr : True
bar = 456
baz = 123
qux = 789
foo.bar=123.bar=456.urr : False
foo.qux=123.baz=456.qux=789.urr : False

【讨论】:

  • 感谢您的贡献,但我应该在我最初的问题中明确指出,在我的具体情况下,相同的正则表达式子表达式不能用于 bar、baz 和 qux' s 值同时,对不起。
  • 我认为你正在做某事! :) 现在快凌晨 4 点了,所以我要去睡觉了,但我会在早上试试这个,到目前为止谢谢! :D
【解决方案2】:

正如您所要求的示例,这是一种以正确格式捕获所有字符串的方法:

^foo\.(?:([a-zA-Z]+)\=(\d+)\.)*urr$

有两个编号的捕获组,因此在您的示例 foo.baz=123.bar=456.qux=789.urr 中,匹配项如下:

0: baz, bar, qux
1: 123, 456, 789

在代码中处理这个相对简单。比如:

var reg = new Regex(@"^foo\.(?:([a-zA-Z]+)\=(\d+)\.)*urr$");
var test = "foo.baz=123.bar=456.qux=789.urr";
var match = reg.Match(test);
return match.Groups[0].Captures.Count == match.Groups[0].Captures.Cast<Capture>().Distinct();

如果没有您描述的重复字符串,这将返回 true,否则返回 false。

作为代码处理的另一个示例,您还可以使用所描述的技术非常轻松地将两个捕获集合转换为字典,例如:create a dictionary using 2 lists using LINQ

另外请注意,如果您想在正则表达式中指定键,您可以将[a-zA-Z]+ 替换为您想匹配的单个字符串,例如baz|bar|qux。或者,您也可以在代码中进行检查。

【讨论】:

  • 感谢您的贡献,但我应该在最初的问题中明确指出,在我的具体情况下,相同的正则表达式子表达式不能用于 bar、baz 和 qux' s 值同时,对不起。
  • @Dai 好的,在这种情况下,我的建议是为该部分提供更通用的表达式(例如.*),然后在代码中进行验证。一般来说,将这类东西推送到代码中会使您的解决方案更具可读性和可维护性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-17
  • 1970-01-01
  • 2012-11-24
  • 1970-01-01
  • 2022-01-18
  • 2021-08-16
  • 2015-01-23
相关资源
最近更新 更多