12

相关问题/材料:

众所周知,各种编程语言支持的“正则表达式”生成的语言在形式上是非常规的,并且如上述材料所示,能够识别至少一些上下文敏感的语言。

语言 L = {x | x 是以 10 为底的素数} 是一种上下文相关的语言,因为素性可以通过线性有界自动机测试(但它不是通过泵引理参数的上下文无关语言)。

那么,是否可以编写一个 Perl 或 Java 正则表达式来精确接受所有以 10 为底的素数?如果感觉更容易,请随意替换任何其他基数≥ 2 或准确识别所有合数。

例如,使用转义符运行任意 Perl 代码被视为作弊。重复替换(这很容易图灵完备)也超出了范围;整个工作应该在正则表达式中完成。这个问题更多的是关于正则表达式实际上有多强大的界限。

4

1 回答 1

4

注意:这些正则表达式是为 PHP 编写的,并使用所有格量词,这些量词在许多但不是所有语言中都使用过,例如 java-script 不支持它们。这也是非常低效的,很快就会变得不可行。

编辑:这里是基数 10\b(((\d)(?=[\d\s]*(\4{0,10}(n(?=.*n\3)|nn(?=.*1\3)|n{3}(?=.*2\3)|n{4}(?=.*3\3)|n{5}(?=.*4\3)|n{6}(?=.*5\3)|n{7}(?=.*6\3)|n{8}(?=.*7\3)|n{9}(?=.*8\3))?)))+)(?![\d\s]*(n(?=\4))++(..?1|(...*)\8+1))我在此之后使用了基数 2 以使事情变得更容易。

编辑:这将允许您传入一个包含多个二进制数的字符串并匹配那些是素数的\b(((\d)(?=[\d\s]*(\4{0,2}n(?=.*\3)|\4{0,2})))+)(?![\d\s]*(n(?=\4))++(..?1|(...*)\7+1)) 它基本上是通过使用边界 \b 而不是字符串 ^ 的开头来做到这一点的,它在前进时允许任意数量的小数和空格ns 并将测试基数为 1 表示的整个部分包装在负前瞻中。除此之外,它的工作方式与下面的相同。作为一个例子1111 1011 nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn1 将匹配1011

我设法得到了一些我认为有效的东西(检查到 25)并且它匹配非素数。这里是基数 2(更容易解释)^((\d)(?= \d*\s(\3{0,2}n(?=.*\2)|\3{0,2})))+\s(n(?=\3))*+\K(..?1|(..+?)\6+1),它可以扩展到基数 n,但这会非常快速地扩展正则表达式。为了让这个正则表达式工作,我需要一些额外的条件(有点hacky),输入字符串必须是数字,后跟一个空格,后跟至少与你的数字值一样多的n个字符(如果你有数字10 你需要至少 10 ns 之后)后跟你的基数,以便不包括你的 0 数字(例如,对于基数 10 123456789),不包括你的 0。例如11 nnnnnnnnnnnnnn1. 这是因为正则表达式没有可访问的存储,所以我需要使用捕获组来执行此操作。最后,这个正则表达式使用 /x 来忽略表达式中的空格,如果你不想使用它,去掉所有的空格。

我现在将分 3 个步骤解释它是如何工作的。此正则表达式分为 3 个部分:

第 1 部分:这部分将基数 n > 1 更改为基数 1 作为 ns 的捕获组

这是它与问题中的示例^((\d)(?= \d*\s(\3{0,2}n(?=.*\2)|\3{0,2})))+非常相似的部分。a^nb^n前面的 ^ 表示完整的匹配必须从头开始,这对后面很重要。这段代码的主要结构是^((\d)(?= \d*\s (suff)))+这需要开始和第一个空格之间的每个小数,并使用 (\d)(?=) 执行正向预读,其中 \d 是小数, (?=) 是预读\d 在捕获组()中供以后使用。这是我们目前正在查看的数字。

前瞻的内部实际上并不是检查前面的条件,而是建立一个捕获组,代表我们以 1 为底的数字。捕获组的内部看起来像这样

\d*\s(\3{0,2}n(?=.*\2)|\3{0,2})) 

\d*\s 部分基本上将我们正在查看的字符移动到其余的数字 \d* (\d 是数字,* 是 0 到 n 尽可能多次)这现在让我们看开头的ns。

(\3{0,2}n(?=.*\2)|\3{0,2}))

是一个自引用捕获组,这里需要您放在最后的数字。该组匹配自身 0 到 2 次,但尽可能多地匹配(使用 \3{0,2} 和 \3 含义处理组 3 和 {0,2} 表示从 0 到 2 次匹配)这意味着如果在当前数字之前有一个数字,则它的基数 1 表示乘以 2。这对于基数 10 是 10,对于基数 16 是 16 . 如果这是第一个数字,则组将未定义,因此它将匹配 0 次。然后它根据匹配我们当前正在处理的数字(使用其捕获组)添加单个 n 或不添加 n。它通过使用积极的前瞻性来查看我们放置数字的输入的末尾,n(?=.*\2) 如果它可以找到我们正在处理的数字后面的任何内容,则匹配 n。这使它能够识别我们此时正在处理的数字。(我会在后面使用,但它们是固定长度的)如果您的底数为 3,并且想检查您当前正在处理的数字是否为 2,您将使用 nn(?=.*1\2) 这将匹配 nn仅当数字为两位时。我们使用了一个或运算符'|' 对于所有这些,如果没有找到数字,我们假设它是 0 并且不添加任何 ns。由于这是在捕获组中,因此此匹配项将保存在组中。

总结这一部分,我们所做的是将每个数字向前看,取前一个数字的基数 1 表示(保存在捕获组中)并将其乘以基数,然后匹配它,然后将它的基数表示数字并将其保存在组中。如果您依次对每个数字执行此操作,您将获得该数字的基数表示。让我们看看例子。第101章

首先它会因为 ^ 而进入坐席。所以:101 nnnnnnnnnnnnnnnn1

然后它转到第一个数字并将其保存在捕获摸索中 1 01 nnnnnnnnnnnnnnnnn1

组 2 : 1

它使用 \d*\s 使用前瞻来遍历所有数字和第一个空格。1 01 n nnnnnnnnnnnnnnn1

它现在在捕获组 3 内

它取这个caputing group的前一个值,匹配0到2次

由于它是未定义的,它匹配 0 次。

它现在再次向前看,试图找到一个与捕获组 2 1 01 n nnnnnnnnnnnnnnnn中的数字匹配的数字1

发现它与捕获组 3 2 1 01 nn nnnnnnnnnnnnnnn1中的 1 n 匹配

它现在离开组 3,更新其值并离开前瞻组 3 = n

它现在查看下一个数字并将其保存在捕获组中 1 0 1 nnnnnnnnnnnnnnnnn1

第 2 组 = 0

第 3 组 = n

然后它还使用前瞻并转到第一个 n 1 0 1 n nnnnnnnnnnnnnnnn1

然后它匹配第 3 组 0 到 2 次,但尽可能多,所以 n 1 0 1 nn nnnnnnnnnnnnnnn1

然后它使用前瞻来尝试匹配它可以这样做的第 2 组中的数字,因此它在返回第 3 组和前瞻之前不添加 ns

组 3 = nn

它现在查看下一个数字并将其保存在第 2 组 10 1 nnnnnnnnnnnnnnnnn1 使用前瞻,它转到 ns 的开头并匹配 2 次第 3 组 10 1 nnnn nnnnnnnnnnnnn1 然后使用前瞻来尝试匹配它发现第 2 组中的数字与 an 匹配并从第 3 组和前瞻中返回。group3 = nnnnn 组 3 现在包含我们数字的基数 1 表示。

第 2 部分将 ns 减少到以 1 为基数的数字表示的大小

\s(n(?=\3))*+\K

只要您可以匹配前面的第 3 组(您的号码的基数表示),这将匹配空格,然后匹配 ns。它通过使用所有格的 *+ 尽可能多次匹配 n 来做到这一点(它永远不会放弃匹配,这是为了阻止匹配在以后缩小以使匹配工作) n 具有积极的前瞻 n (?=\3) 这意味着只要前面有一个组 3,n 就会匹配(\3 给出捕获组 3)。这给我们留下了以 1 为基数的表示形式,而数字是唯一不匹配的东西。然后我们 \K 说从这里重新开始匹配。

Part3 我们现在使用问题中提到的相同算法来获取素数,除了我们强制它在基表示的开始和数字的开始之间不匹配。您可以在此处阅读其工作原理如何使用正则表达式确定数字是否为素数?

最后要把它变成一个基本的正则表达式,你必须做一些事情

 ^((\d)(?= \d*\s(\3{0,2}n(?=.*\2)|\3{0,2})))+\s(n(?=\3))*+\K(..?1|(..+?)\6+1)

拳头在输入字符串的末尾添加更多数字,然后更改 n

?=.*\2 to  n?=.*n\2 |  n?=.*1\2   n?=.*3\2 ..,  n?=.***n**\2

最后将 \3{0,2} 更改为 \3{0, n }。其中n是基数。还要记住,如果没有正确的输入字符串,这将不起作用。

于 2016-11-07T14:25:13.327 回答