web-dev-qa-db-fra.com

Comment déterminer si un nombre est un nombre premier avec regex?

J'ai trouvé l'exemple de code suivant pour Java on RosettaCode :

public static boolean prime(int n) {
  return !new String(new char[n]).matches(".?|(..+?)\\1+");
}
  • Je ne sais pas Java en particulier, mais je comprends tous les aspects de cet extrait, à l'exception de l'expression régulière elle-même
  • J'ai des connaissances de base à avancées de Regex telles que vous les trouvez dans les fonctions intégrées PHP

Comment .?|(..+?)\\1+ correspond-il aux nombres premiers?

125
kitlite

Vous avez dit que vous comprenez cette partie, mais pour souligner, la chaîne générée a une longueur égale au nombre fourni. La chaîne a donc trois caractères si et seulement si n == 3.

.?

La première partie de l'expression rationnelle dit "n'importe quel caractère, zéro ou une fois". Donc, fondamentalement, y a-t-il zéro ou un caractère-- ou, d'après ce que j'ai mentionné ci-dessus, n == 0 || n == 1. Si nous avons la correspondance, renvoyez la négation de cela. Cela correspond au fait que zéro et un ne sont PAS premiers.

(..+?)\\1+

La deuxième partie de l'expression régulière est un peu plus délicate, en s'appuyant sur les groupes et les références. Un groupe est n'importe quoi entre parenthèses, qui sera ensuite capturé et stocké par le moteur d'expression régulière pour une utilisation ultérieure. Une référence arrière est un groupe apparié qui est utilisé plus tard dans la même expression régulière.

Le groupe capture 1 personnage, puis 1 ou plus de n'importe quel personnage. (Le caractère + signifie un ou plusieurs, mais UNIQUEMENT du caractère ou groupe précédent. Il ne s'agit donc pas de "deux ou quatre ou six etc. caractères", mais plutôt de "deux ou trois etc.". Le +? Est comme +, mais il essaie de faire correspondre le moins de caractères possible. + essaie normalement de gober la chaîne entière s'il le peut, ce qui est mauvais dans ce cas car cela empêche la partie de référence arrière de fonctionner.)

La partie suivante est la référence arrière: ce même ensemble de caractères (deux ou plus), réapparaissant. Cette rétro-référence apparaît une ou plusieurs fois.

Alors. Le groupe capturé correspond à un nombre naturel de caractères (à partir de 2) capturés. Ledit groupe apparaît alors un certain nombre de fois naturel (également à partir de 2). S'il y a IS une correspondance, cela implique qu'il est possible de trouver un produit de deux nombres supérieur ou égal à 2 qui correspondent à la chaîne de longueur n ... ce qui signifie que vous avez un n composite. Encore une fois, retournez la négation de la correspondance réussie: n n'est PAS premier.

Si aucune correspondance ne peut être trouvée, alors vous ne pouvez pas trouver un produit de deux nombres naturels supérieur ou égal à 2 ... et vous avez à la fois une non-correspondance et un nombre premier, d'où le retour de la négation du résultat du match.

Le voyez-vous maintenant? C'est incroyablement délicat (et coûteux en calcul!) Mais c'est aussi assez simple en même temps, une fois que vous l'avez obtenu. :-)

Je peux élaborer si vous avez d'autres questions, comme sur la façon dont fonctionne réellement l'analyse syntaxique des expressions rationnelles. Mais j'essaie de garder cette réponse simple pour l'instant (ou aussi simple que possible).

116
Platinum Azure

Je vais expliquer la partie regex en dehors du test de primalité: la regex suivante, étant donné un String s Qui consiste à répéter String t, Trouve t.

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

La façon dont cela fonctionne est que l'expression régulière capture (.*) Dans \1, Puis voit s'il y a \1+ À la suite. L'utilisation de ^ Et $ Garantit qu'une correspondance doit être de la chaîne entière.

Donc, d'une certaine manière, on nous donne String s, Qui est un "multiple" de String t, Et l'expression régulière trouvera un tel t (le plus long possible, puisque \1 Est gourmand).

Une fois que vous comprenez pourquoi cette expression régulière fonctionne, alors (en ignorant la première alternative dans l'expression régulière d'OP pour l'instant), expliquer comment il est utilisé pour le test de primalité est simple.

  • Pour tester la primalité de n, commencez par générer un String de longueur n (rempli avec le même char)
  • Le regex capture un String d'une certaine longueur (disons k) dans \1, Et essaie de faire correspondre \1+ Au reste du String
    • S'il existe une correspondance, alors n est un multiple propre de k, et donc n n'est pas un nombre premier.
    • S'il n'y a pas de correspondance, alors k n'existe pas qui divise n, et n est donc un nombre premier

Comment .?|(..+?)\1+ correspond-il aux nombres premiers?

En fait, ce n'est pas le cas! Il correspondString dont la longueur n'est PAS première!

  • .?: La première partie de l'alternance correspond à String de longueur 0 Ou 1 (PAS d'amorce par définition)
  • (..+?)\1+: La deuxième partie de l'alternance, une variation de l'expression rationnelle expliquée ci-dessus, correspond à String de longueur n qui est "un multiple" d'un String de longueur k >= 2 (c'est-à-dire que n est un composite, PAS un nombre premier).
    • Notez que le modificateur réticent ? N'est en fait pas nécessaire pour l'exactitude, mais il peut aider à accélérer le processus en essayant d'abord un plus petit k

Notez l'opérateur de complément !boolean dans l'instruction return: il annule matches. C'est lorsque l'expression régulière NE correspond PAS , n est premier! C'est une logique à double négatif, donc pas étonnant que ce soit un peu déroutant !!


Simplification

Voici une simple réécriture du code pour le rendre plus lisible:

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

Ce qui précède est essentiellement le même que le code Java Java original, mais divisé en plusieurs instructions avec des affectations à des variables locales pour rendre la logique plus facile à comprendre.

Nous pouvons également simplifier l'expression régulière, en utilisant la répétition finie, comme suit:

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

Encore une fois, étant donné un String de longueur n, rempli du même char,

  • .{0,1} Vérifie si n = 0,1, NE PAS amorcer
  • (.{2,})\1+ Vérifie si n est un multiple propre de k >= 2, PAS premier

À l'exception du modificateur réticent ? Sur \1 (Omis pour plus de clarté), l'expression rationnelle ci-dessus est identique à l'original.


Regex plus amusant

Le regex suivant utilise une technique similaire; il doit être éducatif:

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

Voir également

71

Belle astuce regex (bien que très inefficace) ... :)

La regex définit les non-nombres premiers comme suit:

N n'est pas premier si et seulement si N <= 1 OR N est divisible par un K> 1.

Au lieu de transmettre la simple représentation numérique de N au moteur d'expression rationnelle, il est alimenté avec une séquence de longueur N, composée d'un caractère répétitif. La première partie de la disjonction vérifie N = 0 ou N = 1, et la seconde recherche un diviseur K> 1, en utilisant des références arrières. Il force le moteur d'expression régulière à trouver une sous-séquence non vide qui peut être répétée au moins deux fois afin de former la séquence. Si une telle sous-séquence existe, cela signifie que sa longueur divise N, donc N n'est pas premier.

25
Eyal Schneider
/^1?$|^(11+?)\1+$/

Appliquer aux nombres après conversion en base 1 (1 = 1, 2 = 11, 3 = 111, ...). Les non-premiers correspondront à cela. S'il ne correspond pas, il est premier.

Explication ici .

3
Dinah