匹配长度为四次幂的字符串


28

在这个问题的范围内,让我们只考虑由x任意次数重复的字符组成的字符串。

例如:

<empty>
x
xx
xxxxxxxxxxxxxxxx

(嗯,实际上不必如此x-只要整个字符串只有一种类型的字符,任何字符都可以)

以您选择的任何正则表达式形式编写一个正则表达式,以匹配某个非负整数n(n> = 0)长度为n 4的所有字符串。例如,长度为0、1、16、81等的字符串有效;其余的无效。

由于技术限制,难以测试大于128的n值。但是,无论如何,您的正则表达式在逻辑上均应正常工作。

请注意,不允许您在正则表达式中(对Perl用户)执行任意代码。允许使用任何其他语法(环顾四周,向后引用等)。

还请提供有关您解决问题方法的简短说明。

(请不要粘贴自动生成的正则表达式语法说明,因为它们没有用)


“ xx”无效吗?
Kendall Frey 2014年

@KendallFrey:不。这是无效的。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 2014年

@nhahtdh您认为对此有可能解决吗?
xem 2014年

1
@Timwi:是的。Java,PCRE(也可能是Perl,但无法测试)、. NET。我的在Ruby / JS中不起作用。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 2014年

1
此问题已添加到“高级Regex-Fu”下的“ 堆栈溢出正则表达式常见问题解答 ”中。
aliteralmind 2014年

Answers:


21

这种(不规则的)正则表达式似乎起作用。

^((?(1)((?(2)\2((?(3)\3((?(4)\4x{24}|x{60}))|x{50}))|x{15}))|x))*$

此正则表达式与PCRE,Perl,.NET风格兼容。

这基本上是遵循“差异树”(不确定是否有合适的名称)的,它告诉正则表达式为下一个四次幂匹配多少个x:

1     16    81    256   625   1296  2401 ...
   15    65    175   369   671   1105 ...
      50    110   194   302   434 ...
         60    84    108   132 ...
            24    24    24 ...  # the differences level out to 24 on the 4th iteration

\2\3\4存储和更新分别显示在第二,第三和第四行,差异。

此结构可以轻松扩展为更高的功率。

当然,这不是一个优雅的解决方案,但确实可以。


+1。好答案。尽管此答案与我的答案不同(它使用条件正则表达式,而我的则不是),但它具有与我的解决方案相同的精神(利用差异树并利用某些正则表达式引擎的前向后向引用)。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 2014年

整洁的想法重新差异树。对于正方形,树是1 4 9 16 ... 3 5 7 ... 2 2 2,对吗?
Sparr

@Sparr谢谢,是的
波动性

24

另一种解决方案

我认为,这是该网站上最有趣的问题之一。我要感谢Deadcode使其重新回到顶部。

^((^|xx)(^|\3\4\4)(^|\4x{12})(^x|\1))*$

39个字节,没有任何条件或断言...之类的。在使用(^|)时,交替是一种有条件的方式,可以在“第一次迭代”和“不是第一次迭代”之间进行选择。

可以看到此正则表达式在这里工作:http : //regex101.com/r/qA5pK3/1

PCRE和Python都正确地解释了正则表达式,并且它也在Perl中经过了n = 128的测试,包括n 4 -1n 4 +1


定义

通用技术与已发布的其他解决方案中的技术相同:定义一个自引用表达式,该表达式在每个后续迭代中都使用无限量词()匹配等于前向差分函数D f的下一项的长度*。前向差分函数的正式定义:

定义1:前向差函数

此外,还可以定义高阶差分函数:

定义2:第二正向差分函数

或更笼统地说:

定义3:第k个前向差分函数

前向差分函数具有许多有趣的特性。排序是对连续函数的导数。例如,d ˚FÑ次多项式将始终是一个N-1次多项式,并且对于任何,如果d ˚F = d ˚F i + 1的,则该函数˚F是指数的,在大致相同的方式e x的导数等于自身。最简单的离散函数为其˚F = d ˚F2 Ñ


f(n)= n 2

在研究上述解决方案之前,让我们从一些简单的事情开始:一个正则表达式,它匹配长度为完美平方的字符串。检查前向差异函数:

FDF:n ^ 2

意,在第一迭代应匹配长度的串1,长度的第二字符串3,长度的第三串5等等,并且通常,每个迭代应比以前的匹配的字符串两个较长。相应的正则表达式几乎直接来自此语句:

^(^x|\1xx)*$

可以看出,第一次迭代将只匹配一个x,而每个后续迭代将匹配一个比前一个字符串长两倍的字符串,这完全符合指定。这也意味着在perl中一个非常短的完美平方测试:

(1x$_)=~/^(^1|11\1)*$/

该正则表达式可以进一步推广为匹配任何n个角长度:

三角数:
^(^x|\1x{1})*$

平方数:
^(^x|\1x{2})*$

五角形数:
^(^x|\1x{3})*$

六角数:
^(^x|\1x{4})*$

等等


f(n)= n 3

转到n 3,再次检查前向差分函数:

FDF:n ^ 3

如何实现这一点可能尚不明显,因此我们还要检查第二个差异函数:

FDF ^ 2:n ^ 3

因此,前向差分函数不会增加一个常数,而是一个线性值。D f 2的初始(“ -1 th”)值为零很好,这样可以节省第二次迭代的初始化时间。生成的正则表达式如下:

^((^|\2x{6})(^x|\1))*$

与之前一样,第一次迭代将匹配1,第二次迭代将匹配更长的字符串67),第三次将匹配更长的字符串1219),依此类推。


f(n)= n 4

n 4的前向差分函数:

FDF:n ^ 4

第二个前向差函数:

FDF ^ 2:n ^ 4

第三个正向差异函数:

FDF ^ 3:n ^ 4

现在很丑。D f 2D f 3的初始值分别均为非零,212,这需要加以考虑。您现在可能已经知道正则表达式将遵循以下模式:

^((^|\2\3{b})(^|\3x{a})(^x|\1))*$

因为D f 3在第二次迭代中必须匹配12的长度,所以a一定12。但是由于每项增加24,因此下一个更深层的嵌套必须使用其先前的值两次,这意味着b = 2。最后要做的是初始化D f 2。由于D f 2直接影响D f,这最终就是我们要匹配的D f,因此在这种情况下,可以通过将适当的原子直接插入正则表达式来初始化其值(^|xx)。最终的正则表达式将变为:

^((^|xx)(^|\3\4{2})(^|\4x{12})(^x|\1))*$

高阶

五阶多项式可以与以下正则表达式匹配:
^((^|\2\3{c})(^|\3\4{b})(^|\4x{a})(^x|\1))*$

f(n)= n 5是一个相当容易的练习,因为第二个和第四个正向差分函数的初始值为零:

^((^|\2\3)(^|\3\4{4})(^|\4x{30})(^x|\1))*$

对于六阶多项式:
^((^|\2\3{d})(^|\3\4{c})(^|\4\5{b})(^|\5x{a})(^x|\1))*$

对于七阶多项式:
^((^|\2\3{e})(^|\3\4{d})(^|\4\5{c})(^|\5\6{b})(^|\6x{a})(^x|\1))*$

等等

注意,如果任何必要的系数不是整数,则并非所有多项式都可以完全按照这种方式进行匹配。例如,n 6要求a = 60b = 8c = 3/2。在这种情况下,可以解决此问题:

^((^|xx)(^|\3\6\7{2})(^|\4\5)(^|\5\6{2})(^|\6\7{6})(^|\7x{60})(^x|\1))*$

在这里,我将b更改为6,将c更改为2,它们的乘积与上述值相同。重要的是,乘积不要改变,因为a·b·c·…控制着常数差函数,对于六阶多项式,常数差函数为D f 6。存在两个初始化原子:一个用于将D f初始化为2(与n 4一样),另一个将第五个差分函数初始化为360,同时将b中缺少的两个相加。


您在哪个引擎上对此进行了测试?
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 2014年

我终于明白发生了什么事。确实,唯一需要的就是支持前向引用。+1
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳14年

@nhahtdh啊,你是对的。前向引用也不一定是通用功能。
primo 2014年

1
优秀!我喜欢这是多么简短,简单和容易理解。凭借其浅层嵌套,可以轻松地手动计算其行为。而且,它的速度与Volatilitynhahtdh的解决方案一样快。我喜欢您的详细说明,包括证明它甚至可以扩展到多项式的示例。如果可以的话,我会给加分。
Deadcode

@琳恩谢谢!没想到...
primo

13

这是一个不使用条件,前向声明或嵌套的反向引用,后向,平衡组或正则表达式递归的解决方案。它仅使用广泛支持的前瞻性和标准反向引用。由于使用了ECMAScript regex引擎的Regex Golf,我受到了在这些限制下运行的启发。

这个50字节的正则表达式的工作原理在概念上相当简单,并且与所有其他为此难题提交的解决方案完全不同。令人惊讶地发现这种数学逻辑可以在正则表达式中表达。

      \2                     \4  \5
^((?=(xx+?)\2+$)((?=\2+$)(?=(x+)(\4+)$)\5){4})*x?$

(捕获组在正则表达式上方标记)

只需将4in 替换{4}为所需的幂,即可将正则表达式推广为任何幂。

在线尝试!

通过重复除以当前值可除的质数的最小四次幂来工作。由于每一步的商总是四次方,如果原始值是四次方。最终商为1表示原始值确实是四次幂。这样就完成了比赛。零也被匹配。

首先,它使用一个惰性捕获组\2来捕获大于1的数字的最小因子。因此,可以确保此因子为素数。例如,使用1296(6 ^ 4),它将最初捕获\2= 2。

然后,在被重复4次循环的开始,它测试看是否当前数目整除\2,用(?=\2+$)。第一次通过此循环,该测试没有用,但是稍后它的目的将变得明显。

接下来,在此内部循环中,它使用贪婪捕获组\4捕获数量小于其自身的最大因子:(?=(x+)(\4+)$)。实际上,这会将数字除以最小的质因子\2;例如,最初将1296捕获为\4= 1296/2 =648。请注意,当前数的除法\2是隐式的。尽管可以将当前数字除以捕获组中包含的数字(我在发布此答案后才发现四天),但是这样做会使正则表达式的运行速度变慢且难以理解。必要的,因为大于1的数字的最小因子将始终与小于其自身的最大因子相匹配(这样它们的乘积就等于数字本身)。

由于这种正则表达式只能通过将结果保留在字符串末尾来“吃掉”字符串(使其变小),因此我们需要将除法结果“移至”字符串末尾。这是通过将减法结果(当前数字减\4)捕获到捕获组中\5,然后在前瞻之外将与对应的当前数字开头的一部分进行匹配来完成的\5。这将剩下的未处理字符串留\4在长度匹配的末尾。

现在,它循环回到内部循环的开始,在那里很明显为什么要用质因数检验除数。我们只是除以数字的最小素数;如果该数字仍可被该因数整除,则意味着原始数字可被该因数的四次幂整除。第一次执行此测试是没有用的,但是接下来的3次,它将确定隐式除以的结果\2是否仍可被整除\2。如果\2在循环的每次迭代开始时仍可将其整除,则证明每次迭代将数字除以\2

在我们的示例中,输入为1296,将如下循环:

\2= 2
\4= 1296/2 = 648
\4= 648/2 = 324
\4= 324/2 = 162
\4= 162/2 = 81

现在,正则表达式可以循环回到第一步;这就是决赛的目的*。在此示例中,81将成为新数字;下一个循环将如下所示:

\2= 3
\4= 81/3 = 27
\4= 27/3 = 9
\4= 9/3 = 3
\4= 3/3 = 1

现在,它将以新的数字1再次循环回到第一步。

数字1不能除以任何质数,这将使其与不匹配(?=(xx+?)\2+$),因此它退出顶级循环(*结尾处为)。现在到达x?$。这只能匹配零或一。当且仅当原始数字是完美的四次方时,当前数字才为0或1;如果此时为0,则表示顶级循环从不匹配任何内容;如果为1,则表示顶级循环将完美的四次幂除,直到不再被任何东西整除为止(或首先是1,表示顶层循环从未匹配任何内容)。

也可以通过执行重复的显式除法(也适用于所有幂-将所需幂减一,替换为{3})来解决,以49个字节为单位来解决此问题,但是这种方法要慢得多,并且要解释使用的算法不在此答案的范围内:

^((x+)((\2(x+))(?=(\4*)\2*$)\4*(?=\5$\6)){3})?x?$

在线尝试!


从我的测试(最大长度为1024)看来,这是正确的。但是,正则表达式太慢-仅花费很长时间才能匹配长度16 ^ 4,因此很难验证较大的数字。但是由于性能不是必需的,所以我在理解您的正则表达式时会表示敬意。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 2014年

1
您的正则表达式和波动率都很棒。他们的速度和简洁让我赞叹不已,他们两个在我的i7-2600k上在7.5秒内匹配了100000000,比我预期的正则表达式快得多。我在这里的解决方案是完全不同的数量级,因为匹配50625需要12秒。但是我的目标不是速度,而是使用一组非常有限的操作以最小的代码长度完成这项工作。
Deadcode,2014年

我们的答案很快,因为他们几乎没有做任何回溯。您在中做了很多回溯((((x+)\5+)\4+)\3+)\2+$。您自己的方式也很神奇,因为在没有前向声明的反向引用的情况下,我什至无法想到如何匹配平方数。
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ 2014年

顺便说一句,这个问题不是代码问题,而是一个难题。我不根据代码长度来判断解决方案。
n̴̖̋h̷͉̃a̷̭̿h̴̖̋ẗ̵̨́d̷̰̀ĥ̷̳

哦。那解释了您为什么使用(?:)。因此,我应该编辑我的答案以使优化版本成为主要版本吗?
Deadcode

8

^(?:(?=(^|(?<=^x)x|xx\1))(?=(^|\1\2))(^x|\3\2{12}xx))*$

此正则表达式与Java,Perl,PCRE和.NET风格兼容。此正则表达式使用了很多功能:超前,后向和前向声明的反向引用。前向声明的后向引用种类将此正则表达式的兼容性限制到了一些引擎。

说明

该解决方案利用以下推导。

通过完全扩展求和,我们可以证明以下等式:

\ sum \ limits_ {i = 1} ^ n(i + 1)^ 4-\ sum \ limits_ {i = 1} ^ ni ^ 4 =(n + 1)^ 4-1
\ sum \ limits_ {i = 1} ^ ni ^ 4-\ sum \ limits_ {i = 1} ^ n(i-1)^ 4 = n ^ 4

让我们在左侧合并求和:

\ sum \ limits_ {i = 1} ^ n(4(i + 1)^ 3-6(i + 1)^ 2 + 4(i + 1)-1)=(n + 1)^ 4-1
\ sum \ limits_ {i = 1} ^ n(4i ^ 3-6i ^ 2 + 4i-1)= n ^ 4

减去2个方程式(顶部方程式减去底部方程式),然后在左侧合并总和,然后对其进行简化:

\ sum \ limits_ {i = 1} ^ n(12i ^ 2 + 2)=(n + 1)^ 4-n ^ 4-1

我们获得连续四次幂之间的差作为幂和:

(n + 1)^ 4-n ^ 4 = \ sum \ limits_ {i = 1} ^ n(12i ^ 2 + 2)+ 1

这意味着连续的第四次幂之间的差异增加(12n 2 + 2)。

为了更容易思考,请参考波动率答案中差异树

  • 最终方程的右侧是差异树中的第二行。
  • 增量(12n 2 + 2)是差异树中的第三行。

足够的数学。返回上面的解决方案:

  • 如公式中所示,第一个捕获组维护一系列奇数以计算i 2

    确切地说,随着循环的迭代,第一个捕获组的长度将为0(未使用),1、3、5、7,...。

    (?<=^x)x设置奇数序列的初始值。的^是就在那里,允许先行在第一次迭代中得到满足。

    xx\1 加2并前进到下一个奇数。

  • 第二捕获组为i 2维护平方数序列。

    准确地说,随着循环的迭代,第二个捕获组的长度将为0、1、4、9,...。

    ^in (^|\1\2)设置平方数系列的初始值。并且\1\2增加了奇数当前的平方数,以它前进到下一个平方数。

  • 第三个捕获组(在任何超前且实际消耗文本的范围之外)与我们上面导出的方程式的整个右侧匹配。

    ^xin (^x|\3\2{12}xx)设置初始值,该初始值+ 1位于等式的右侧。

    \3\2{12}xx使用捕获组2中的n 2来增加差异(12n 2 + 2),并同时匹配差异。

由于每次迭代中匹配的文本量大于或等于执行预构造n 2所需的文本量,因此这种安排是可能的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.