我正在尝试构建一个对输入长度施加限制的正则表达式,但并非所有字符在此长度上都相等。我会把理由放在问题的底部。作为一个简单的例子,我们将最大长度限制为 12,并且只允许 a
和 b
,但是 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]*$)/
我们将编写一个函数来创建这样的组合前瞻,它完全匹配 m
次 c1
和最多 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/