为什么这个正则表达式贪婪,当我告诉它不是?

时间:2016-03-08 11:30:50

标签: python regex

我试图将我系统宏的以下EBNF grammar编码为正则表达式(如下),但尽管我付出了最大的努力,但似乎贪婪地匹配多个宏:不会在收盘时}}@停止。

    Expand ``@{{...}}@`` references which may appear in Step parameters.
    The syntax is described by the following EBNF grammar::

        depdata = "@{{", source identifier, ":", attribute, "}}@"
                | "@{{TAGS:", expression, "}}@" ;
        source identifier = ? printable 7-bit ASCII ? ;
        attribute = "DATADIR" | "TAGSFILE" | "RESULT_INT" ;
        expression = ? printable 7-bit ASCII ? ;

和我提出的Python正则表达式

@{{(?:(?:(?P<id>.*?):(?P<attr>DATADIR|TAGSFILE|RESULT_INT))|TAGS:(?P<expr>.+?))}}@

修改:在+群组中添加了遗失expr

Regular expression visualization

Debuggex Demo

在以下测试用例中找到所有匹配项时,我希望结果是三个匹配,但我只得到两个:

@{{TAGS:sTagsJob << "job||ID||source"}}@ test  @{{job:DATADIR}}@ email body @{{job:DATADIR}}@ blah

我期待的比赛是:

    {li> @{{TAGS:sTagsJob << "job||ID||source"}}@设置了expr组 {li> @{{job:DATADIR}}@设置了idattr
  1. @{{job:DATADIR}}@(再次)设置了idattr
  2. 但相反,匹配是:

    1. @{{TAGS:sTagsJob << "job||ID||source"}}@ test @{{job:DATADIR}}@
    2. @{{job:DATADIR}}@
    3. 为什么非贪婪的比赛(.+?)似乎贪得无厌?我错过了什么?

      (是的,我知道EBNF语法是愚蠢的,并且可以通过始终将固定字符串显示在右侧来改进。但这不是我的问题:我想知道为什么我的正则表达式失败了我)

2 个答案:

答案 0 :(得分:3)

点匹配任何字符(如果 DOTALL 模式关闭,则为换行符)。 **?匹配0+字符,只有.*一次抓取所有内容,*?一步一步地执行,在重试之前检查后续子模式是否匹配*? subppatern。?。你在这里混淆了“贪婪”的含义:请注意,正则表达式试图“通过各种方式”找到匹配,当字符串中的某个位置发生不匹配时,它重试量化的子模式,引擎使用贪婪的量词回溯,它确实一切来获取匹配。延迟量词并不能保证您的模式不会因为量词定义中的额外@{{(?P<id>TAGS|[ -~]+?):(?:(?P<attr>DATADIR|TAGSFILE|RESULT_INT)|(?P<expr>[ -~]+?))}}@ 而过度激活。

因此,如果您不是真的意味着,如果模式已知,则避免使用点匹配模式。在这里,需要可打印的ASCII模式 - 然后使用它,不依赖于点匹配。

[ -~]

请参阅regex demo

请注意@{{匹配任何可打印的ASCII字符。请参阅My favorite regex of all time

模式匹配:

  • (?P<id>TAGS|[ -~]+?) - 前导分隔符
  • id - TAGS组匹配:或1个可打印的ASCII字符,但尽可能少,因为它也匹配:(您可以限制字符类预先排除[ -9;-~]或替换?以使模式更加优化并使用此量词摆脱:
  • : - 文字(?:(?P<attr>DATADIR|TAGSFILE|RESULT_INT)|(?P<expr>[ -~]+?))
  • DATADIR - 匹配TAGSFILERESULT_INTattr并放入[ -~]组,或匹配一个或多个可打印的ASCII(尽可能少) )并放入组“expr”。同样,这里很懒,因为}(?:(?!}}@)[ -~])+匹配。否则,您可以在}}@使用tempered greedy token--- grails: profile: web codegen: defaultPackage: grailsapp info: app: name: '@info.app.name@' version: '@info.app.version@' grailsVersion: '@info.app.grailsVersion@' spring: groovy: template: check-template-location: false --- grails: mime: disable: accept: header: userAgents: - Gecko - WebKit - Presto - Trident types: all: '*/*' atom: application/atom+xml css: text/css csv: text/csv form: application/x-www-form-urlencoded html: - text/html - application/xhtml+xml js: text/javascript json: - application/json - text/json multipartForm: multipart/form-data pdf: application/pdf rss: application/rss+xml text: text/plain hal: - application/hal+json - application/hal+xml xml: - text/xml - application/xml urlmapping: cache: maxsize: 1000 controllers: defaultScope: singleton converters: encoding: UTF-8 views: default: codec: html gsp: encoding: UTF-8 htmlcodec: xml codecs: expression: html scriptlets: html taglib: none staticparts: none --- hibernate: cache: queries: false use_second_level_cache: true use_query_cache: false region.factory_class: 'org.hibernate.cache.ehcache.EhCacheRegionFactory' endpoints: jmx: unique-names: true dataSource: pooled: true jmxExport: true driverClassName: com.mysql.jdbc.Driver dialect: org.hibernate.dialect.MySQL5InnoDBDialect username: root password: root environments: development: dataSource: dbCreate: update url: jdbc:mysql://localhost/inventory test: dataSource: driverClassName: org.h2.Driver username: sa password: dbCreate: update url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE production: dataSource: dbCreate: update url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE properties: jmxEnabled: true initialSize: 5 maxActive: 50 minIdle: 5 maxIdle: 25 maxWait: 10000 maxAge: 600000 timeBetweenEvictionRunsMillis: 5000 minEvictableIdleTimeMillis: 60000 validationQuery: SELECT 1 validationQueryTimeout: 3 validationInterval: 15000 testOnBorrow: true testWhileIdle: true testOnReturn: false jdbcInterceptors: ConnectionState defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED 。请参阅demo
  • public class StudentsGroup implements IFile, Comparable<StudentsGroup> { private List<Student> studentsList = new ArrayList<Student>(); public int compareTo(StudentsGroup sg) { return Integer.compare(getTotalPoints(), sg.getTotalPoints()); } public int getTotalPoints() { return Math.toIntExact(studentsList.stream() .mapToInt(Student::getStudentPoints).sum()); } } - 尾随分隔符

答案 1 :(得分:0)

这是一个似乎做你想做的正则表达式:

@{{(?:(?:(?P<id>[^:]*?):(?P<attr>DATADIR|TAGSFILE|RESULT_INT))|TAGS:(?P<expr>.*?))}}@

This correctly matches:

MATCH 1
expr    [8-37]  `sTagsJob << "job||ID||source"`

MATCH 2
id  [50-53] `job`
attr    [54-61] `DATADIR`

MATCH 3
id  [79-82] `job`
attr    [83-90] `DATADIR`

你的正则表达式中有一个错误阻止它匹配TAG部分(忘记P<expr>.?))}}@中的kleene星?),但仅凭这一点并不能解决匹配过多的问题,所以我在<id>之后将点更改为“非冒号”。