it-swarm.com.de

Wie kann man mit regulären Ausdrücken überprüfen, ob ein String ein Palindrom ist?

Das war eine Interviewfrage, die ich nicht beantworten konnte:

Wie kann man mit regulären Ausdrücken überprüfen, ob ein String ein Palindrom ist?

p.s. Es gibt bereits eine Frage " Wie prüfe ich, ob die angegebene Zeichenfolge ein Palindrom ist? " und gibt viele Antworten in verschiedenen Sprachen, aber keine Antwort, die reguläre Ausdrücke verwendet.

71
Degvik

Die Antwort auf diese Frage lautet: "Es ist unmöglich". Genauer gesagt, fragt sich der Interviewer, ob Sie in Ihrem Computertheorieunterricht aufpassen.

In Ihrem Computer für Computertheorie haben Sie Finite-State-Maschinen gelernt. Eine endliche Zustandsmaschine besteht aus Knoten und Kanten. Jeder Rand wird mit einem Buchstaben aus einem endlichen Alphabet versehen. Ein oder mehrere Knoten sind spezielle "akzeptierende" Knoten und ein Knoten ist der "Start" -Knoten. Während jeder Buchstabe aus einem bestimmten Wort gelesen wird, durchqueren wir den angegebenen Rand in der Maschine. Wenn wir in einem akzeptablen Zustand landen, sagen wir, dass die Maschine dieses Wort "akzeptiert".

Ein regulärer Ausdruck kann immer in eine äquivalente Zustandsmaschine übersetzt werden. Das heißt, einer, der dieselben Wörter wie der reguläre Ausdruck akzeptiert und zurückweist (in der realen Welt erlauben einige reguläre Ausdrücke beliebige Funktionen, diese zählen nicht).

Es ist unmöglich, eine Zustandsmaschine zu bauen, die alle Palindrome akzeptiert. Der Beweis beruht auf den Tatsachen, dass wir leicht eine Zeichenfolge erstellen können, die eine beliebig große Anzahl von Knoten erfordert, nämlich die Zeichenfolge

a ^ x b a ^ x (z. B. aba, aabaa, aaabaaa, aaaabaaaa, ....)

wobei a ^ x ein x-mal ist. Dies erfordert mindestens x Knoten, da wir, nachdem wir das 'b' gesehen haben, x-mal zurückzählen müssen, um sicherzustellen, dass es ein Palindrom ist.

Um auf die ursprüngliche Frage zurückzukommen, könnten Sie dem Interviewer sagen, dass Sie einen regulären Ausdruck schreiben können, der alle Palindrome akzeptiert, die kleiner als eine begrenzte feste Länge sind. Wenn es eine reale Anwendung gibt, bei der Palindrome identifiziert werden müssen, dann werden mit Sicherheit keine willkürlich langen eingeschlossen. Daher würde diese Antwort zeigen, dass Sie theoretische Unmöglichkeiten von realen Anwendungen unterscheiden können. Trotzdem wäre der eigentliche Regex ziemlich lang, viel länger als das entsprechende 4-Zeilen-Programm (einfache Übung für den Leser: Schreiben Sie ein Programm, das Palindrome identifiziert).

123
Jose M Vidal

Während die PCRE - Engine rekursive reguläre Ausdrücke unterstützt (siehe die Antwort von Peter Krauss ), können Sie auf der ICU - Engine keinen Regex verwenden (z. B. von Apple), um dies ohne zusätzlichen Code zu erreichen. Sie müssen so etwas tun:

Dies erkennt jedes Palindrom, erfordert jedoch eine Schleife (die erforderlich ist, da reguläre Ausdrücke nicht zählen können). 

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

Es ist nicht möglich. Palindrome werden nicht durch eine reguläre Sprache definiert. (Sehen Sie, ich DID etwas in der Computertheorie lernen)

27
ZCHudson

Mit Perl Regex:

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

Wie viele schon gesagt haben, kann dies nicht als regulärer Ausdruck betrachtet werden, wenn Sie streng sein wollen. Reguläre Ausdrücke unterstützt keine Rekursion.

23
Markus Jarderot

Hier ist Eins zum Erkennen von Palindromen mit 4 Buchstaben (z. B. Tat) für jeden Zeichentyp:

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

Hier ist eine zum Erkennen von 5-Buchstaben-Palindromen (z. B. Radar), wobei nur nach Buchstaben gesucht wird:

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

Es scheint also, dass wir für jede mögliche Wortlänge einen anderen Regex benötigen Dieser Beitrag auf einer Python-Mailingliste enthält einige Details dazu (Finite State Automata und Pumping Lemma).

11
FOR

Ja, du kannst es in .Net!

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

Sie können es überprüfen hier ! Es ist ein wunderbarer Beitrag!

10
kev

Abhängig davon, wie sicher Sie sind, würde ich diese Antwort geben:

Ich würde es nicht mit einem regulären machen. Ausdruck. Es ist nicht angemessen Verwendung regulärer Ausdrücke.

9
Jon Skeet

Wie einige bereits gesagt haben, gibt es keinen einzigen Regex, der ein allgemeines Palindrom aus der Box heraus erkennt. Wenn Sie jedoch Palindrome bis zu einer bestimmten Länge erkennen möchten, können Sie so etwas verwenden

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

StackOverflow steckt voller Antworten wie "Reguläre Ausdrücke? Nein, sie unterstützen es nicht. Sie können nicht unterstützen es.".

Die Wahrheit ist, dass reguläre Ausdrücke nichts mehr mit regulären Grammatiken zu tun haben Moderne reguläre Ausdrücke bieten Funktionen wie Rekursion und Bilanzkreise, und die Verfügbarkeit ihrer Implementierungen nimmt ständig zu (siehe Ruby Beispiele hier zum Beispiel). Meiner Meinung nach ist der alte Glaube, dass reguläre Ausdrücke in unserem Bereich alles andere als a sind Das Programmierkonzept ist nur kontraproduktiv: Anstatt sie für die nicht mehr am besten geeignete Wortwahl zu hassen, ist es an der Zeit, dass wir die Dinge akzeptieren und weitermachen.

Hier ist ein Zitat von Larry Wall , der Schöpfer von Perl selbst:

(…) Im Allgemeinen mit den sogenannten regulären Ausdrücken zu tun haben, die sich nur am Rande auf echte reguläre Ausdrücke beziehen. Trotzdem ist der Begriff mit den Fähigkeiten unserer Pattern-Matching-Engines gewachsen, weshalb ich hier nicht versuchen werde, die sprachliche Notwendigkeit zu bekämpfen. Ich werde sie jedoch im Allgemeinen als "Regexes" (oder "Regexen") bezeichnen, wenn ich in angelsächsischer Stimmung bin.

Und hier ist ein Blog-Beitrag von einem der Kernentwickler von PHP :

Da der Artikel ziemlich lang war, hier eine Zusammenfassung der wichtigsten Punkte:

  • Die von Programmierern verwendeten „regulären Ausdrücke“ haben mit dem ursprünglichen Begriff der Regelmäßigkeit im Kontext der formalen Sprachtheorie sehr wenig gemein.
  • Reguläre Ausdrücke (mindestens PCRE) können mit allen kontextfreien Sprachen übereinstimmen. Als solche passen sie auch zu wohlgeformtem HTML und so ziemlich allen anderen Programmiersprachen.
  • Reguläre Ausdrücke können mindestens einigen kontextsensitiven Sprachen entsprechen.
  • Die Übereinstimmung von regulären Ausdrücken ist NP-vollständig. Als solches können Sie jedes andere NP Problem mit regulären Ausdrücken lösen.

Davon abgesehen können Sie Palindrome mit Regexen abgleichen, indem Sie Folgendes verwenden:

^(?'letter'[a-z])+[a-z]?(?:\k'letter'(?'-letter'))+(?(letter)(?!))$

... was natürlich nichts mit regulären Grammatiken zu tun hat.
Mehr Infos hier: http://www.regular-expressions.info/balancing.html

7
rr-

Das kann jetzt in Perl gemacht werden. Rekursive Referenz verwenden:

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

basierend auf dem nahe letzten Teil geändert http://perldoc.Perl.org/perlretut.html

4
Hui Liu

In Ruby können Sie benannte Erfassungsgruppen verwenden. so etwas wird funktionieren -

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

probieren Sie es aus, es funktioniert ...

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/

es gilt für die Oniguruma-Engine (die in Ruby verwendet wird)

nahm aus Pragmatic Bookshelf

3
mpugach

Rekursive reguläre Ausdrücke können es schaffen!

So einfacher und selbstverständlicher Algorithmus zum Erkennen einer Zeichenfolge, die ein Palindrom enthält:

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

Unter rexegg.com/regex-recursion erklärt das Tutorial, wie es funktioniert.


Es funktioniert gut mit jeder Sprache, hier ein Beispiel, das aus derselben Quelle (Link) als Proof-of-Concept mit PHP angepasst wurde:

$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";
}

ausgänge

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

Vergleich

Der reguläre Ausdruck ^((\w)(?:(?1)|\w?)\2)$ führt den gleichen Job aus, enthält aber stattdessen "ja". 
PS: Es wird eine Definition verwendet, bei der "o" kein Palimbrom ist, das "able-elba" Format mit Bindestrich kein Palindrom, aber "ableelba" ist. Benennen Sie es definition1
Wenn "o" und "able-elba" Palindronen sind, benennen Sie definition2.

Vergleich mit anderen "Palindrom-Regexen",

  • ^((.)(?:(?1)|.?)\2)$ den Basis-Regex oben ohne \w Einschränkung, wobei "able-elba" akzeptiert wird.

  • ^((.)(?1)?\2|.)$ ( @LilDevil ) Verwenden Sie definition2 (akzeptiert "o" und "able-elba", unterscheidet sich also auch in der Erkennung von "aaaaa" - und "bbbb" -Strings).

  • ^((.)(?1)\2|.?)$ ( @Markus ) hat weder "kook" noch "bbbb" erkannt

  • ^((.)(?1)*\2|.?)$ ( @Csaba ) Verwenden Sie Definition2.


HINWEIS: Zum Vergleich können Sie weitere Wörter bei $subjects und eine Zeile für jeden verglichenen Regex hinzufügen.

  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

Bezüglich des PCRE-Ausdrucks (von MizardX):

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

Hast du es getestet? Auf meinem PHP 5.3 unter Win XP Pro schlägt es fehl auf: aaaba Eigentlich habe ich den Ausdruck etwas geändert, um zu lesen:

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

Ich denke, was passiert, ist, dass, während das äußere Zeichenpaar verankert ist, die verbleibenden inneren nicht. Dies ist nicht die ganze Antwort, da es zwar falsch an "aaaba" und "aabaacaa" weitergibt, aber bei "aabaaca" nicht richtig funktioniert.

Ich frage mich, ob es hier ein Fixup gibt und auch Besteht das Perl-Beispiel (von JF Sebastian/Zsolt) meine Tests richtig?

Csaba Gabor aus Wien

2
Csaba

In Perl (siehe auch Zsolt Botykais Antwort ):

$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

Es ist eigentlich einfacher, es mit String-Manipulationen zu tun, anstatt mit regulären Ausdrücken:

bool isPalindrome(String s1)

{

    String s2 = s1.reverse;

    return s2 == s1;
}

Ich weiß, dass dies die Interviewfrage nicht wirklich beantwortet, aber Sie könnten damit zeigen, wie Sie eine bessere Art kennen, eine Aufgabe zu erledigen, und Sie sind nicht die typische Person mit einem Hammer, der jedes Problem als Nagel sieht . "

2
Dan

Hier ist meine Antwort auf Regex Golfs 5. Ebene (Ein Mann, ein Plan). Es funktioniert mit dem Regexp des Browsers für bis zu 7 Zeichen (Ich verwende Chrome 36.0.1985.143).

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

Hier ist eine für bis zu 9 Zeichen

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

Um die maximale Anzahl der Zeichen zu erhöhen, für die es funktionieren würde, würden Sie wiederholt . mit (?: (.).?\n?)?.

2
pbatey

hier ist der PL/SQL-Code, der angibt, ob die angegebene Zeichenfolge ein Palindrom ist oder reguläre Ausdrücke verwendet:

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

Wie von ZCHudson hervorgehoben, können Sie feststellen, ob etwas ein Palindrom ist, und zwar nicht mit einem normalen Regex.

Ich stimme überhaupt nicht zu Airsource Ltd wenn er sagt, dass "es nicht möglich ist", ist dies nicht die Art von Antwort, die der Interviewer sucht. Während meines Interviews komme ich zu dieser Art von Frage, wenn ich mich einem guten Kandidaten gegenüberstelle, um zu prüfen, ob er das richtige Argument finden kann, wenn wir ihm vorgeschlagen haben, etwas Falsches zu tun. Ich möchte niemanden einstellen, der versucht, etwas falsch zu machen, wenn er einen besseren kennt.

1
Nicolas

Nach der Theorie der Automaten ist es unmöglich, ein Paliandrom beliebiger Länge zu erreichen (da dies unendlich viel Speicher erfordert). IT IS MÖGLICH, um Paliandrome mit fester Länge abzugleichen. Man kann sagen, dass es möglich ist, einen Regex zu schreiben, der allen Paliandromen der Länge <= 5 oder <= 6 usw. entspricht, aber nicht> = 5 usw., wo die obere Grenze liegt unklar

1
Vijeenrosh P.W

In Ruby können Sie \b(?'Word'(?'letter'[a-z])\g'Word'\k'letter+0'|[a-z])\b verwenden, um palindrome Wörter wie a, dad, radar, racecar, and redivider abzugleichen. ps: dieser reguläre Ausdruck passt nur zu palindromischen Wörtern, die eine ungerade Anzahl von Buchstaben enthalten.

Mal sehen, wie dieser Regex mit dem Radar übereinstimmt. Die Word-Grenze\b stimmt mit dem Anfang der Zeichenfolge überein. Die Regex-Engine tritt in die Erfassungsgruppe "Word" ein. [a-z] stimmt mit r überein, das dann im Stapel für die Erfassungsgruppe "letter" auf Rekursionsstufe Null gespeichert wird. Nun tritt die Regex-Engine in die erste Rekursion der Gruppe "Word" ein. (? 'letter' [a-z]) stimmt überein und erfasst eine auf Rekursionsebene eins. Der Regex gibt die zweite Rekursion der Gruppe "Word" ein. (? 'letter' [a-z]) erfasst d auf Rekursionsstufe zwei. Während der nächsten zwei Rekursionen erfasst die Gruppe a und r auf den Stufen drei und vier. Die fünfte Rekursion schlägt fehl, da in der Zeichenfolge keine Zeichen vorhanden sind, die von [a-z] übereinstimmen. Die Regex-Engine muss zurückgehen.

Die Regex-Engine muss jetzt die zweite Alternative in der Gruppe "Word" ausprobieren. Das zweite [a-z] im Regex entspricht dem letzten r in der Zeichenfolge. Die Engine wird nun von einer erfolgreichen Rekursion beendet und geht eine Stufe zurück zur dritten Rekursion.

Nach dem Matching (& Word) erreicht die Engine\k'letter + 0 '. Die Rückreferenz schlägt fehl, da das reguläre Ausdrück-Modul das Ende der Betreffzeichenfolge bereits erreicht hat. Also geht es noch einmal zurück. Die zweite Alternative entspricht jetzt der a. Die Regex-Engine beendet die dritte Rekursion.

Die Regex-Engine hat erneut abgeglichen (& Word) und muss die Rückreferenz erneut versuchen. Die Rückwärtsreferenz gibt +0 oder die aktuelle Rekursionsebene an, die 2 ist. Auf dieser Ebene stimmt die Erfassungsgruppe mit d überein. Die Rückreferenz schlägt fehl, da das nächste Zeichen in der Zeichenfolge r ist. Die zweite alternative Übereinstimmung d.

Nun stimmt\k'letter + 0 'mit dem zweiten a in der Zeichenfolge überein. Das liegt daran, dass die Regex-Engine bei der ersten Rekursion angekommen ist, bei der die Erfassungsgruppe mit der ersten a übereinstimmt. Die Regex-Engine beendet die erste Rekursion.

Die Regex-Engine befindet sich jetzt wieder außerhalb der Rekursion. Dass diese Ebene die Erfassungsgruppe r gespeichert hat. Die Rückwärtsreferenz kann jetzt mit dem letzten r in der Zeichenfolge übereinstimmen. Da die Engine keine Rekursion mehr enthält, wird sie mit dem Rest der Regex nach der Gruppe fortgesetzt.\b stimmt am Ende der Zeichenfolge überein. Das Ende des Regex ist erreicht und das Radar wird als Gesamtübereinstimmung zurückgegeben.

1

Ich habe noch nicht den Repräsentanten, der inline kommentieren kann, aber der von MizardX bereitgestellte und von Csaba modifizierte reguläre Ausdruck kann weiter modifiziert werden, damit er in PCRE funktioniert. Der einzige Fehler, den ich gefunden habe, ist der Single-Char-String, den ich aber separat testen kann.

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

Wenn Sie es bei anderen Zeichenfolgen zum Scheitern bringen können, kommentieren Sie dies bitte.

1
Lil Devil

Das Beste, was Sie mit Regex tun können, bevor Sie keine Capture-Gruppen mehr haben:

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

Dies entspricht allen Palindromen mit bis zu 19 Zeichen.

Das programmatische Lösen für alle Längen ist trivial:

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

etwas, was Sie mit Perl machen können: http://www.perlmonks.org/?node_id=577368

1
Zsolt Botykai

Ich würde dem Interviewer erklären, dass die Sprache, die aus Palindromen besteht, keine reguläre Sprache ist, sondern kontextfrei.

Der reguläre Ausdruck, der allen Palindromen entspricht, wäre unendlich . Stattdessen würde ich vorschlagen, sich auf eine maximale Größe von Palindromen zu beschränken. oder wenn alle Palindrome benötigt werden, verwenden Sie mindestens einen bestimmten NDPA-Typ oder verwenden Sie einfach die einfache Umkehrung/Gleichheit.

1
Flame

mein $ 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

Eine geringfügige Verfeinerung der Methode von Airsource Ltd im Pseudocode:

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

In JavaScript erfolgt dies durch Eingabe

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

Sie können dies auch ohne Rekursion tun:

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

oder um die leere Zeichenfolge auszuschließen:

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

Funktioniert mit Perl, PCRE, Ruby, Java

Demo

\b([a-z])?([a-z])?([a-z])?\2\1\b/gi

Stimmt mit Palindromen mit 5 Buchstaben überein, wie z. B. Refer und Kajak. Dies geschieht mit (nicht gierigen) Übereinstimmungen von drei Buchstaben, gefolgt von den 2. und 1. übereinstimmenden Buchstaben.

Link zu regex101 Seite, die dies benutzt

0
Josh