【问题标题】:Arcane isPrime method in JavaJava中的奥术isPrime方法
【发布时间】:2012-09-24 19:19:31
【问题描述】:

考虑以下方法:

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

我从来都不是正则表达式专家,所以谁能完整地解释一下这种方法实际上是如何工作的? 此外,与其他可能的确定整数是否为素数的方法相比,它是否有效?

【问题讨论】:

  • 好的,我收回我的评论。真的很奇怪。
  • @NullUserException 哦,谢谢你的链接——我想我会看看是否有人能回答我问题的第二部分。
  • @A.R.S.我非常怀疑它是否有效。
  • @Foon - 它实现了 Eratosthenes 的筛子。它详尽地检查每个除数。 (例如,如果 2 失败,它仍然会尝试 4。)

标签: java regex primes


【解决方案1】:

首先,请注意,此正则表达式适用于一元计数系统中表示的数字,即

1       is 1
11      is 2
111     is 3
1111    is 4
11111   is 5
111111  is 6
1111111 is 7

等等。真的,可以使用任何字符(因此表达式中的.s),但我将使用“1”。

其次,注意这个正则表达式匹配复合(非素数)数字;因此否定检测素数。


说明:

表达式的前半部分,

.?

表示字符串 "" (0) 和 "1" (1) 是匹配的,即不是素数(根据定义,though arguable.)

后半段,用简单的英文说:

匹配长度至少为 2 的最短字符串,例如“11”(2)。现在,看看我们是否可以通过重复来匹配整个字符串。 “1111”(4)匹配吗? “111111”(6) 匹配吗? “11111111”(8) 匹配吗?等等。如果不是,则再次尝试下一个最短的字符串“111”(3)。等等。

您现在可以看到,如果原始字符串不能与其子字符串的 多个 匹配,那么根据定义,它是素数!

顺便说一句,非贪心运算符? 是使“算法”从最短开始并向上计数的原因。


效率:

这很有趣,但肯定不是有效的,通过各种论点,我将在下面整合其中的一些:

  • 正如@TeddHopp 所指出的,众所周知的埃拉托色尼筛法不会费心检查整数的倍数,例如 4、6 和 9,在检查 2 的倍数和3. 唉,这种正则表达式方法会彻底检查每个较小的整数。

  • 正如@PetarMinchev 所说,一旦我们达到数字的平方根,我们就可以“短路”多重检查方案。我们应该能够做到,因为大于平方根的因子必须与小于平方根的因子配对(否则两个大于平方根的因子会产生乘积大于数字),如果存在这个更大的因素,那么我们应该已经遇到(并因此匹配)了较小的因素。

  • 正如@Jesper 和@Brian 简明扼要地指出,从非算法的角度来看,考虑正则表达式如何从分配内存来存储字符串开始例如 char[9000] 为 9000。嗯,这很容易,不是吗? ;)

  • 正如@Foon 所指出的,存在概率方法可能对更大的数字更有效,尽管它们可能并不总是正确的(改为使用伪素数)。但也有确定性测试 100% 准确,并且比基于筛分的方法更有效。 Wolfram's 有一个很好的总结。

【讨论】:

  • +1 表示一元解释,但它仍然没有真正解决效率问题。
  • @PetarMinchev - 啊,我看到你提到了同样的sqrt 策略,但我发誓我写我的时候没有看到你的解决方案。我在纽约市的地铁上玩了一个游戏,在我到达目的地之前,我试图确定我所乘坐的购物车的 4 位数字是否是素数,并遇到了这个提示。当我的每日投票限制重置时,我会为你 +1 :)
  • @Brian 很容易看出这是多么低效。它已经从分配数组char[n] 开始。如果n 很大,这将分配一个巨大的数组。所以这是一个巧妙但不切实际的把戏。
  • @Jesper 没错。如果测试值为 9,000,它会生成一个 9,000 个字符长的字符串,然后 会尝试对其运行正则表达式。这些技巧有时让我发疯。是的,它看起来很酷,但无论如何都不是一个好的解决方案。
  • @Brian - 想象一下,如果我们 超过 9,000 会发生什么?!?!
【解决方案2】:

素数的一元特征以及它为什么起作用已经被讨论过了。所以这里有一个使用传统方法和这种方法的测试:

public class Main {

    public static void main(String[] args) {
        long time = System.nanoTime();
        for (int i = 2; i < 10000; i++) {
            isPrimeOld(i);
        }
        time = System.nanoTime() - time;
        System.out.println(time + " ns (" + time / 1000000 + " ms)");
        time = System.nanoTime();
        for (int i = 2; i < 10000; i++) {
            isPrimeRegex(i);
        }
        time = System.nanoTime() - time;
        System.out.println(time + " ns (" + time / 1000000 + " ms)");
        System.out.println("Done");
    }

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

    public static boolean isPrimeOld(int n) {
        if (n == 2)
            return true;
        if (n < 2)
            return false;
        if ((n & 1) == 0)
            return false;
        int limit = (int) Math.round(Math.sqrt(n));
        for (int i = 3; i <= limit; i += 2) {
            if (n % i == 0)
                return false;
        }
        return true;
    }
}

这个测试从 2 开始计算数字是否是素数,直到 9,999。下面是它在相对强大的服务器上的输出:

8537795 ns (8 ms)
30842526146 ns (30842 ms)
Done

因此,一旦数字变得足够大,它的效率就会非常低。 (对于高达 999,正则表达式的运行时间约为 400 毫秒。)对于小数字,它很快,但以传统方式生成高达 9,999 的素数仍然比以旧方式生成高达 99 的素数更快( 23 毫秒)。

【讨论】:

  • 请注意:这不包括概率素数,因为概率素数通常用于非常大的数字。这个算法在高达 9,999 的数字上几乎不能很好地运行,更不用说几十位数的数字了,所以我把它排除在分析之外。
  • 吹毛求疵,但如果 n2,则代码不起作用。 if ((n &amp; 1) == 0) 将在转到 n == 2 检查之前返回 false
  • @PetarMinchev 是的,我看到了,谢谢 :) 另外,你错过了我的 i++ 而不是 i += 2 ;)
【解决方案3】:

这不是检查数字是否为素数的真正有效方法(它检查每个除数)。

一种有效的方法是检查直到sqrt(number) 的除数。这是如果你想确定一个数字是否是素数。否则会有更快的概率素性检查,但不是 100% 正确。

【讨论】:

  • 取决于你是想确定还是只是相当有信心......概率素性检查对于大#的效率更高
猜你喜欢
  • 2010-09-13
  • 2020-10-31
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 1970-01-01
  • 2011-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多