为何要写这么一篇文章呢?是由于本身最近在研究和学习正则表达式,而后在RegexGolf上练习技能的时候遇到了这么一道题目,以为颇有趣。我当时虽然也解决了这个问题,可是正则表达式写的有点长,并且也只算是一种取巧的解决方案。由于若是测试用例再多一点可能我写的这个正则表达式就不可以知足需求了。javascript
后来在复盘这道题目的解决方案的时候,查阅了不少相关的资料。发现了更简洁,更准确的答案。当我看到答案的那一瞬间,我突然发现本身当初距离这个答案其实也不远,若是本身当时再好好研究一下,有可能就想出了更简洁的答案了。固然,也许最终仍是没有想出来,那里有那么多的若是呢😂。java
说了这么多,让咱们来看一下这道题目吧。以下图所示:git
咱们须要匹配左边的这些字符串,同时排除右边的这些字符串。不知道你们以前有没有作过相似的题目,如今你们就能够测试一下本身的正则的水平。我的感受若是这道题目能够作出来的话,那么你的正则水平至少是中等偏上的水平啦。没有作出来也没有关系,毕竟在平时的开发过程当中咱们很难会遇到这种需求,不过也能够学习一下。加深一下本身对正则匹配过程的理解。github
我当时看了题目的提示,知道是匹配素数,可是对于如何匹配一个素数我倒是不知道的。因而我就取了个巧,在测试用例不是不少的状况下,能够进行穷举呀。因而我就写下了下面的答案:正则表达式
^(?!(?:(x{2}){2,}|(x{3}){2,}|(x{5}){2,})$)
虽然有点长,可是好歹也算是经过了测试用例。app
对正则表达式学习的比较深刻的一些同窗,看到了上面的正则应该很快就知道是什么意思了。我能够先简单的给你们讲解一下。上面的正则表达式匹配的过程是这样的:函数
^
:匹配一行的开始。(?!...)
:否认的顺序环视,匹配一个位置,后面紧跟着的是须要排除的条件。(?:)
:非捕获型括号,在这里用来限制多选结构的做用范围,固然在这里也可使用捕获型的括号。(x{2}){2,}
:首先{2,}
限定了前面的表达式(x{2})
出现的次数只能是两次或者两次以上。(x{2})
表示须要匹配两个x
。(x{3}){2,}
和(x{5}){2,}
:这两个部分跟上面的正则片断是相似的意思。$
:匹配一行的结尾。因此上面的正则表达式表示的意思就是匹配一个字符串的开始,而后紧接着字符串中x的个数不能是2个x的2倍或者以上,不能是3个x的2倍或者以上,不能是5个x的2倍或者以上;直到字符串的结尾。学习
接下来,咱们针对这个问题深刻的研究一下。首先咱们须要知道什么是素数,素数就是除了1和它自己以外不可以被其余整数整除的数,不包括1。那么咱们怎么判断一个数字是否是素数呢?咱们假设这个数字是N,而后咱们用N去除以2,看一下是否可以整除,不可以整除的话,咱们再用N去除以3,若是还不能够就除以4,一直除到N/2(N/2若是不可以整除,须要取整)。若是都不能够的话,咱们就能够断定N是一个素数。测试
若是咱们能够把上面这个过程使用正则表达式表示出来,那么咱们就可以匹配一个素数了。可是想要一会儿解决这个问题可能有点难,咱们须要按照刚才的思路一步一步来构造咱们的正则表达式。首先咱们能够转换一下思路,直接匹配一个素数有点难度,咱们能够先匹配一个合数,而后排除掉匹配的这个合数就能够了。网站
首先咱们须要匹配可以被2整除的数,按照上面题目的要求的话,咱们须要匹配一个字符串,这个字符串中x
出现的次数须要是偶数次。对于这个问题,咱们很容易想到这样一个表达式/(xx)+/
;若是须要匹配可以被3整除的数的话,按照上面题目的要求,咱们很容易想到正则表达式/(xxx)+/
,因而到这里你可能会以为我好像知道了匹配可以被大于等于2整除的数的答案,那就是/(xx+)+/
。你能够本身试一下这个正则表达式,结果并非你所想的那样。
为何呢?由于正则表达式/(xx+)+/
的意思是,(xx+)
能够出现至少一次,可是(xx+)
多是2个x
,也多是3个x
或者4个x
。上面的正则表达式不可以知足在同一次的匹配过程当中(xx+)
都表示相同个数的x
。那么咱们应该如何解决这个问题呢?这个时候咱们就要思考,在正则表达式中什么能够表示已经匹配了的内容呢?没错,就是捕获与反向引用。若是咱们须要在匹配的过程当中继续匹配相同的次数的话,咱们须要使用反向引用表示前面已经匹配的结果。那么到这里,这个正则表达式就呼之欲出了。这个正则表达式就是:
/(xx+)\1/
我来解释一下上面的这个正则表达式,首先(xx+)
表示x
的个数>=2,\1
表示反向引用,引用的值就是(xx+)
已经匹配的值。当(xx+)
后面紧跟着\1
时,(xx+)
在实际中到底能匹配多少个x
呢?这由要匹配的字符中出现多少个x
决定的。咱们一块儿来看一下上面的正则表达式能匹配的x
的个数。
上面的图二中,蓝色背景表示整个正则表达式匹配的内容。在图三中,青色部分代表了(xx+)
匹配的内容。\1
匹配的内容和(xx+)
匹配的内容是同样的。这就是捕获((xx+)
)与反向引用(\1
)。
上面的正则表达式表示了字符串的长度可以被二等分,咱们还要更进一步;咱们须要字符串的长度还能够被三等分,四等分甚至更多。那咱们应该怎么作呢?答案彷佛已经很明显了,咱们只须要在\1
后面添加一个+
就能够了。
/(xx+)\1+/
这样,上面的正则表达式表示的意思就是,首先匹配2个或2个以上的x
,而后记住匹配的结果。接下来须要将这个匹配结果重复至少1次以上。在这里须要注意的是+
是贪婪匹配,它会把它做用的那个匹配匹配尽量多的次数。
因此到目前为止咱们已经能够成功的匹配字符串的长度是合数的这些字符串了,可是还不知足题目的需求。咱们要作的是须要排除上面匹配到的这些结果。遇到这种状况,咱们首先想到的应该就是顺序环视了,固然咱们在这里须要使用否认的顺序环视(?!...)
,它表示当前位置的后面不可以出现匹配...
的状况。
因此这个正则表达式的完整结果就是:
/^(?!(xx+)\1+$)/
其中^
和$
也是必须的,对于这里的^
的意思是,匹配字符串的开头,而后接下来整个(?!(xx+)\1+$)
匹配的也是一个位置,它表示在这个位置的后面字符串中x
的长度不可以是一个合数,直到字符串的结尾。
到这里为止,关于上面题目的讲解就算完成啦。可是咱们尚未解决如何匹配一个真正的素数而不是一个字符串的问题。固然有了上面上面的经验,我想这对你们来讲应该也不是一个难题了。咱们能够把这个素数N,转换成一个字符串,这个字符串中的全部字符都是相同的,而且长度为N。那么咱们立刻就能够写出一个这样的函数。在这里咱们使用JavaScript语言来表示这样一个函数。以下所示:
const isPrime = (n) => /^(?!(11+)\1+$)/.test(new Array(n).fill(1).join('')); console.log(isPrime(2)); // true console.log(isPrime(3)); // true console.log(isPrime(4)); // false console.log(isPrime(5)); // true
在这里给你们推荐几个学习和练习正则表达式的网站:
关于正则表达式匹配一个素数的原理到这里就结束啦,若是你们有什么疑问和建议均可以在这里提出来。欢迎你们关注个人公众号「关山不难越」,咱们一块儿学习更多有用的正则知识,一块儿进步。