匹配恰好5个数字和一个可选空格的正则表达式

21

最近我需要创建一个JavaScript正则表达式来检查输入。输入可以是5或6个字符长,必须恰好包含5个数字和一个可选空格,该空格可以出现在任何位置。我对正则表达式不太熟悉,尽管我试图寻找更好的方法,但最终只能得到以下内容:

(^\d{5}$)|(^ \d{5}$)|(^\d{5} $)|(^\d{1} \d{4}$)|(^\d{2} \d{3}$)|(^\d{3} \d{2}$)|(^\d{4} \d{1}$)  

这正好符合我的需求,所以允许的输入为(如果0是任意数字)

'00000'  
' 00000'  
'0 0000'  
'00 000'  
'000 00'  
'0000 0'  
'00000 '

我怀疑这不是实现与正则表达式匹配的唯一方式,但我没有找到更简洁的方法。因此我的问题是,有什么更好的写法吗?

谢谢。

编辑:
所以,这是可能的!Tom Lord的回答使用正则表达式实现了我需要的功能,因此我将其标记为了我的问题的正确答案。

然而,就在我发布这个问题后不久,我意识到我的想法是错误的,因为项目中的其他所有输入都可以轻松地用正则表达式进行“验证”,所以我立即假设我也可以用它来验证这个输入。

结果发现我只需要这样做:

const validate = function(value) {
    const v = value.replace(/\s/g, '')
    const regex = new RegExp('^\\d{5}$');
    return regex.test(v);
}  

感谢大家提供的酷炫答案和想法! :)

编辑2:我忘了提及一个可能非常重要的细节,那就是输入受限,所以用户最多只能输入6个字符。我的歉意。


1
不确定您是否能够在正则表达式中捕获此内容,而不是使用淫秽的扭曲方式,但通过简单地循环遍历输入字符来解决应该很容易。 - Jared Smith
如果字符串中空格的出现次数为0或1,则可以计算空格的出现次数,然后匹配[\d ](或者您可能还需要验证字符串长度)。 - user3089519
正则表达式:现在你有两个问题 :) - Tom Lord
正则表达式:现在你有两个问题 :) - EyfI
1
@Eyfl 更全面的分析“现在你有两个问题”:https://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/ - Kevin Fee
显示剩余7条评论
7个回答

23
注意:使用正则表达式解决此问题可能不是最佳答案。如下所述,使用简单的函数计算数字和空格可能更容易!但是,由于问题要求使用正则表达式回答,在某些情况下您可能被迫使用正则表达式解决此问题(例如,如果您被限制在某个库的实现中),以下答案可能会有所帮助:
此正则表达式匹配恰好包含5个数字的行:
^(?=(\D*\d){5}\D*$)

这个正则表达式匹配包含一个可选空格的行:
^(?=[^ ]* ?[^ ]*$)

如果我们将它们放在一起,并确保字符串仅包含数字和空格([\d ]*$),我们得到:
^(?=(\D*\d){5}\D*$)(?=[^ ]* ?[^ ]*$)[\d ]*$

你也可以使用[\d ]{5,6}代替[\d ]*,实现同样的效果。

演示

解释:

该正则表达式使用预查。这些是零宽度模式匹配器,这意味着模式的两个部分都"锚定"到字符串的开头。

  • \d表示"任何数字",\D表示"任何非数字字符"。

  • 表示空格,[^ ]表示"任何非空格字符"。

  • \D*\d被重复5次,以确保字符串中恰好有5个数字。

以下是正则表达式工作原理的可视化图示:

regex visualisation

请注意,如果您实际上希望“可选空格”包括制表符等内容,则可以使用\s\S

更新:由于这个问题似乎引起了相当大的反响,我想澄清一下关于这个答案的一些内容。

我的答案上面有几个更加“简单”的变体解决方案,例如:

// Only look for digits and spaces, not "non-digits" and "non-spaces":
^(?=( ?\d){5} *$)(?=\d* ?\d*$)

// Like above, but also simplifying the second lookahead:
^(?=( ?\d){5} *$)\d* ?\d*

// Or even splitting it into two, simpler, problems with an "or" operator: 
^(?:\d{5}|(?=\d* \d*$).{6})$

以上每行的演示:1 2 3

甚至,如果我们可以假设该字符串不超过6个字符,那么仅这样就足够了:

^(?:\d{5}|\d* \d*)$

所以考虑到这一点,为什么你可能想要使用原始解决方案来解决类似的问题呢?因为它是通用的。再看一下我的原始答案,使用free-spacing重新编写:
^
(?=(\D*\d){5}\D*$) # Must contain exactly 5 digits
(?=[^ ]* ?[^ ]*$)  # Must contain 0 or 1 spaces
[\d ]*$            # Must contain ONLY digits and spaces

使用连续的前瞻模式可以在各种场景下使用,编写高度结构化且易于扩展的模式(也许令人惊讶的是)。

例如,假设规则已更改,您现在想匹配2-3个空格,1 . 和任意数量的连字符。 实际上非常容易更新正则表达式:

^
(?=(\D*\d){5}\D*$)       # Must contain exactly 5 digits
(?=([^ ]* ){2,3}[^ ]*$)  # Must contain 2 or 3 spaces
(?=[^.]*\.[^.]*$)        # Must contain 1 period
[\d .-]*$   # Must contain ONLY digits, spaces, periods and hyphens

因此,总的来说,确实存在“更简单”的正则表达式解决方案,而且很可能还有更好的非正则表达式解决方案适用于OP的具体问题。但是我提供的是一种通用的、可扩展的设计模式,用于匹配这种类型的模式。

1
这会通过 "f7364ffff8f" 吗?“输入可以是5或6个字符长,必须恰好包含5个数字和一个可选空格”。 - Yury Tarabanko
感谢您的纠正,@YuryTarabanko,我已经解决了这个问题。 - Tom Lord
1
更简单的解决方案是 ^(?=(\D*\d){5}\D*$)\d* ?\d*$,它读作:"正好 5 个数字" 和 "只有数字和一个可选的空格"。如果加上限制条件,使其不超过 6 个字符,则可以更简单:^( ?\d){5} ?。增加的条件可以表示为 (?=.{,6}$) - maaartinus
4
过于复杂,六个月后容易产生误解或混淆。如果需求发生变化,则难以更改。 - jpmc26
1
@yay295,我们不要在这里混淆视听;使用\d是可以的。你提供的链接是关于Python3而不是JavaScript的。只有当你启用utf8正则表达式修饰符(/u)时,才可能出现问题——即使如此,我也不确定JavaScript的行为。 - Tom Lord
显示剩余5条评论

8

我建议首先检查是否有五个数字 ^\d{5}$ 或者在六个字符中向前查找单个空格 ^(?=\d* \d*$)

将这些部分表达式组合起来得到 ^\d{5}$|^(?=\d* \d*$).{6}$

let regex = /^\d{5}$|^(?=\d* \d*$).{6}$/;

console.log(regex.test('00000'));   // true
console.log(regex.test(' 00000'));  // true
console.log(regex.test('00000 '));  // true
console.log(regex.test('00 000'));  // true
console.log(regex.test('  00000')); // false
console.log(regex.test('00000  ')); // false
console.log(regex.test('00  000')); // false
console.log(regex.test('00 0 00')); // false
console.log(regex.test('000 000')); // false
console.log(regex.test('0000'));    // false
console.log(regex.test('000000'));  // false
console.log(regex.test('000 0'));   // false
console.log(regex.test('000 0x'));  // false
console.log(regex.test('0000x0'));  // false
console.log(regex.test('x00000'));  // false

或者可以通过以下方式单独匹配部分表达式:

/^\d{5}$/.test(input) || input.length == 6 && /^\d* \d*$/.test(input)

也匹配982 5l - revo
@revo 很好,正则表达式的前瞻缺少了一个 $ - le_m

7

对我来说这似乎更直观,并且是O(n)的。

function isInputValid(input) {
    const length = input.length;
    if (length != 5 && length != 6) {
        return false;
    }

    let spaceSeen = false;
    let digitsSeen = 0;
    for (let character of input) {
        if (character === ' ') {
            if (spaceSeen) {
                return false;
            }
            spaceSeen = true;
        }
        else if (/^\d$/.test(character)) {
            digitsSeen++;
        }
        else {
            return false;
        }
    }

    return digitsSeen == 5;
}

喜欢你的方法。 - Piyush
是的,你一直是对的。这从一开始就不是纯正则表达式的问题。我想出了一个稍微不同的方法(你可以看到我编辑到问题中了)。我不确定你的方法是否更有效,但我赞成你的答案,因为你很快就想出了我早该想出的东西。干杯! - EyfI
哦,看了一下你的函数,它可能也会对6个数字返回true,但实际上它不应该这样做。 - EyfI
1
@EyfI 已经修复了。希望你不介意,Jonathan。非正则表达式答案加1。正则表达式并不是处理所有字符串的方式。 - jpmc26

1
你可以把它分成两半:

var input = '0000 ';

if(/^[^ ]* [^ ]*$/.test(input) && /^\d{5,6}$/.test(input.replace(/ /, '')))
  console.log('Match');


2
只允许输入五位数字。 if (/^\d{5}$/.test(input.replace(' ', ''))) 应该就可以了。 - Casimir et Hippolyte

1
这是一个简单的正则表达式来完成任务:

^(?=[\d ]{5,6}$)\d*\s?\d*$

解释:

^ 断言字符串的开头位置

正向先行断言 (?=[\d ]{5,6}$)

断言以下正则表达式匹配

匹配列表中的一个字符 [\d ]{5,6}

{5,6} 量词 - 匹配 5 至 6 次,尽可能多地匹配,必要时回溯(贪婪模式)

\d 匹配数字 (相当于 [0-9])

匹配字符 字面意义 (区分大小写)

$ 断言字符串的结尾位置 \d* 匹配数字 (相当于 [0-9])

  • 量词 - 匹配零次或多次,尽可能多地匹配,必要时回溯(贪婪模式)

\s 匹配任何空白字符 (相当于 [\r\n\t\f\v ])

\d* 匹配数字 (相当于 [0-9])

  • 量词 - 匹配零次或多次,尽可能多地匹配,必要时回溯(贪婪模式)

$ 断言字符串的结尾位置


1
现在对于数字位数来说太过宽松了,它会匹配到 '000000' 和 '000 0'。 - le_m

0
string="12345 ";
if(string.length<=6 && string.replace(/\s/g, '').length<=5 && parseInt(string,10)){
  alert("valid");
}

您可以简单地检查长度并检查它是否为有效数字...

1
除非空格位于“字符串”的前面或末尾,否则此检查将失败,因为这是trim()将删除空格并使string.length <= 5的唯一位置。 - Mike Corcoran

0

这是我不使用正则表达式的做法:

string => [...string].reduce(
    ([spaces,digits], char) =>
        [spaces += char == ' ', digits += /\d/.test(char)],
    [0,0]
).join(",") == "1,5";

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接