Як визначити, чи число є простим числом з регулярним виразом?


128

Я знайшов такий приклад коду для Java на RosettaCode :

public static boolean prime(int n) {
  return !new String(new char[n]).matches(".?|(..+?)\\1+");
}
  • Я не знаю Java особливо, але розумію всі аспекти цього фрагмента, за винятком самого регулярного виразка
  • У мене є основні базові передові знання Regex, як ви знайдете їх у вбудованих функціях PHP

Як .?|(..+?)\\1+співпадає просте число?


9
@Amir Rachum: !new String(new char[n]).matches(".?|(..+?)\\1+")еквівалентно !((new String(new char[n])).matches(".?|(..+?)\\1+")).
Gumbo

14
Це не тільки обчислювально дорого, але й потенційно руйнівно пам'ять. Якщо хтось вирішить скористатися таким підходом, що я б радив, оскільки алгоритм пошуку прайметів такий простий (чому в світі ускладнюють його і роблять його таким марнотратним), слід перевірити перед "новим знаком [n ] ", щоб переконатися, що вона нижча за розумну межу. Наприклад, виклик "prime (Integer.MAX_VALUE)", а потім подати помилку, коли вона викидає OutOfMemoryError.
nicerobot

28
@nicerobot: Полегшати?
Cam

6
@nicerobot: насправді я повертаю це назад. Я спочатку вважав, що академічний характер цього питання має на увазі його використання лише для навчальних цілей, і що ви були неприємним дурнем. Однак по другій думці це не так; ніколи не згадується і навіть не мається на увазі у питанні, що регулярний вираз призначений лише для навчальних цілей. Насправді моє перше враження від цього полягає в тому, що це дуже просто на вигляд фрагментів коду, так що початківець дійсно може припустити, що його можна використовувати на практиці. +1.
Cam

7
@incrediman не хвилюється. Я бачу, як ти можеш це думати. Був лише мій намір попередити про наслідки використання цього, а не перешкоджати навчанню, як це працює. Просте "Не розгортайте це". до решти мого коментаря, можливо, це зробило б менш поблажливим звучання з вашого початкового погляду.
nicerobot

Відповіді:


120

Ви сказали, що розумієте цю частину, але просто підкреслимо, що створена струна має довжину, рівну кількості, що надається. Отже, рядок має три символи, якщо і лише якщо n == 3.

.?

У першій частині регексу сказано: "будь-який символ, нуль або один раз". Таким чином , в принципі, є нуль або один character-- або, за те , що я вже згадував вище, n == 0 || n == 1. Якщо у нас є збіг, то поверніть заперечення цього. Це відповідає тому, що нуль і один НЕ є простими.

(..+?)\\1+

Друга частина регулярного виразів трохи складніша, спираючись на групи та зворотні посилання. Група - це все, що в дужках, яке потім буде захоплено і збережене механізмом регулярних виразів для подальшого використання. Зворотний зв'язок - це відповідна група, яка буде використана пізніше в тому ж регулярному виразі.

Група охоплює 1 символ, потім 1 або більше будь-яких символів. (Символ + означає один або більше, але ТІЛЬКИ попереднього символу чи групи. Отже, це не "два-чотири-шість тощо символів", а скоріше "два-три тощо". +? Це як +, але він намагається відповідати якомога менше символів. + як правило, намагається обвести весь рядок, якщо це можливо, що погано в цьому випадку, оскільки це заважає роботі задньої референції.)

Наступна частина - це зворотна довідка: той самий набір символів (два або більше), які з’являються знову. Згадані зворотні посилання з'являються один або кілька разів.

Так. Захопленій групі відповідає натуральна кількість символів (від 2 далі), захоплених. Зазначена група потім з'являється деяке природне число разів (також від 2-х років далі). Якщо є відповідність, це означає, що можна знайти добуток з двох чисел, більших або рівних 2, які відповідають n-довжині рядка ... означає, що у вас складений n. Отже, знову поверніть заперечення успішного матчу: n НЕ є простим.

Якщо не можна знайти збігу, ви не можете придумати свій продукт з двох натуральних чисел, більших або рівних 2 ... результату матчу

Ви це бачите зараз? Це неймовірно складно (і обчислювально дорого!), Але це одночасно і просто, як тільки ви це отримаєте. :-)

Я можу пояснити, якщо у вас є додаткові запитання, як-от як насправді працює синтаксичний аналіз. Але я намагаюся зараз відповісти простою (або такою простою, як це може бути).


10
Я спробував цю логіку з JS на консолі хромованого розробника. на веб-сторінці. і щойно пройшов 5 перевірити. Сторінка вийшла з ладу!
Амо Талпалікар

Коментар нижче дає кращі пояснення. Будь ласка, прочитайте його, перш ніж рухатися далі!
Іван Давидов

"Краще" є суб'єктивним - я б сказав, що він підходить до проблеми з іншого кута і є прекрасним доповненням до цієї відповіді. :-)
Platinum Azure

1
Насправді я написав повідомлення в блозі, де пояснював це більш детально: Демістифікація регулярного вираження, яке перевіряє, чи є число основним .
Ілля Герасимчук

73

Я поясню частину регулярних виразів поза тестом на первинність: наступний регулярний вираз, поданий а, String sякий складається з повторення String t, знахідки t.

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

Як це працює в тому , що регулярні вирази захоплює (.*)в\1 , а потім бачить , якщо там \1+після нього. Використання ^та $гарантує, що відповідність має бути з усієї рядка.

Таким чином, ми певним чином даємо String s, що є "кратним" String t, і регулярний вираз знайде таке t(найдовше можливо, оскільки \1жадібний).

Після того, як ви зрозумієте, чому працює цей регулярний вираз, тоді (ігноруючи першу чергу в регексе OP), пояснюючи, як він використовується для тестування первинності, це просто.

  • Щоб перевірити первинність n, спочатку створіть Stringдовжину n(заповнену однаковоюchar )
  • Режекс фіксує Stringдеяку довжину (скажімо k) \1, і намагається відповідати \1+рештіString
    • Якщо є відповідність, то nце правильна кратна величина k, і тому nне є простим.
    • Якщо немає відповідності, то не kіснує такого , що розділяє n, і nтому є простим

Як .?|(..+?)\1+співпадає просте число?

Насправді це не так! Це збігається String , довжина якого НЕ є простим!

  • .?: Перша частина чергування збігів Stringдовжини 0або 1(НЕ простим за визначенням)
  • (..+?)\1+: Друга частина чергування, зміна регулярного вираження, пояснена вище, збігається Stringпо довжині n, "кратній" Stringдовжини k >= 2(тобто nє складовою, а не простою).
    • Зауважте, що модифікатор неохоче ?насправді не потрібен для коректності, але це може допомогти прискорити процес, спробувавши kспочатку менші розміри

Зауважте ! booleanоператор доповнення у returnвиписці: він заперечує значення matches. Це коли регекс НЕ співпадає, 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+");

Знову ж , з огляду на Stringдовжину n, наповнений такою ж 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!"

Дивитися також


6
+1: Я думаю, що ваш підхід, мабуть, кращий, ніж мій. Не маю ідеї, чому я отримав так багато відгуків або галочку ... я цього більше заслуговую. :-( Вибачте
Platinum Azure

@Platinum: Нічого собі, я ніколи не думав, що ти будеш говорити про це публічно! Дякую за підтримку. Можливо, я отримаю [Populist]від цього якийсь день.
полігенмастильні матеріали

2
Ну, це просто правда (як я це сприймаю) ... насправді не величезна справа. Я не тут для представників (хоча це завжди бонус і приємний сюрприз) ... Я тут, щоб спробувати відповісти на питання, коли зможу. Таким чином, не дивно, що я можу визнати, коли хтось зробив це краще, ніж я, у певному питанні.
Platinum Azure

25

Хороший трюк з реджексом (хоча і дуже неефективним) ... :)

Регекс визначає непро-праймери наступним чином:

N не є простим, якщо і лише тоді, коли N <= 1 АБО N ділиться на деякий K> 1.

Замість передачі простого цифрового зображення N двигуну регулярних виразів він подається з послідовністю довжини N, складеною з повторюваного символу. Перша частина диз'юнкції перевіряє N = 0 або N = 1, а друга шукає дільник K> 1, використовуючи зворотні референції. Це змушує двигун регулярного вибору знайти деяку непорожню підпослідовність, яку можна повторити хоча б двічі, щоб сформувати послідовність. Якщо така підряд існує, це означає, що її довжина ділить N, отже, N не є простим.


2
Як не дивно, навіть після того, як неодноразово читав інші триваліші та більш технічні пояснення, я знайшов це пояснення таким, яке змусило його «натиснути» в голові.
Вісім-бітний гуру

2
/^1?$|^(11+?)\1+$/

Застосувати до чисел після перетворення до бази 1 (1 = 1, 2 = 11, 3 = 111, ...). Непримітки будуть відповідати цьому. Якщо це не відповідає, це просто.

Пояснення тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.