正则表达式(ECMAScript的香味),392个 358 328 224 206 165字节
将斐波那契数与ECMAScript正则表达式匹配(一元)所需的技术与在大多数其他正则表达式中的最佳实现方式相去甚远。缺少前向/嵌套的反向引用或递归意味着不可能直接计算或保持任何事物的连续总数。缺乏后顾之忧使得即使有足够的工作空间也常常是一个挑战。
必须从完全不同的角度解决许多问题,并且直到一些关键见解出现之前,这些问题似乎无法解决。它迫使您建立一个更广泛的网络,以发现您正在处理的数字的哪些数学属性可以用来解决特定问题。
2014年3月,斐波纳契数就是这种情况。最初查看Wikipedia页面时,我想不出办法,尽管其中一个特定的属性似乎非常接近。然后,数学家teukon概述了一种方法,该方法非常清楚地表明可以同时使用该属性和另一个属性。他不愿真正构建正则表达式。我继续前进时他的反应:
你疯了!我以为你可以这样做。
与其他ECMAScript一元数学正则表达式帖子一样,我会发出警告:我强烈建议学习如何在ECMAScript正则表达式中解决一元数学问题。对于我而言,这是一次引人入胜的旅程,对于那些可能想要自己尝试的人,尤其是对数字理论感兴趣的人,我都不想破坏它。请参阅该帖子,以获取逐个解决问题的建议问题列表,以一一解决。
因此,如果您不想破坏一元正则表达式魔术,请不要再继续阅读。如果您确实想自己弄清楚该魔术,我强烈建议您先解决ECMAScript regex中的一些问题,如上面链接的那部分所述。
我最初面临的挑战:正整数X 为Fibonacci数当且仅当5× 2 + 4和/或5× 2 -图4是一个完全平方。但是在正则表达式中没有空间可以计算此值。我们唯一需要处理的空间就是数字本身。我们甚至没有足够的空间乘以5 或乘平方,更不用说两者了。
teukon关于如何解决它的想法(最初在这里发布):
正则表达式以形式为字符串的形式呈现^x*$
,令z为长度。手动检查z是否是前几个斐波那契数之一(最多应做21个)。如果不是:
- 读取两个数字,a <b,使b不大于2a。
- 使用前瞻性构建a 2,ab和b 2。
- 断言或者5A 2 + 4或5a 2 -图4是一个完全平方数(因此必须为F n-1个对一些N)。
- 断言5b 2 + 4或5b 2 + 4是一个完美的正方形(因此b必须为F n)。
- 通过使用较早构建的a 2,ab和b 2以及标识,检查z = F 2n + 3或z = F 2n + 4:
- F 2n-1 = F n 2 + F n-1 2
- F 2n =(2F n-1 + F n)F n
简言之:这些身份使我们能够减少检查给定的数字是斐波那契数来检查一对的问题,很多小的数字是斐波那契数。一个小代数将表明,对于足够大的n(应该做n = 3),F 2n + 3 > F n + 5F n 2 + 4总是应该有足够的空间。
而这里是在C算法的样机,我在正则表达式执行它之前写了一个测试。
因此,事不宜迟,这里是正则表达式:
^((?=(x*).*(?=x{4}(x{5}(\2{5}))(?=\3*$)\4+$)(|x{4})(?=xx(x*)(\6x?))\5(x(x*))(?=(\8*)\9+$)(?=\8*$\10)\8*(?=(x\2\9+$))(x*)\12)\7\11(\6\11|\12)|x{0,3}|x{5}|x{8}|x{21})$
在线尝试!
以及印刷精美,带有注释的版本:
^(
(?=
(x*) # \2+1 = potential number for which 5*(\2+1)^2 ± 4
# is a perfect square; this is true iff \2+1 is a Fibonacci
# number. Outside the surrounding lookahead block, \2+1 is
# guaranteed to be the largest number for which this is true
# such that \2 + 5*(\2+1)^2 + 4 fits into the main number.
.*
(?= # tail = (\2+1) * (\2+1) * 5 + 4
x{4}
( # \3 = (\2+1) * 5
x{5}
(\2{5}) # \4 = \2 * 5
)
(?=\3*$)
\4+$
)
(|x{4}) # \5 = parity - determined by whether the index of Fibonacci
# number \2+1 is odd or even
(?=xx (x*)(\6 x?)) # \6 = arithmetic mean of (\2+1) * (\2+1) * 5 and \8 * \8,
# divided by 2
# \7 = the other half, including remainder
\5
# require that the current tail is a perfect square
(x(x*)) # \8 = potential square root, which will be the square root
# outside the surrounding lookahead; \9 = \8-1
(?=(\8*)\9+$) # \10 = must be zero for \8 to be a valid square root
(?=\8*$\10)
\8*
(?=(x\2\9+$)) # \11 = result of multiplying \8 * (\2+1), where \8 is larger
(x*)\12 # \12 = \11 / 2; the remainder will always be the same as it
# is in \7, because \8 is odd iff \2+1 is odd
)
\7\11
(
\6\11
|
\12
)
|
x{0,3}|x{5}|x{8}|x{21} # The Fibonacci numbers 0, 1, 2, 3, 5, 8, 21 cannot be handled
# by our main algorithm, so match them here; note, as it so
# happens the main algorithm does match 13, so that doesn't
# need to be handled here.
)$
这些评论中没有解释乘法算法,但是在我的大量正则表达式文章的一段中对此做了简要解释。
我维护着六个不同版本的Fibonacci正则表达式:四个从最短长度到最快速度并使用上述算法,另外两个使用不同,更快但更长的算法,正如我发现的,实际上可以返回将Fibonacci索引作为匹配项(在此处说明该算法已超出了本文的范围,但在原始讨论Gist中对此进行了解释)。我不认为我会再维护很多非常相似的正则表达式版本,因为当时我正在PCRE和Perl中进行所有测试,但正则表达式引擎 足够快,以至于对速度的关注不再那么重要了(如果某个特定的结构造成了瓶颈,我可以对其进行优化)–尽管如果两者之间存在差异,我可能会再次维护一个最快的版本和一个最短的版本速度足够大。
“将斐波纳契指数减去1作为匹配项”版本(不打高尔夫球):
在线尝试!
所有版本都在github上,其中包含高尔夫优化的完整提交历史记录:
用于匹配斐波那契数字的正则表达式-短,速度为0.txt(如本文中最短但最慢的
正则表达式)用于匹配斐波那契数字的正则表达式-短,速度为1.txt的
正则表达式用于匹配斐波那契数字-短,速度为2.txt的
正则表达式为匹配斐波纳契数字-简短,速度为3.txt的
正则表达式,用于匹配斐波那契数字-最快.txt
正则表达式,用于匹配斐波那契数字-返回index.txt