Метод Arcane isPrime в Java
рассмотрим следующий способ:
public static boolean isPrime(int n) {
return ! (new String(new char[n])).matches(".?|(..+?)1+");
}
Я никогда не был гуру регулярных выражений, поэтому может ли кто-нибудь полностью объяснить, как этот метод на самом деле работает? далее, эффективен ли он по сравнению с другими возможными методами определения того, является ли целое число простым?
3 ответов
во-первых, обратите внимание, что это регулярное выражение применяется к числам, представленным в унарной системе подсчета, т. е.
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) матчи, т. е. не prime (по определению хотя спорный.)
вторая половина, на простом английском языке, говорит:
соответствует самой короткой строке, длина которой не менее 2, например, " 11 " (2). Теперь посмотрим, сможем ли мы сопоставить всю строку, повторяя ее. Соответствует ли "1111" (4)? Соответствует ли "111111" (6)? Соответствует ли "11111111" (8)? И так далее. Если нет, затем повторите попытку для следующей короткой строки " 111 " (3). Так далее.
теперь вы можете увидеть, как, если исходная строка не может быть сопоставлена как несколько его подстрок, то по определению, это просто!
кстати, не жадный оператор ?
это то, что заставляет "алгоритм" начинать с самого короткого и подсчитывать.
эффективность:
Это интересно, но, конечно, не эффективно, различными аргументы, некоторые из которых я обобщу ниже:
как отмечает @TeddHopp, известный подход сита Эратосфена не будет беспокоиться о проверке кратных целых чисел, таких как 4, 6 и 9, уже "посещенных" при проверке кратных 2 и 3. Увы, этот подход regex исчерпывающе проверяет каждое меньшее целое число.
как отмечает @PetarMinchev, мы можем "закоротить" схему многократной проверки, как только мы достигнем квадратного корня номера. Мы должны быть в состоянии, потому что фактор больше чем квадратный корень должен сотрудничать с фактором меньше чем квадратный корень (так как в противном случае два фактора больше квадратного корня произвели бы произведение больше числа), и если этот больший фактор существует, то мы должны были бы уже столкнуться (и, таким образом, совпали) с меньшим фактором.
как @Jesper и @ Brian примечание с краткостью, из не алгоритмического перспектива, рассмотрим, как регулярное выражение будет начинаться с выделение памяти для хранения строки, например
char[9000]
за 9000. Ну, это было легко, не так ли? ;)как отмечает @Foon, существуют вероятностные методы, которые могут быть более эффективными для больших чисел, хотя они не всегда могут быть правильными (вместо этого появляются псевдопримеси). Но также есть детерминированные тесты, которые на 100% точны и намного эффективнее, чем ситовые методы. компании Wolfram есть хорошее резюме.
унарные характеристики простых чисел и почему это работает уже были охвачены. Итак, вот тест с использованием обычных подходов и этого подхода:
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;
}
}
этот тест вычисляет, является ли число простым до 9999, начиная с 2. И вот его вывод на относительно мощный сервер:
8537795 ns (8 ms)
30842526146 ns (30842 ms)
Done
таким образом, это крайне неэффективно, как только числа становятся достаточно большими. (До 999 регулярное выражение выполняется примерно за 400 мс.) Для небольших чисел это быстро, но по-прежнему быстрее генерировать простые числа до 9,999 обычным способом, чем даже генерировать простые числа до 99 старым способом (23 мс).
Это не очень эффективный способ проверить, является ли число простым (он проверяет каждый делитель).
эффективным способом является проверка делителей до sqrt(number)
. Это если вы хотите быть уверены, что число является простым. В противном случае существуют вероятностные проверки примитивности, которые быстрее, но не на 100% правильны.