web-dev-qa-db-fra.com

Comment vérifier qu'une chaîne est un palindrome à l'aide d'expressions régulières?

C'était une question d'entrevue à laquelle je n'ai pas pu répondre:

Comment vérifier qu'une chaîne est un palindrome à l'aide d'expressions régulières?

p.s. Il y a déjà une question " Comment vérifier si la chaîne donnée est palindrome? " et donne beaucoup de réponses dans différentes langues, mais pas de réponse utilisant des expressions régulières.

71
Degvik

La réponse à cette question est que "c'est impossible". Plus spécifiquement, l'intervieweur se demande si vous avez prêté attention à votre cours de théorie informatique.

Dans votre cours de théorie informatique, vous avez découvert les machines à états finis. Une machine à états finis est composée de nœuds et d'arêtes. Chaque bord est annoté avec une lettre d'un alphabet fini. Un ou plusieurs nœuds sont des nœuds "accepteurs" spéciaux et un nœud est le nœud "début". Lorsque chaque lettre est lue à partir d'un mot donné, nous traversons le bord donné dans la machine. Si nous nous retrouvons dans un état d'acceptation, nous disons que la machine "accepte" ce mot.

Une expression régulière peut toujours être traduite en une machine à états finis équivalente. C'est-à-dire qui accepte et rejette les mêmes mots que l'expression régulière (dans le monde réel, certains langages d'expressions rationnelles autorisent des fonctions arbitraires, celles-ci ne comptent pas).

Il est impossible de construire une machine à états finis acceptant tous les palindromes. La preuve repose sur le fait que nous pouvons facilement créer une chaîne qui nécessite un nombre arbitrairement grand de nœuds, à savoir la chaîne

a ^ x b a ^ x (par exemple, aba, aabaa, aaabaaa, aaaabaaaa, ....)

où a ^ x est répété x fois. Cela nécessite au moins x nœuds car, après avoir vu le «b», nous devons compter x fois pour nous assurer qu'il s'agit bien d'un palindrome.

Enfin, pour revenir à la question initiale, vous pouvez dire à l'intervieweur que vous pouvez écrire une expression régulière qui accepte tous les palindromes plus petits que certaines longueurs fixes finies. S'il existe une application réelle qui nécessite l'identification des palindromes, elle n'inclura presque certainement pas les longues longueurs arbitraires. Cette réponse montrerait donc que vous pouvez différencier les impossibilités théoriques des applications réelles. Néanmoins, l'expression rationnelle réelle serait assez longue, bien plus longue qu'un programme équivalent de 4 lignes (exercice facile pour le lecteur: écrivez un programme identifiant les palindromes).

123
Jose M Vidal

Bien que le moteur PCRE supporte les expressions rationnelles récursives (voir la réponse de Peter Krauss ), vous ne pouvez pas utiliser une expression rationnelle sur le moteur ICU Apple) pour y parvenir sans code supplémentaire. Vous devrez faire quelque chose comme ça:

Cela détecte tout palindrome, mais nécessite une boucle (ce qui sera nécessaire car les expressions régulières ne peuvent pas compter). 

$a = "teststring";
while(length $a > 1)
{
   $a =~ /(.)(.*)(.)/;
   die "Not a palindrome: $a" unless $1 eq $3;
   $a = $2;
}
print "Palindrome";
42
Airsource Ltd

Ce n'est pas possible. Les palindromes ne sont pas définis par un langage normal. (Voir, je DID apprends quelque chose en théorie des calculs)

27
ZCHudson

Avec l'expression rationnelle Perl:

/^((.)(?1)\2|.?)$/

Cependant, comme beaucoup l'ont souligné, cela ne peut pas être considéré comme une expression régulière si vous voulez être strict. Expressions régulières ne prend pas en charge la récursivité.

23
Markus Jarderot

Voici un pour détecter les palindromes de 4 lettres (par exemple: acte de propriété), pour tout type de personnage:

\(.\)\(.\)\2\1

Voici un pour détecter les palindromes à 5 lettres (par exemple: radar), en vérifiant uniquement les lettres:

\([a-z]\)\([a-z]\)[a-z]\2\1

Il semble donc que nous ayons besoin d’une expression rationnelle différente pour chaque longueur de mot possible . Cet article sur une liste de diffusion Python contient des informations détaillées sur les raisons de ce choix (automates finis et automate de pompage).

11
FOR

Oui, vous pouvez le faire en .Net!

(?<N>.)+.?(?<-N>\k<N>)+(?(N)(?!))

Vous pouvez le vérifier ici ! C'est un post merveilleux!

10
kev

En fonction de votre confiance, je donnerais cette réponse:

Je ne le ferais pas avec un habitué expression. Ce n'est pas un approprié. utilisation d'expressions régulières.

9
Jon Skeet

Comme quelques-uns l’ont déjà dit, il n’existe pas d’expression rationnelle unique qui détecte un palindrome général hors de la boîte, mais si vous souhaitez détecter des palindromes d’une certaine longueur, vous pouvez utiliser quelque chose comme:

(.?)(.?)(.?)(.?)(.?).?\5\4\3\2\1
7
Stewart

Cela peut être fait en Perl maintenant. Utilisation de référence récursive:

if($istr =~ /^((\w)(?1)\g{-1}|\w?)$/){
    print $istr," is palindrome\n";
}

modifié sur la base de la dernière partie proche http://perldoc.Perl.org/perlretut.html

4
Hui Liu

Dans Ruby, vous pouvez utiliser des groupes de capture nommés. donc quelque chose comme ça va marcher -

def palindrome?(string)
  $1 if string =~ /\A(?<p>| \w | (?: (?<l>\w) \g<p> \k<l+0> ))\z/x
end

essayez, ça marche ...

1.9.2p290 :017 > palindrome?("racecar")
 => "racecar" 
1.9.2p290 :018 > palindrome?("kayak")
 => "kayak" 
1.9.2p290 :019 > palindrome?("woahitworks!")
 => nil 
4
Taylor
/\A(?<a>|.|(?:(?<b>.)\g<a>\k<b+0>))\z/

il est valable pour le moteur Oniguruma (utilisé en Ruby)

pris de Pragmatic Bookshelf

3
mpugach

Les expressions régulières récursives peuvent le faire!

Algorithme si simple et évident pour détecter une chaîne qui contient un palindrome:

   (\w)(?:(?R)|\w?)\1

Sur rexegg.com/regex-recursion le didacticiel explique comment cela fonctionne.


Cela fonctionne très bien avec n'importe quelle langue, voici un exemple adapté de la même source (lien) comme preuve de concept, en utilisant PHP:

$subjects=['dont','o','oo','kook','book','paper','kayak','okonoko','aaaaa','bbbb'];
$pattern='/(\w)(?:(?R)|\w?)\1/';
foreach ($subjects as $sub) {
  echo $sub." ".str_repeat('-',15-strlen($sub))."-> ";
  if (preg_match($pattern,$sub,$m)) 
      echo $m[0].(($m[0]==$sub)? "! a palindrome!\n": "\n");
  else 
      echo "sorry, no match\n";
}

les sorties

dont ------------> sorry, no match
o ---------------> sorry, no match
oo --------------> oo! a palindrome!
kook ------------> kook! a palindrome!
book ------------> oo
paper -----------> pap
kayak -----------> kayak! a palindrome!
okonoko ---------> okonoko! a palindrome!
aaaaa -----------> aaaaa! a palindrome!
bbbb ------------> bbb

Comparant

L'expression régulière ^((\w)(?:(?1)|\w?)\2)$ effectue le même travail, mais sous la forme oui/non, "contient". 
PS: il utilise une définition où "o" n’est pas un palimbrome, le format "capable-elba" hyphened n’est pas un palindrome, mais "capableelba". Le nommer definition1
Quand "o" et "capable-elba" sont des palindrones, nommez definition2.

En comparant avec une autre "regex palindrome",

  • ^((.)(?:(?1)|.?)\2)$ la regex de base ci-dessus sans restriction \w, acceptant "capable-elba".

  • ^((.)(?1)?\2|.)$ ( @LilDevil ) Utilisez definition2 (accepte "o" et "capable-elba", ce qui diffère également par la reconnaissance des chaînes "aaaaa" et "bbbb").

  • ^((.)(?1)\2|.?)$ ( @Markus ) non détecté "kook" ni "bbbb"

  • ^((.)(?1)*\2|.?)$ ( @Csaba ) Utilisez definition2.


NOTE: pour comparer, vous pouvez ajouter plus de mots à $subjects et une ligne pour chaque regex comparée,

  if (preg_match('/^((.)(?:(?1)|.?)\2)$/',$sub)) echo " ...reg_base($sub)!\n";
  if (preg_match('/^((.)(?1)?\2|.)$/',$sub)) echo " ...reg2($sub)!\n";
  if (preg_match('/^((.)(?1)\2|.?)$/',$sub)) echo " ...reg3($sub)!\n";
  if (preg_match('/^((.)(?1)*\2|.?)$/',$sub)) echo " ...reg4($sub)!\n";
3
Peter Krauss

En ce qui concerne l'expression PCRE (de MizardX):

/^((. )(?1)\2|.?)$/

Avez-vous testé? Sur mon PHP 5.3 sous Win XP Pro, il échoue sur: aaaba En fait, j’ai légèrement modifié l’expression, comme suit:

/^((. )(?1)*\2|.?)$/

Je pense que ce qui se passe, c'est que, si la paire de caractères externe est ancrée, les autres ne le sont pas. Ce n'est pas tout à fait la réponse car s'il transmet à tort "aaaba" et "aabaacaa", il échoue correctement sur "aabaaca".

Je me demande s’il existe une solution pour cela, et aussi, L’exemple Perl (de JF Sebastian/Zsolt) passe-t-il correctement mes tests?

Csaba Gabor de Vienne

2
Csaba

En Perl (voir aussi réponse de Zsolt Botykai ):

$re = qr/
  .                 # single letter is a palindrome
  |
  (.)               # first letter
  (??{ $re })??     # apply recursivly (not interpolated yet)
  \1                # last letter
/x;

while(<>) {
    chomp;
    say if /^$re$/; # print palindromes
}
2
jfs

Il est en fait plus facile de le faire avec une manipulation de chaîne plutôt qu'avec des expressions régulières:

bool isPalindrome(String s1)

{

    String s2 = s1.reverse;

    return s2 == s1;
}

Je me rends compte que cela ne répond pas vraiment à la question de l'entrevue, mais vous pouvez l'utiliser pour montrer comment vous connaissez une meilleure façon de faire une tâche, et vous n'êtes pas la personne type avec un marteau, qui voit chaque problème comme un clou . "

2
Dan

Voici ma réponse à 5ème niveau de Regex Golf } _ (Un homme, un plan) Cela fonctionne jusqu'à 7 caractères avec Regexp du navigateur (j'utilise Chrome 36.0.1985.143).

^(.)(.)(?:(.).?\3?)?\2\1$

En voici une pour un maximum de 9 caractères

^(.)(.)(?:(.)(?:(.).?\4?)?\3?)?\2\1$

Pour augmenter le nombre maximal de caractères pour lesquels il fonctionnerait, vous devez remplacer à plusieurs reprises .? avec (?: (.).?\n?)?.

2
pbatey

voici le code PL/SQL qui indique si la chaîne donnée est palindrome ou n'utilise pas d'expressions régulières:

create or replace procedure palin_test(palin in varchar2) is
 tmp varchar2(100);
 i number := 0;
 BEGIN
 tmp := palin;
 for i in 1 .. length(palin)/2 loop
  if length(tmp) > 1 then  
    if regexp_like(tmp,'^(^.).*(\1)$') = true then 
      tmp := substr(palin,i+1,length(tmp)-2);
    else 
      dbms_output.put_line('not a palindrome');
      exit;
    end if;
  end if;  
  if i >= length(palin)/2 then 
   dbms_output.put_line('Yes ! it is a palindrome');
  end if;
 end loop;  
end palin_test;
1
ankush

Comme l'a souligné ZCHudson , déterminer si quelque chose est un palindrome ne peut pas être fait avec une expression rationnelle habituelle, car l'ensemble de palindrome n'est pas un langage courant.

Je suis totalement en désaccord avec Airsource Ltd quand il dit que "ce n'est pas possible" n'est pas le genre de réponse que l'intervieweur recherche. Au cours de mon entretien, j’arrive à ce genre de question lorsque j’affronte un bon candidat afin de vérifier s’il peut trouver le bon argument lorsque nous lui proposons de faire quelque chose de mal. Je ne veux pas embaucher quelqu'un qui essaiera de faire quelque chose de mal s'il en connait mieux.

1
Nicolas

De la théorie des automates, il est impossible de faire correspondre un paliandrome de toute longueur (car cela nécessite une quantité de mémoire infinie). Mais IT IS POSSIBLE de faire correspondre des paliandromes de longueur fixe . Il est possible d’écrire une expression régulière correspondant à tous les paliandromes de longueur <= 5 ou <= 6, etc., mais pas> = 5, etc. lié est pas clair

1
Vijeenrosh P.W

En Ruby, vous pouvez utiliser \b(?'Word'(?'letter'[a-z])\g'Word'\k'letter+0'|[a-z])\b pour faire correspondre des mots palindromes tels que a, dad, radar, racecar, and redivider. ps: cette expression rationnelle correspond uniquement aux mots palindromes qui ont un nombre impair de lettres.

Voyons comment cette regex correspond au radar. La limite de mot\b correspond au début de la chaîne. Le moteur d'expression régulière entre dans le groupe de capture "Word". [a-z] correspond à r qui est ensuite stocké dans la pile pour le groupe de saisie "lettre" au niveau de récursivité zéro. Maintenant, le moteur de regex entre la première récursivité du groupe "Word". (? 'lettre' [a-z]) correspond et capture un niveau de récursivité un. La regex entre dans la deuxième récurrence du groupe "Word". (? 'lettre' [a-z]) capture d au niveau de récurrence deux. Au cours des deux prochaines récurrences, le groupe capture a et r aux niveaux trois et quatre. La cinquième récursion échoue car il ne reste plus de caractères dans la chaîne que [a-z] puisse faire correspondre. Le moteur de regex doit revenir en arrière.

Le moteur de regex doit maintenant essayer la deuxième alternative du groupe "Word". Le second [a-z] de la regex correspond au dernier r de la chaîne. Le moteur sort maintenant d'une récursivité réussie, remontant d'un niveau jusqu'à la troisième récursivité.

Après correspondance (& Word), le moteur atteint\k'letter + 0 '. La référence arrière échoue car le moteur des expressions rationnelles a déjà atteint la fin de la chaîne de sujet. Donc, il revient une fois de plus. La deuxième alternative correspond maintenant à la a. Le moteur de regex sort de la troisième récursivité.

Le moteur des expressions rationnelles a de nouveau correspondu (& Word) et doit tenter à nouveau la référence arrière. La référence arrière spécifie +0 ou le niveau actuel de récursivité, qui est 2. À ce niveau, le groupe de capture correspond d. La référence arrière échoue car le caractère suivant de la chaîne est r. En revenant à nouveau, la deuxième alternative correspond à d.

Maintenant,\k'letter + 0 'correspond au second a de la chaîne. En effet, le moteur des expressions rationnelles est revenu à la première récursivité au cours de laquelle le groupe de capture correspondait à la première a. Le moteur de regex quitte la première récursivité.

Le moteur de regex est maintenant de retour en dehors de toute récursion. Que ce niveau, le groupe de capture stocké r. La référence arrière peut maintenant correspondre au dernier r de la chaîne. Comme le moteur n’est plus dans aucune récursion, il continue avec le reste de la regex après le groupe.\b correspond à la fin de la chaîne. La fin de l'expression rationnelle est atteinte et le radar est renvoyé comme match complet.

1
Melih Altıntaş

Je n'ai pas encore le représentant en ligne pour commenter, mais l'expression régulière fournie par MizardX, et modifiée par Csaba, peut être modifiée davantage pour le rendre fonctionnel dans PCRE. Le seul échec que j'ai trouvé est la chaîne à caractère unique, mais je peux le tester séparément.

/^((.)(?1)?\2|.)$/

Si vous pouvez le faire échouer sur d'autres chaînes, veuillez commenter.

1
Lil Devil

Le mieux que vous puissiez faire avec les expressions rationnelles, avant de manquer de groupes de capture:

/(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?).?\9\8\7\6\5\4\3\2\1/

Cela correspond à tous les palindromes de 19 caractères maximum.

La résolution par programme pour toutes les longueurs est triviale:

str == str.reverse ? true : false
1
Chris
#!/usr/bin/Perl

use strict;
use warnings;

print "Enter your string: ";
chop(my $a = scalar(<STDIN>));    
my $m = (length($a)+1)/2;
if( (length($a) % 2 != 0 ) or length($a) > 1 ) { 
  my $r; 
  foreach (0 ..($m - 2)){
    $r .= "(.)";
  }
  $r .= ".?";
  foreach ( my $i = ($m-1); $i > 0; $i-- ) { 
    $r .= "\\$i";
  } 
  if ( $a =~ /(.)(.).\2\1/ ){
    print "$a is a palindrome\n";
  }
  else {
    print "$a not a palindrome\n";
 }
exit(1);
}
print "$a not a palindrome\n";
1
sapam

quelque chose que vous pouvez faire avec Perl: http://www.perlmonks.org/?node_id=577368

1
Zsolt Botykai

J'expliquerais à l'intervieweur que le langage composé de palindromes n'est pas un langage courant mais qu'il est dépourvu de contexte.

L'expression régulière qui correspondrait à tous les palindromes serait infini . Au lieu de cela, je lui suggérerais de se limiter à une taille maximale de palindromes à accepter; ou si tous les palindromes sont nécessaires, utilisez au minimum un type de NDPA, ou utilisez simplement la technique simple inversion de chaîne/égal à.

1
Flame

mon $ pal = 'malayalam';

while($pal=~/((.)(.*)\2)/){                                 #checking palindrome Word
    $pal=$3;
}
if ($pal=~/^.?$/i){                                         #matches single letter or no letter
    print"palindrome\n";
}
else{
    print"not palindrome\n";
}
0
Kanchan Sen Laskar

Un léger raffinement de la méthode d'Airsource Ltd, en pseudocode:

WHILE string.length > 1
    IF /(.)(.*)\1/ matches string
        string = \2
    ELSE
        REJECT
ACCEPT
0
Stewart

En JavaScript c'est fait en tapant

          function palindrome(str) {
  var symbol = /\W|_/g;
  str = str.replace(symbol, "").toLowerCase();
  var palindrome = str.split("").reverse("").join("");
  return (str === palindrome);
}
0
Erik Rybalkin

Vous pouvez également le faire sans utiliser la récursivité:

\A(?:(.)(?=.*?(\1\2?)\z))*?.?\2\z

ou pour exclure la chaîne vide:

\A(?=.)(?:(.)(?=.*?(\1\2?)\z))*?.?\2\z

Fonctionne avec Perl, PCRE, Ruby, Java

démo

0