如何确定一个数字是不是是正则表达式的素数?

Posted

技术标签:

【中文标题】如何确定一个数字是不是是正则表达式的素数?【英文标题】:How to determine if a number is a prime with regex?如何确定一个数字是否是正则表达式的素数? 【发布时间】:2011-02-17 05:07:19 【问题描述】:

我在RosettaCode 上找到了以下 Java 代码示例:

public static boolean prime(int n) 
  return !new String(new char[n]).matches(".?|(..+?)\\1+");

我并不特别了解 Java,但了解这个 sn-p 的所有方面,除了正则表达式本身 我对 Regex 具有基本到基础的高级知识,您可以在 php 内置函数中找到它

.?|(..+?)\\1+ 如何匹配质数?

【问题讨论】:

@Amir Rachum:!new String(new char[n]).matches(".?|(..+?)\\1+") 等同于 !((new String(new char[n])).matches(".?|(..+?)\\1+")) 这不仅在计算上很昂贵,而且还可能对内存造成毁灭性的消耗。如果有人选择使用这种方法,我建议不要使用这种方法,因为寻找素数的算法是如此简单(为什么在世界上使它复杂化并使其如此浪费),应在“new char [n ]" 以确保它低于合理的阈值。例如。调用“prime(Integer.MAX_VALUE)”,然后在抛出 OutOfMemoryError 时提交错误。 @nicerobot:轻点? @nicerobot:实际上,我收回了这一点。我最初认为这个问题的学术性质意味着它仅用于学习目的,而且你是一个令人讨厌的笨蛋。然而,转念一想,情况并非如此。问题中从未提及甚至暗示正则表达式仅用于学习目的。事实上,我对它的第一印象是,就代码 sn-ps 而言,它看起来非常简单,所以初学者可能确实认为它可以在实践中使用。 +1。 @incrediman 不用担心。我知道你会怎么想。我只是想警告使用它的后果,而不是阻止学习它的工作原理。一个简单的“请不要部署它”。在我的其他评论之前,从您最初的角度来看,这可能会让它听起来不那么居高临下。 【参考方案1】:

您说您了解这部分,但只是强调一下,生成的字符串的长度等于提供的数字。所以字符串有三个字符当且仅当n == 3

.?

正则表达式的第一部分说,“任何字符,零次或一次”。所以基本上,有零个或一个字符 - 或者,根据我上面提到的,n == 0 || n == 1。如果我们有匹配,则返回否定。这与零和一不是素数的事实相对应。

(..+?)\\1+

正则表达式的第二部分有点棘手,它依赖于组和反向引用。组是括号中的任何内容,然后将由正则表达式引擎捕获并存储以供以后使用。反向引用是稍后在同一正则表达式中使用的匹配组。

该组捕获 1 个字符,然后捕获 1 个或多个任意字符。 (+ 字符表示一个或多个,但仅表示前一个字符或组。所以这不是“两个或四个或六个等字符”,而是“两个或三个等”。+?就像 +,但是它尝试匹配尽可能少的字符。+ 通常会尽可能地尝试吞噬整个字符串,这在这种情况下很糟糕,因为它会阻止反向引用部分工作。)

下一部分是反向引用:同一组字符(两个或更多)再次出现。所述反向引用出现一次或多次。

所以。捕获的组对应于捕获的自然数量的字符(从 2 个开始)。然后,所述组出现一些自然次数(也从 2 次开始)。如果存在匹配项,则这意味着可以找到两个大于或等于 2 的数字的乘积与 n 长度的字符串匹配……这意味着您有一个复合 n。因此,再次返回成功匹配的否定:n 不是素数。

如果找不到匹配项,那么您将无法得出两个大于或等于 2 的自然数的乘积...并且您同时拥有不匹配项和质数,因此再次返回匹配结果的否定。

你现在看到了吗?这是令人难以置信的棘手(而且计算成本很高!)但同时它也很简单,一旦你得到它。 :-)

如果您还有其他问题,例如正则表达式解析的实际工作原理,我可以详细说明。但我现在试图让这个答案保持简单(或者尽可能简单)。

【讨论】:

我在 chrome 开发控制台中用 JS 尝试了这个逻辑。在网页上。并且刚刚通过了 5 来检查。页面崩溃了! 下面的评论给出了更好的解释。请在继续之前阅读它! “更好”是主观的——我想说它从不同的角度解决问题,是对这个答案的一个很好的补充。 :-) 我实际上写了一篇博客文章,更详细地解释了这一点:Demystifying The Regular Expression That Checks If A Number Is Prime.【参考方案2】:
/^1?$|^(11+?)\1+$/

在转换为基数 1(1=1, 2=11, 3=111, ...)后应用于数字。非素数将匹配此。如果不匹配,则为素数。

解释here。

【讨论】:

【参考方案3】:

我将解释素数测试之外的正则表达式部分:下面的正则表达式,给定一个包含重复 String tString s,找到 t

    System.out.println(
        "MamamiaMamamiaMamamia".replaceAll("^(.*)\\1+$", "$1")
    ); // prints "Mamamia"

它的工作方式是正则表达式将(.*) 捕获到\1 中,然后查看是否有\1+ 跟随它。使用^$ 可确保匹配必须是整个字符串。

所以,在某种程度上,我们得到了String s,它是String t 的“倍数”,正则表达式会找到这样的t(可能是最长的,因为\1 是贪婪的)。

一旦你理解了这个正则表达式为什么起作用,那么(暂时忽略 OP 正则表达式中的第一个替代)解释它是如何用于素数测试的就很简单了。

要测试n的素数,首先生成一个长度为nString(填充相同的char) 正则表达式将某个长度的String(比如k)捕获到\1,并尝试将\1+String的其余部分相匹配 如果匹配,则nk 的真倍数,因此n 不是素数。 如果没有匹配项,则不存在这样的k 来划分n,因此n 是质数

.?|(..+?)\1+ 如何匹配质数?

其实不然!它的长度不是素数的matchesString

.? :交替的第一部分匹配长度为 01String(根据定义不是素数) (..+?)\1+ :交替的第二部分,上面解释的正则表达式的变体,匹配长度为nString,它是长度为k >= 2String 的“倍数”(即n 是复合,而不是素数)。 请注意,不情愿的修饰符 ? 实际上并不是正确性所必需的,但通过先尝试较小的 k 可能有助于加快处理速度

注意return 语句中的! boolean 补码运算符:它否定了matches。当正则表达式 DOESN'T 匹配时,n 是素数!这是一个双重否定的逻辑,难怪它有点令人困惑!


简化

下面是对代码的简单重写,使其更具可读性:

public static boolean isPrime(int n) 
    String lengthN = new String(new char[n]);
    boolean isNotPrimeN = lengthN.matches(".?|(..+?)\\1+");
    return !isNotPrimeN;

上述代码与原始 Java 代码基本相同,但分解为多个语句,并分配给局部变量以使逻辑更易于理解。

我们还可以使用有限重复来简化正则表达式,如下所示:

boolean isNotPrimeN = lengthN.matches(".0,1|(.2,)\\1+");

同样,给定一个长度为nString,填充相同的char

.0,1 检查 n = 0,1 是否为素数 (.2,)\1+ 检查 n 是否是 k >= 2 的正确倍数,而不是素数

除了\1 上的不情愿修饰符?(为清楚起见省略)之外,上述正则表达式与原始表达式相同。


更有趣的正则表达式

以下正则表达式使用类似的技术;它应该具有教育意义:

System.out.println(
    "OhMyGod=MyMyMyOhGodOhGodOhGod"
        .replaceAll("^(.+)(.+)(.+)=(\\1|\\2|\\3)+$", "$1! $2! $3!")
); // prints "Oh! My! God!"

另见

Regular expressions: who's greedier

【讨论】:

+1:我认为您的方法可能比我的方法更好。不知道为什么我得到了这么多的赞成票或复选标记……我认为你应该得到更多。 :-( 对不起 @Platinum:哇,我从没想过你会公开这么说!感谢您的支持。也许有一天我会收到[Populist] 嗯,这只是事实(据我所知)......真的没什么大不了的。我不是为了代表而来(尽管这总是一种奖励和惊喜)......我来这里是为了尽可能回答问题。因此,当有人在某个特定问题上做得比我做得更好时,我可以承认这一点也就不足为奇了。【参考方案4】:

不错的正则表达式技巧(虽然效率很低)... :)

正则表达式定义非素数如下:

N 不是素数当且仅当 N1 整除。

不是将 N 的简单数字表示传递给正则表达式引擎,而是输入一个 length N 的序列,由一个重复字符组成。析取的第一部分检查 N=0 或 N=1,第二部分使用反向引用查找除数 K>1。它迫使正则表达式引擎找到一些可以重复至少两次以形成序列的非空子序列。如果存在这样的子序列,则意味着它的长度除以N,因此N不是素数。

【讨论】:

奇怪的是,即使在反复阅读其他更长和更技术性的解释之后,我发现 this 解释是让它在我脑海中“点击”的解释。跨度>

以上是关于如何确定一个数字是不是是正则表达式的素数?的主要内容,如果未能解决你的问题,请参考以下文章

如何测试以确定浏览器是不是支持 JS 正则表达式lookahead/lookbehind?

数字之间数字的正则表达式

Linux Shell正则表达式如何匹配1~3位数字

如何正则表达式匹配具有固定长度且其中至少有一个数字的字符串

正则表达式 判断是不是是字母和数字

java或者正则表达式如何判断字符串不是相同数字或者字母?不能全是连续数字?(最好是java)