C# 正则表达式最多按任意顺序解析一次

C# 正则表达式最多按任意顺序解析一次,c#,regex,C#,Regex,我正在开发一个日期解析器,确切地说,在这个解析器中,我必须提取设置为天数、周数、月数和年数之和的日期 这看起来像1d1y或100m2w或1y1d1m1w 我已经构建了一个自定义解析器,但我正在寻找一个使用正则表达式的更干净的解决方案。我必须检查它是否包含最多一个日期字符d、w、m和y,并用整数分隔 ^(?<ValueDay>[0-9]+(d))?(?<ValueWeek>[0-9]+(w))?(?<ValueMonth>[0-9]+(m))?(?<Val

我正在开发一个日期解析器,确切地说,在这个解析器中,我必须提取设置为天数、周数、月数和年数之和的日期

这看起来像1d1y或100m2w或1y1d1m1w

我已经构建了一个自定义解析器,但我正在寻找一个使用正则表达式的更干净的解决方案。我必须检查它是否包含最多一个日期字符d、w、m和y,并用整数分隔

^(?<ValueDay>[0-9]+(d))?(?<ValueWeek>[0-9]+(w))?(?<ValueMonth>[0-9]+(m))?(?<ValueYear>[0-9]+(y))?$
我的问题是,它可能发生在任何顺序1d1w工程以及1w1d。我试着使用如下的积极前瞻,但它不符合所有的标准

^(?=.*(?<ValueDay>[0-9]+(d)))?(?=.*(?<ValueWeek>[0-9]+(w)))?(?=.*(?<ValueMonth>[0-9]+(m)))?(?=.*(?<ValueYear>[0-9]+(y)))?.*$

我如何才能做到这一点?

如果每个组必须出现零次或一次,您可以使用以下方法:

^
 (
  (?(y)(?!)|(?<y>\d+)y)
  |
  (?(m)(?!)|(?<m>\d+)m)
  |
  (?(w)(?!)|(?<w>\d+)w)
  |
  (?(d)(?!)|(?<d>\d+)d)
 )+
$
对于每个字母,它会检查名称为该字母的组是否已经匹配。如果是这样,它将失败并转到下一个字母。如果没有,它将尝试捕获该字母后面的数字,并将其放入以该字母为名称的组中

上一个答案-一切只发生一次版本:

^((?<y>\d+)y|(?<m>\d+)m|(?<w>\d+)w|(?<d>\d+)d){4}$(?<-y>)(?<-m>)(?<-w>)(?<-d>)
这将检查:

输入开始 y、m、w或d中的一个 四次=组数 输入结束 至少有一个y匹配 至少有一个m匹配 至少有一个w匹配 至少有一个d匹配 这应该行得通。它至少匹配\d+[wydm]一次,最多匹配四次。除此之外,当字符[wydm]匹配时,它会向前看,并且同一字符不会在第二次出现在文本中。现在,您可以从组中获取值:

int GetValue(Match m)
{
    int GetGroupValue(Group numberGroup, Group characterGroup)
    {
        if (!numberGroup.Success) { return 0; }
        int number = int.Parse(numberGroup.Value);
        switch (characterGroup.Value)
        {
            case "d": return number;
            case "w": return 7 * number;
            case "m": return 31 * number;
            case "y": return 365 * number;
            default: throw new NotSupportedException(characterGroup.Value + " is not supported");
        }
    }

    return GetGroupValue(m.Groups[1], m.Groups[2])
         + GetGroupValue(m.Groups[3], m.Groups[4])
         + GetGroupValue(m.Groups[5], m.Groups[6])
         + GetGroupValue(m.Groups[7], m.Groups[8]);
}
以下是一些验证核心度的测试:

var tests = new (string s, bool isOk, int desiredValue)[] {
    ("15y", true, 15*365),
    ("1000w", true, 1000*7),
    ("10000d", true, 10000),
    ("100000m", true, 100000*31),
    ("15y8y", false, 0),
    ("", false, 0),
    ("7y9w12m2d", true, 7*365 + 9*7 + 12*31 + 2),
    ("7d9m12w2y", true, 7 + 9*31 + 12*7 + 2*365),
    ("7y9w12m2dd", false, 0),
    ("7y9w12m2y", false, 0),
    ("7y9w12m2x", false, 0),
    ("-5y", false, 0),
    ("1", false, 0),
    ("y2", false, 0),
    ("yd", false, 0),
    ("7y1", false, 0),
    ("m5d", false, 0)
    };

foreach (var test in tests)
{
    Match m = r.Match(test.s);
    if (m.Success != test.isOk)
    {
        throw new Exception("Test failed for " + test.Item1);
    }
    if (GetValue(m) != test.desiredValue)
    {
        throw new Exception("Test failed for " + test.Item1);
    }
}

MessageBox.Show("All " + tests.Count() + " tests passed");

使用三个单独的正则表达式。匹配调用,这是最简单的方法。@WiktorStribiżew有24个排列,你的意思是24个正则表达式调用吗?@AntonínLejsek只有4个值可提取,因此,只有4个值:1天-@\d+d,1周-@\d+w,1个月-@\d+m,1年-\d+y。@WiktorStribiżew但它根本不测试输入的有效性。拒绝解析无意义的东西是非常可取的,即使是OP也需要这样做。你实际上并没有在这里检查任何东西,你只是用?弹出捕获堆栈?。您需要一个条件构造来实际检查捕获堆栈是否为空。无论如何,它不会像这样工作,因为零件的数量是可变的。通过弹出,您正在检查是否存在一个零件。通过计算中的零件数量,您无需检查每个堆栈是否为空。如果你有一个可变数量的部分,你可以用大括号中不同的数字构造一个正则表达式。我需要检查模式a、b、c或d是否发生过零次或一次,所以我需要为我正在解析的每个记录动态更改模式。在应用正则表达式之前,我需要分析和检查数据。@user3627975我已经为每个组添加了一个零或一个匹配的版本。您应该能够在解析/检查时使用正则表达式作为一个整体是否成功。谢谢,它在验证输入时确实非常有效。我可以稍后使用^{d| w | m | y}$分割有效输入,并计算天数。我只是想知道是否有办法解析同一个regex调用,因为我无法使用Match.Groups这个解决方案。我不理解这个问题。正则表达式组是获取正则表达式解析部分的方法。在不使用正则表达式组的情况下,您希望如何读取正则表达式组?我的意思是,使用此解决方案,我们最多可以创建4个组,但我们不知道右侧的内部是什么?就像我不能像上面那样明确地命名它?或者使用指数,例如直接计算天数的总和。我故意避免这样做,因为我不确定你想计算什么。但当你挣扎于此时,我更新了答案,以便更好地向你展示如何应对。这正是我想要的。谢谢你的帮助!
var tests = new (string s, bool isOk, int desiredValue)[] {
    ("15y", true, 15*365),
    ("1000w", true, 1000*7),
    ("10000d", true, 10000),
    ("100000m", true, 100000*31),
    ("15y8y", false, 0),
    ("", false, 0),
    ("7y9w12m2d", true, 7*365 + 9*7 + 12*31 + 2),
    ("7d9m12w2y", true, 7 + 9*31 + 12*7 + 2*365),
    ("7y9w12m2dd", false, 0),
    ("7y9w12m2y", false, 0),
    ("7y9w12m2x", false, 0),
    ("-5y", false, 0),
    ("1", false, 0),
    ("y2", false, 0),
    ("yd", false, 0),
    ("7y1", false, 0),
    ("m5d", false, 0)
    };

foreach (var test in tests)
{
    Match m = r.Match(test.s);
    if (m.Success != test.isOk)
    {
        throw new Exception("Test failed for " + test.Item1);
    }
    if (GetValue(m) != test.desiredValue)
    {
        throw new Exception("Test failed for " + test.Item1);
    }
}

MessageBox.Show("All " + tests.Count() + " tests passed");