正则表达式字符计数,但有些计数为三

Gho*_*per 9 javascript regex

我正在尝试构建一个对输入长度设置限制的正则表达式,但并非所有字符在此长度内都相等.我会把理由放在问题的最底层.举个简单的例子,让我们将最大长度限制为12并仅允许ab,但是b计算3个字符.

允许的是:

  • aa (少于12的东西都没问题).
  • aaaaaaaaaaaa (正好是12个很好).
  • aaabaaab (6 + 2*3 = 12,这很好).
  • abaaaaab (仍然是6 + 2*3 = 12).

不允许的是:

  • aaaaaaaaaaaaa(13 a).
  • bbbba (1 + 4*3 = 13,这太多了).
  • baaaaaaab (7 + 2*3 = 13,这太多了).

我做了一个相当接近的尝试:

^(a{0,3}|b){0,4}$
Run Code Online (Sandbox Code Playgroud)

这匹配最多4个簇,可能包含0-3 a或1 b.

但是,它无法与我最后一个正面例子相匹配:abaaaaab因为这会强制第一个集群a在开始时成为单个集群,为此消耗第二个集群b,然后为剩下的集群仅留下2个集群aaaaab,这太长了.

约束

  • 必须在JavaScript中运行.这个正则表达式提供给Qt,它显然使用了JavaScript的语法.
  • 真的不需要快.最后它只适用于最多40个字符的字符串.我希望它能在50ms左右的时间内验证,但稍微慢一些是可以接受的.

合理

为什么我需要使用正则表达式执行此操作?

它是通过PyQt和QML在Qt中的用户界面.用户可以在此处的文本字段中键入配置文件的名称.此配置文件名称是url编码的(特殊字符由%XX替换),然后保存在用户的文件系统中.当用户键入许多特殊字符(例如中文)时会遇到问题,然后编码为非常长的文件名.事实证明,在17个字符的某个地方,这个文件名对于某些文件系统来说太长了.URL编码编码为UTF-8,每个字符最多包含4个字节,文件名中最多可包含12个字符(因为每个字符都经过百分比编码).

对于个人资料名称,16个字符太短.甚至我们的一些默认名称也超过了它.我们需要基于这些特殊字符的变量限制.

Qt通常允许您指定Validator以确定文本框中可接受的值.我们尝试实现这样的验证器,但是由于PyQt中的错误导致了上游的段错误.目前似乎无法处理自定义Validator实现.但是,PyQt还公开了三个内置验证器.两个仅适用于数字.第三个是正则表达式验证器,允许您放置一个匹配所有有效字符串的正则表达式.因此需要这种正则表达式.

小智 6

鉴于regexp的局限性,没有真正直接的方法可以做到这一点.您将不得不测试所有组合,例如十三个b,最多一个a,十二个b,最多四个a,等等.我们将构建一个小程序来为我们生成这些程序.最多四个测试的基本格式a

/^(?=([^a]*a){0,4}[^a]*$)/
Run Code Online (Sandbox Code Playgroud)

我们会写一些例程来为我们创建这些前瞻,给出一些字母以及最小和最大出现次数:

function matchLetter(c, m, n) {
  return `(?=([^${c}]*${c}){${m},${n}}[^${c}]*$)`;
}

> matchLetter('a', 0, 4)
< "(?=([^a]*a){0,4}[^a]*$)"
Run Code Online (Sandbox Code Playgroud)

我们可以将这些结合起来测试三个b,最多三个a:

/^(?=([^b]*b){3}[^b]*$)(?=([^a]*a){0,3}[^a]*$)/
Run Code Online (Sandbox Code Playgroud)

我们将编写一个函数来创建这样的组合向前看符号,其精确匹配m的出现c1和最多n的事件c2:

function matchTwoLetters(c1, m, c2, n) {
  return matchLetter(c1, m, m) + matchLetter(c2, 0, n);
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用它来精确匹配十二个b和最多四个a,总共四十或更少:

> matchTwoLetters('b', 12, 'a', 1, 4)
< "(?=([^b]*b){12,12}[^b]*$)(?=([^a]*a){0,4}[^a]*$)"
Run Code Online (Sandbox Code Playgroud)

它仍然只是为每个计数创建这个版本b,并将它们放在一起(对于最大计数为12的情况):

function makeRegExp() {
  const res = [];
  for (let bs = 0; bs <= 4; bs++)
    res.push(matchTwoLetters('b', bs, 'a', 12 - bs*3));
  return new RegExp(`^(${res.join('|')})`);
}

> makeRegExp()
< "^((?=([^b]*b){0,0}[^b]*$)(?=([^a]*a){0,12}[^a]*$)|(?=([^b]*b){1,1}[^b]*$)(?=([^a]*a){0,9}[^a]*$)|(?=([^b]*b){2,2}[^b]*$)(?=([^a]*a){0,6}[^a]*$)|(?=([^b]*b){3,3}[^b]*$)(?=([^a]*a){0,3}[^a]*$)|(?=([^b]*b){4,4}[^b]*$)(?=([^a]*a){0,0}[^a]*$))"
Run Code Online (Sandbox Code Playgroud)

现在你可以用它做测试了

makeRegExp().test("baabaaa");
Run Code Online (Sandbox Code Playgroud)

对于长度= 40的情况,regxp长度为679个字符.一个非常粗略的基准测试显示它在一微秒内执行.