Jak określić, czy liczba jest liczbą pierwszą za pomocą wyrażenia regularnego?

Znalazłem następujący przykład kodu dla Javy na RosettaCode :

public static boolean prime(int n) {
  return !new String(new char[n]).matches(".?|(..+?)\\1+");
}
  • nie znam Javy w szczególności, ale rozumiem wszystkie aspekty tego fragmentu z wyjątkiem samego wyrażenia regularnego
  • mam podstawową do podstawowej-zaawansowaną wiedzę o Regex, jak można ją znaleźć we wbudowanych funkcjach PHP

Jak .?|(..+?)\\1+ pasuje do liczb pierwszych?

Author: user2618142, 2010-05-08

4 answers

Powiedziałeś, że rozumiesz tę część, ale dla podkreślenia, wygenerowany ciąg ma długość równą podanej liczbie. Tak więc łańcuch ma trzy znaki wtedy i tylko wtedy, gdy n == 3.

.?

Pierwsza część wyrażenia regularnego mówi: "dowolny znak, zero lub jeden raz". Więc w zasadzie, jest zero lub jeden znak -- lub, zgodnie z tym, co wspomniałem powyżej, n == 0 || n == 1. Jeśli mamy dopasowanie, zwróć negację tego. Odpowiada temu fakt, że zero i Jedynka nie są prime.

(..+?)\\1+

Druga część wyrażenia regularnego jest nieco trudniejsza, opierając się na grupach i zapleczu. Grupa jest cokolwiek w nawiasach, które będą następnie przechwytywane i przechowywane przez silnik regex do późniejszego użycia. Backreference to dopasowana grupa, która jest używana później w tym samym wyrażeniu regularnym.

Grupa przechwytuje 1 znak, a następnie 1 lub więcej dowolnego znaku. (Znak + oznacza jeden lub więcej znaków, ale tylko poprzedniego znaku lub grupy. Więc to nie jest " dwa, cztery, sześć itd. znaków", ale raczej " dwa lub trzy itd."+? jest jak+, ale stara się dopasować jak najmniej znaków. + normalnie próbuje pożreć cały łańcuch, jeśli może, co jest złe w tym przypadku, ponieważ uniemożliwia działanie części backreference.)

Następna część to backreference: ten sam zestaw znaków (dwa lub więcej), pojawiający się ponownie. Wspomniany backreference pojawia się jeden lub więcej razy.

Więc. Przechwycona grupa odpowiada naturalnej liczbie znaków (od 2 dalej). Grupa ta pojawia się wtedy w pewnej naturalnej liczbie razy (również od 2). Jeśli istnieje dopasowanie, oznacza to, że możliwe jest znalezienie iloczynu dwóch liczb większych lub równych 2, które pasują do ciągu n-długości... oznacza to, że masz złożone N. więc ponownie zwróć negację pomyślnego dopasowania: n nie jest liczbą pierwszą.

Jeśli nie można znaleźć dopasowania, to nie można wymyślić iloczynu dwóch liczb naturalnych większych lub równych 2... i masz zarówno non-match i prime, stąd znowu powrót negacji wyniku meczu.

Widzisz to teraz? To niewiarygodnie trudne (i obliczeniowo drogie!), ale jednocześnie jest to dość proste. :-)

Mogę rozwinąć, jeśli masz dalsze pytania, jak na przykład, jak parsowanie regex faktycznie działa. Ale staram się, aby ta odpowiedź była prosta na razie(lub tak prosta, jak to tylko możliwe).

 110
Author: Platinum Azure,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-01 14:46:42

Wyjaśnię część regex poza testowaniem primality: następujące regex, biorąc pod uwagę String s, które składa się z powtarzania String t, znajduje t.

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

Sposób działania polega na tym, że regex przechwytuje (.*) do \1, a następnie widzi, czy \1+ podąża za nim. Użycie ^ i $ zapewnia, że dopasowanie musi być całego ciągu znaków.

Więc w pewnym sensie otrzymujemy String s, która jest "wielokrotnością" String t, a regex znajdzie takie t (najdłuższe możliwe, ponieważ \1 jest chciwy).

Gdy zrozumiesz, dlaczego to wyrażenie regularne działa ,to (ignorując na razie pierwszy wariant w wyrażeniu regularnym op) wyjaśnienie, w jaki sposób jest używane do testowania primality jest proste.

    Aby sprawdzić pierwotność n, najpierw Wygeneruj String o długości n (wypełnioną tym samym char)
  • regex przechwytuje String pewnej długości (powiedzmy k) do \1 i próbuje dopasować \1+ do reszty String
    • jeśli istnieje dopasowanie, to n jest właściwym wielokrotność k, a zatem n nie jest liczbą pierwszą.
    • Jeśli nie ma dopasowania, to nie istnieje taka k, która dzieli n, A n jest zatem liczbą pierwszą

Jak .?|(..+?)\1+ pasuje do liczb pierwszych?

Właściwie to nie! It pasuje String której długość nie jest pierwsza!
  • .?: pierwsza część alternacji pasuje String o długości 0 lub 1 (nie pierwsza przez definicja)
  • (..+?)\1+ : druga część alternacji, odmiana regex wyjaśniona powyżej, pasuje String o długości n, która jest "wielokrotnością" String o długości k >= 2 (tzn. n jest złożoną, Nie pierwszą).
    • zauważ, że niechętny modyfikator ? w rzeczywistości nie jest potrzebny do poprawności, ale może przyspieszyć proces, próbując najpierw mniejszych k {70]}

Uwaga ! boolean operator dopełniacza w return twierdzenie: neguje matches. Gdy regex nie pasuje, n jest liczbą pierwszą! To logika podwójnie negatywna, więc nic dziwnego, że to trochę mylące!!


Uproszczenie

Oto proste przepisanie kodu, aby uczynić go bardziej czytelnym:

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

Powyższy kod jest zasadniczo taki sam jak oryginalny kod Javy, ale podzielony na wiele instrukcji z przypisaniem do zmiennych lokalnych, aby logika była łatwiejsza do zrozumienia.

We można również uprościć wyrażenia regularne, używając skończonych powtórzeń, w następujący sposób:

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

Ponownie, biorąc pod uwagę String o długości n, wypełniony tym samym char,

  • .{0,1} sprawdza, czy n = 0,1, a nie prime
  • (.{2,})\1+ sprawdza, czy n jest właściwą wielokrotnością k >= 2, A Nie liczbą pierwszą

Z wyjątkiem niechętnego modyfikatora ? on \1 (pominięty dla jasności), powyższy regex jest identyczny z oryginałem.


More fun regex

The po regex używa podobnej techniki; powinien być edukacyjny:

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

Zobacz też

 68
Author: polygenelubricants,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:45

Ładny trik regex (choć bardzo nieefektywny)... :)

Regex definiuje Nie-liczby pierwsze w następujący sposób:

N nie jest liczbą pierwszą wtedy i tylko wtedy, gdy N 1.

Zamiast przekazywać prostą cyfrową reprezentację n do silnika regex, jest ona zasilana sekwencją długości N, złożoną z powtarzającego się znaku. Pierwsza część dysjunkcji sprawdza, czy N = 0 lub N = 1, a druga szuka dzielnika K>1, używając backreferences. Zmusza do regex engine, aby znaleźć pewną niepustą pod sekwencję, która może być powtórzona co najmniej dwa razy w celu utworzenia sekwencji. Jeśli taka podklasa istnieje, oznacza to, że jej długość dzieli N, A więc N nie jest liczbą pierwszą.

 24
Author: Eyal Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-08 18:48:10
/^1?$|^(11+?)\1+$/

Zastosuj do liczb po przeliczaniu na bazę 1 (1=1, 2=11, 3=111, ...). Nie-pierwsze będą pasować do tego. Jeśli nie pasuje, to jest pierwsza.

Wyjaśnienie tutaj .

 3
Author: Dinah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:56