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

标签 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}$

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

但是,它无法匹配我的最后一个正例:abaaaaab,因为这迫使第一个集群在开始时成为单个 a,消耗了第二个集群对于 b,然后只为其余的 aaaaab 留下 2 个簇,这太长了。

约束

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

理由

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

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

16 个字符对于个人资料名称来说太短了。甚至我们的一些默认名称也超过了这个范围。我们需要基于这些特殊字符的可变限制。

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

最佳答案

考虑到正则表达式的局限性,没有真正直接的方法可以做到这一点。您将必须测试所有组合,例如十三个 b 和最多一个 a,十二个 b 和最多四个 a,依此类推。我们将构建一个小程序来为我们生成这些。测试最多四个 a 的基本格式是

/^(?=([^a]*a){0,4}[^a]*$)/

我们将编写一个小程序来为我们创建这些前瞻,给定一些字母以及出现的最小和最大次数:

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

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

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

/^(?=([^b]*b){3}[^b]*$)(?=([^a]*a){0,3}[^a]*$)/

我们将编写一个函数来创建这样的组合前瞻,它完全匹配 mc1 和最多 n c2:

function matchTwoLetters(c1, m, c2, n) {
  return matchLetter(c1, m, m) + matchLetter(c2, 0, n);
}

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

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

剩下的就是为 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]*$))"

现在你可以用

做测试了
makeRegExp().test("baabaaa");

对于长度=40 的情况,regxp 的长度为 679 个字符。一个非常粗略的基准表明它的执行时间不到一微秒。

关于javascript - 正则表达式字符计数,但有些计数为三个,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40296630/

相关文章:

javascript - AngularJS 中的增量距离

java - 在android中提取xml标签之间的字符串而不解析xml

python - 检查带有特殊字符的 unicode

Python正则表达式将分割符之前的文本和整个文本之后的文本分组?

php - 尝试使用 JSON 将 javascript 发送到 php 脚本进行打包

javascript - JSHINT 严格违规函数表达式与函数声明

javascript - 正则表达式小组赛

java - 正则表达式未正确匹配限制值

javascript - 无法使用 Three.js 导入 OrbitControls.js

javascript - 学习 HTML5 和 CSS3。我也需要 javascript 吗?