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

时间:2016-10-28 01:32:50

标签: 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,为{{1}消耗第二个群集

约束

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

原理

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

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

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

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

3 个答案:

答案 0 :(得分:6)

鉴于regexp的局限性,没有真正直接的方法可以做到这一点。您将需要测试所有组合,例如十三个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次出现nc2次出现{/ 1}}:

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个字符。一个非常粗略的基准测试显示它在不到一微秒的时间内执行。

答案 1 :(得分:1)

尝试使用以下内容:

^((a{1,3}|b){1,4}|(a{1,4}|a?b|ba){1,3}|((a{2,3}|b){2}|aaba|abaa){2})$

示例:https://regex101.com/r/yTTiEX/6

这将其分解为逻辑可能性:

4个部分,每个部分的值最多为3 3个部分,每个部分的值最多为4 2个部分,每个部分的值最多为6个。

答案 2 :(得分:1)

如果要在存在多字节编码时计算字节数,可以使用此功能:

function bytesLength(str) {
  var s = str.length;
  for (var i = s-1; i > -1; i--) {
    var code = str.charCodeAt(i);
    if (code > 0x7f && code <= 0x7ff) {s++;}
    else if (code > 0x7ff && code <= 0xffff) {s+=2;}
    if (code >= 0xDC00 && code <= 0xDFFF) {i--;}
  }
  return s;
}

console.log(bytesLength('敗')); // length 3