Regex系列的可选匹配,分隔符取决于状态

ubbxdtey  于 2023-10-22  发布在  其他
关注(0)|答案(6)|浏览(109)

我想接受用户输入的一系列模式的主要增量,以次要增量的流体。
1gal-1qt-2pt-2cup-5oz
我需要强制执行单位后缀的顺序(gal->qt->pt->cup->oz),但是任何特定的增量都是可选的,以允许用户获得最大的表现力,例如

  • 1gal-6oz是有效输入
  • 1gal有效
  • 6oz有效

我打算提取数值作为捕获组。
我问了一个类似的问题here,这很有帮助,但最终我很难把这个问题应用到这个问题上。
我面临的挑战是:
1.分隔符的可见性,它需要根据前面元素的存在而出现或消失。
1.当模式的大部分对于一个有效的匹配不是必需的,而不是必须有至少一个numeral+suffix匹配时的“可选性”。
也许正则表达式并不是最好的表示方法,我应该看看解析尝试,或类似的。
我怎么能做到这一点?
编辑:
我用第四只鸟的回复和LogiKals的回复运行了一些C#基准测试。首先,我运行基准测试6个模式,内存开销是相同的31 kb。Logikal在0.5ns时出现,而T4 B在0.66ns时出现。
然后,我将测试提高到1000次迭代,T4 B在速度上领先,但并没有太多。对于1000次运行,内存开销在mb范围内。
我不知道如何使用正则表达式来选择其中任何一个作为“最佳答案”,所以如果有人能给我建议,我将不胜感激。他们两个都非常有帮助。谢谢.

s2j5cfk0

s2j5cfk01#

这里有一个基于正则表达式的解决方案。
我的正则表达式应该是这样的:

(?=^\d+[a-z]+(?:-\d+[a-z]+)*$)(?:(?<gal>\d+)gal(?=-|$))?-?(?:(?<qt>\d+)qt(?=-|$))?-?(?:(?<pt>\d+)pt(?=-|$))?-?(?:(?<cup>\d+)cup(?=-|$))?-?(?:(?<oz>\d+)oz)?$

将其写入多行以提高可读性:

(?=^\d+[a-z]+(?:-\d+[a-z]+)*$)
(?:(?<gal>\d+)gal(?=-|$))?-?
(?:(?<qt>\d+)qt(?=-|$))?-?
(?:(?<pt>\d+)pt(?=-|$))?-?
(?:(?<cup>\d+)cup(?=-|$))?-?
(?:(?<oz>\d+)oz)?$

正则表达式基于以下模式构建:

  • 积极前瞻:(?=^\d+[a-z]+(?:-\d+[a-z]+)*$)
  • 基本匹配:(?:(?<gal>\d+)gal(?=-|$))?-?

第一种是确保尊重一般模式。
第二个匹配单个值,数字为named capturing group。它确保模式以连字符或字符串结尾结束,并允许在结尾插入可选的连字符。
请参阅here的演示。
如果你愿意,你可以用单词边界(\b)替换(?=-|$)
对于.NET表达式,命名组称为命名匹配子表达式。

zyfwsgd6

zyfwsgd62#

另一个想法是使用word-boundariesoptional分组/连字符:

^\b(?:-?(\d+)gal)?(?:-?\b(\d+)qt)?(?:-?\b(\d+)pt)?(?:-?\b(\d+)cup)?(?:-?\b(\d+)oz)?$

See this demo at regex101

  • word boundaries* 用于验证-的位置或以单词字符开始/结束。也要求至少有一个项目(不允许空字符串)。
ut6juiuv

ut6juiuv3#

另一种选择是首先声明整个字符串的格式,然后使所有部分都是可选的。
在C#中,你可以使用atomic groups(?>来防止一些不必要的回溯。

^(?=\d+[a-z]+(?:-\d+[a-z]+)*$)(?>(?<gal>\d+)gal-?)?(?>(?<qt>\d)+qt-?)?(?>(?<pt>\d+)pt-?)?(?>(?<cup>\d+)cup-?)?(?>(?<oz>\d+)oz-?)?$

模式匹配:

  • ^字符串开头
  • (?=\d+[a-z]+(?:-\d+[a-z]+)*$)正向先行,Assert字符串的格式,因此也至少出现一次
  • (?>(?<gal>\d+)gal-?)?原子组,可选匹配组gal中的1+位,后跟文本“gal”和可选连字符
  • (?>(?<qt>\d)+qt-?)?qt的组
  • (?>(?<pt>\d+)pt-?)?pt的组
  • (?>(?<cup>\d+)cup-?)?cup
  • (?>(?<oz>\d+)oz-?)?oz
  • $字符串结尾

参见Regex demoC# demo

oxosxuxt

oxosxuxt4#

对于任何给定的字符串:

  • “gal”必须在“qt”之前;
  • “qt”必须在“pt”之前;
  • “pt”必须在“cup”之前;
  • “cup”必须在“oz”之前;
  • 这五个字符串中的每一个在字符串中最多出现一次;和
  • 字符串中必须至少出现“gal”、“qt”、“pt”、“cup”和“oz”之一。

问题所举的例子显示有较严格的规定,但我选择从较一般和字面的Angular 来解释问题。例如,下面的字符串满足上述要求。

"Yes, 1qt is equal to 2 pt or 4 cup"

当且仅当字符串与以下正则表达式匹配时,字符串才满足这些要求:

^(?=.*(?:\d|\b)(?:gal|qt|pt|cup|oz)\b)(?:(?!(?:\d|\b)(?:qt|pt|cup|oz)\b).)*(?:(?:\d|\b)gal\b)?(?:(?!(?:\d|\b)(?:gal|pt|cup|oz)\b).)*(?:(?:\d|\b)qt\b)?(?:(?!(?:\d|\b)(?:gal|qt|cup|oz)\b).)*(?:(?:\d|\b)pt\b)?(?:(?!(?:\d|\b)(?:gal|qt|pt|oz)\b).)*(?:(?:\d|\b)cup\b)?(?:(?!(?:\d|\b)(?:gal|qt|pt|cup)\b).)*(?:(?:\d|\b)oz\b)?(?!.*(?:\d|\b)(?:gal|qt|pt|cup|oz)\b)

我在正则表达式中使用了缓和贪婪标记技术。要了解这是如何工作的,请考虑regex

^(?:(?!(?:\d|\b)(?:gal|qt|pt|cup|oz)\b).)*$

它一次匹配一个字符,只有当它不包含任何字符串"gal""qt""pt""cup""oz",前面是一个数字或一个词边界(\b),后面是一个词边界时,它才会匹配字符串。由于单词边界,它将匹配字符串(例如)“3 cupcakes”。
正则表达式具有以下元素。

^                       # match beginning of string
(?=                     # begin a positive lookahead
  .*                    # match zero or more characters other than line
                        # terminators, as many as possible
  (?:                   # begin a non-capture group
    \d                  # match a digit
    |                   # or
    \b                  # match a word boundary
  )                     # end non-capture group
  (?:gal|qt|pt|cup|oz)  # match one of the 5 2-3 character strings
  \b                    # followed by a word boundary
)                       # end positive lookahead
(?:                     # begin a non-capture group
  (?!                   # begin negative lookahead
    (?:\d|\b)           # match a digit or word boundary         
    (?:qt|pt|cup|oz)    # match one of the 2-3 char strings
    \b                  # match a word boundary
  )                     # end negative lookahead
  .                     # match any char except a line terminator
)                       # end non-capture group
*                       # execute non-capture group zero or more times
(?:                     # begin a non-capture group
  (?:\d|\b)             # match a digit or word boundary
  gal                   # match a literal
  \b                    # match a word boundary
)                       # end the non-capture group
?                       # optionally match the above capture group
(?:                     # similar to above
  (?!
    (?:\d|\b)
    (?:gal|pt|cup|oz)
    \b
  )
  .
)*
(?:
  (?:\d|\b)
  qt
  \b
)?
(?:                     # similar to above
  (?!
    (?:\d|\b)
    (?:gal|qt|cup|oz)
    \b
  )
  .
)*
(?:
  (?:\d|\b)
  pt
  \b
)?
(?:                     # similar to above
  (?!
    (?:\d|\b)
    (?:gal|qt|pt|oz)
    \b
  )
  .
)*
(?:
  (?:\d|\b)
  cup\b
(?:                     # similar to above
  (?!
    (?:\d|\b)
    (?:gal|qt|pt|cup)
    \b
  )
  .
)*
(?:
  (?:\d|\b)
  oz
  \b
)?
(?!                     # begin a negative lookahead
  .*                    # match zero or more chars other than line
                        # terminators, as many as possible
  (?:\d|\b)             # match a digit or word boundary
  (?:gal|qt|pt|cup|oz)  # match one of the 5 2-3 character strings
  \b                    # match a word boundary
)                       # end negative lookahead
yfjy0ee7

yfjy0ee75#

您可以使用以下方法:
1.将输入字符串拆分到-上(正如@Barmar在他们对问题帖子的评论中所建议的那样)
1.然后,对于拆分输入字符串的每个部分:
1.使用正则表达式验证部件是否以整数开头
1.验证部分是否以有效单位结束(紧跟在整数之后)
1.验证单位“size”是否小于前一个单位“size”
要检查部分是否以有效单位结尾(步骤2.2),可以使用正则表达式删除初始整数部分,然后尝试在仅包含有效单位的字典中查找单位。(由于步骤2.3,我建议使用字典而不是哈希集。
要检查单位“size”是否小于前面的单位“size”(步骤2.3),包含有效单位的字典可以将“comparison size”作为单位(键)的值。例如,物理单位越大,“比较大小”就越大("gal": 4"qt": 3,等等)。
这一办法的一种实施方式可以是:

private const char Delimiter = '-';

private static readonly Regex StartsWithIntegerRegex = new Regex(@"^(\d+)");

private static readonly Dictionary<string, int> ComparisonSizeByUnit = new()
{
    ["oz"] = 0,
    ["cup"] = 1,
    ["pt"] = 2,
    ["qt"] = 3,
    ["gal"] = 4,
};
private static bool IsValidQuantityInput(string quantityInput)
{
    if (string.IsNullOrWhiteSpace(quantityInput))
    {
        return false;
    }

    var quantities = quantityInput.Split(Delimiter);
    
    if (!TryParseQuantity(quantities[0], out var previousUnitComparisonSize))
    {
        return false;
    }
    
    for (var i = 1; i < quantities.Length; i++)
    {
        if (!TryParseQuantity(quantities[i], out var unitComparisonSize))
        {
            return false;
        }
        
        if (unitComparisonSize >= previousUnitComparisonSize)
        {
            return false;
        }
        
        previousUnitComparisonSize = unitComparisonSize;
    }
    
    return true;
}
private static bool TryParseQuantity(string quantity, out int unitComparisonSize)
{
    var quantityStartsWithInteger = StartsWithIntegerRegex.IsMatch(quantity);
    
    if (!quantityStartsWithInteger)
    {
        unitComparisonSize = 0;
        return false;
    }
    
    var unit = StartsWithIntegerRegex.Replace(quantity, string.Empty);
    
    return ComparisonSizeByUnit.TryGetValue(unit, out unitComparisonSize);
}
wgxvkvu9

wgxvkvu96#

如果你想要“一个非空的字符串^(?!$),有顺序的可选单位,其中单位是一个数字%n,单位名称,如果不在最后则减去%d

%0 = ^(?!$)(%ngal%d)?(%nqt%d)?(%npt%d)?(%ncup%d)?(%noz%d)?$
%d = ($|-(?!$))
%n = \d+

它看起来像https://regex101.com/r/RfCKYO/1

/^(?!$)(\d+gal($|-(?!$)))?(\d+qt($|-(?!$)))?(\d+pt($|-(?!$)))?(\d+cup($|-(?!$)))?(\d+oz($|-(?!$)))?$/gm

(我使用https://tsplay.dev/dimava-regex101-markup-1 js片段在regex101上使用此标记)

相关问题