ECMAScript的正则表达式,733+ 690+ 158 119 118 (117🐌)字节
经过超过4½年的不活动时间,我对正则表达式的兴趣激起了新的活力。因此,我一直在寻找更自然的数字集和函数以与一元ECMAScript正则表达式匹配,继续改进我的正则表达式引擎,并且也开始研究PCRE。
我着迷于ECMAScript正则表达式中构造数学函数的异同。必须从完全不同的角度来解决问题,直到关键的见识到来之前,才知道这些问题是否完全可以解决。它迫使铸造一个更宽的网,以发现哪些数学特性可以用来解决特定问题。
匹配阶乘数是我什至在2014年甚至没有考虑解决的问题-或者如果我只是暂时将其排除在外,那就太不可能了。但是上个月,我意识到可以做到。
与我在其他ECMA regex帖子中一样,我会警告:我强烈建议学习如何在ECMAScript regex中解决一元数学问题。对于我而言,这是一次引人入胜的旅程,对于那些可能想要自己尝试的人,尤其是对数字理论感兴趣的人,我都不想破坏它。请参阅此较早的帖子,以获取一系列连续扰流器标记的建议问题的列表,以逐一解决。
因此,如果您不想破坏一些高级一元正则表达式魔术,请不要再继续阅读。如果您确实想自己弄清楚该魔术,我强烈建议您先解决ECMAScript regex中的一些问题,如上面链接的那部分所述。
这是我的主意:
与大多数其他方法一样,与此数字集进行匹配的问题在于,在ECMA中,通常不可能在循环中跟踪两个变化的数字。有时它们可以多路复用(例如,可以将相同基数的功率明确地加在一起),但这取决于它们的属性。因此,我不能只是从输入数字开始,然后将其除以递增的红利,直到达到1(或者至少我以为如此)。
然后,我对阶乘中素数的多重性进行了一些研究,并了解到有一个公式可用于 ECMA正则表达式中!
经过一段时间的研究,并同时构建了其他一些正则表达式后,我承担了编写阶乘正则表达式的任务。花了几个小时,但最终效果很好。另外,算法可以将反阶乘作为匹配返回。甚至没有回避的机会。由于必须在ECMA中实施该方法的本质,因此有必要在做任何其他事情之前先猜测一下逆阶乘是什么。
缺点是该算法需要很长的正则表达式...但是我很高兴它最终需要在我的651字节乘法正则表达式中使用一种技术(该方法最终被淘汰了,因为使用了50字节正则表达式)。我一直希望出现一个需要此技巧的问题:通过明确地将它们加在一起并在每次迭代中将它们分开,循环使用两个数字,它们都是同一个基数的幂。
但是由于该算法的难度和长度,我使用了分子前行(形式为(?*...)
)来实现它。这不是ECMAScript或任何其他主流正则表达式引擎中的功能,而是我在引擎中实现的功能。在分子前瞻内部没有任何捕获,它在功能上等同于原子前瞻,但是有了捕获,它可能会非常强大。引擎将回溯到前瞻,并且可以用来推测一个值,该值循环所有可能性(以供以后测试),而不会消耗输入字符。使用它们可以使实现更清洁。(可变长度后视至少在功能上等同于分子先行,但后者往往会带来更直接,更优雅的实现。)
因此733和690字节的长度实际上并不能表示该解决方案的ECMAScript兼容版本-因此后面是“ +”;肯定有可能将该算法移植到纯ECMAScript中(这会大大增加其长度),但是我却没有解决它……因为我想到了一个更简单,更紧凑的算法!一种无需分子先行即可轻松实现的方法。它也明显更快。
像以前一样,这个新的必须对逆阶乘数进行猜测,循环遍历所有可能性并测试它们是否匹配。它将N除以2,以为其需要做的工作腾出空间,然后播种一个循环,在该循环中,它将反复用除以3的除数重复该输入,并每次递增。(因此,1!和2!不能与主算法匹配,必须分开处理。)除数通过将其添加到运行商中来保持跟踪;这两个数字可以明确分开,因为假设M!== N,运行商将继续被M整除,直到等于M。
这个正则表达式在循环的最内部进行除数运算。除法算法与我的其他正则表达式相同(并且类似于乘法算法):对于A≤B,仅当C%A = 0且B是满足B≤C的最大数时,A * B = C(如果有) C%B = 0且(CB-(A-1))%(B-1)= 0,其中C是除数,A是除数,B是商。(对于A≥B的情况,可以使用类似的算法,并且如果不知道A与B的比较,则只需进行一次除数检验。)
因此,我很喜欢这个问题能够比我的高尔夫优化斐波那契正则表达式减少甚至更少的复杂性,但是令我感到失望的是,我的相同基数的多路复用功率技术必须等待另一个问题实际上需要它,因为这一点不需要。这就是我的651字节乘法算法被一个50字节的算法取代的故事!
编辑:我能够使用格里米(Grimy)发现的技巧来丢弃1个字节(119→118),该技巧可在商被保证大于或等于除数的情况下进一步缩短除法。
事不宜迟,这里是正则表达式:
正确/错误版本(118字节):
^((x*)x*)(?=\1$)(?=(xxx\2)+$)((?=\2\3*(x(?!\3)xx(x*)))\6(?=\5+$)(?=((x*)(?=\5(\8*$))x)\7*$)x\9(?=x\6\3+$))*\2\3$|^xx?$
在线尝试!
返回阶乘或不匹配(124字节):
^(?=((x*)x*)(?=\1$)(?=(xxx\2)+$)((?=\2\3*(x(?!\3)xx(x*)))\6(?=\5+$)(?=((x*)(?=\5(\8*$))x)\7*$)x\9(?=x\6\3+$))*\2\3$)\3|^xx?$
在线尝试!
以ECMAScript +\K
(120字节)返回反阶乘或不匹配:
^((x*)x*)(?=\1$)(?=(xxx\2)+$)((?=\2\3*(x(?!\3)xx(x*)))\6(?=\5+$)(?=((x*)(?=\5(\8*$))x)\7*$)x\9(?=x\6\3+$))*\2\K\3$|^xx?$
以及带有注释的自由空间版本:
^
(?= # Remove this lookahead and the \3 following it, while
# preserving its contents unchanged, to get a 119 byte
# regex that only returns match / no-match.
((x*)x*)(?=\1$) # Assert that tail is even; \1 = tail / 2;
# \2 = (conjectured N for which tail == N!)-3; tail = \1
(?=(xxx\2)+$) # \3 = \2+3 == N; Assert that tail is divisible by \3
# The loop is seeded: X = \1; I = 3; tail = X + I-3
(
(?=\2\3*(x(?!\3)xx(x*))) # \5 = I; \6 = I-3; Assert that \5 <= \3
\6 # tail = X
(?=\5+$) # Assert that tail is divisible by \5
(?=
( # \7 = tail / \5
(x*) # \8 = \7-1
(?=\5(\8*$)) # \9 = tool for making tail = \5\8
x
)
\7*$
)
x\9 # Prepare the next iteration of the loop: X = \7; I += 1;
# tail = X + I-3
(?=x\6\3+$) # Assert that \7 is divisible by \3
)*
\2\3$
)
\3 # Return N, the inverse factorial, as a match
|
^xx?$ # Match 1 and 2, which the main algorithm can't handle
我对这些正则表达式进行高尔夫优化的完整历史记录在github上:
用于匹配阶乘数的正则表达式-多重比较方法,带有分子lookahead.txt
用于匹配阶乘数.txt的正则表达式(上面显示的一个)
请注意,((x*)x*)
可以将其更改为((x*)+)
,将大小减小1个字节(至117个字节),而不会丢失正确的功能-但是正则表达式的响应速度呈指数级增长。但是,此技巧虽然可在PCRE和.NET中使用,但由于在循环中遇到零长度匹配时的行为而无法在ECMAScript 中使用。((x+)+)
可以在ECMAScript中工作,但这会破坏正则表达式,因为对于,需要捕获的值(将正则表达式更改为1索引将使高尔夫球失去这种好处)。n = 3 !3 − 3 = 0\2
3 − 3 = 0
.NET regex引擎不会在其ECMAScript模式下模拟此行为,因此117字节的regex可以工作:
在线尝试! (指数级减速版本,带有.NET正则表达式引擎+ ECMAScript仿真)
1
?