それは私が答えることができなかったインタビューの質問でした:
正規表現を使用して文字列が回文であることを確認する方法は?
追伸「 指定された文字列がpalindromeであるかどうかを確認する方法? 」という質問がすでにあり、さまざまな言語で多くの回答が得られますが、正規表現を使用する回答はありません。
この質問に対する答えは、「それは不可能です」ということです。より具体的には、インタビュアーは、計算理論のクラスに注意を払っているかどうか疑問に思っています。
計算理論のクラスでは、有限状態マシンについて学びました。有限状態マシンは、ノードとエッジで構成されます。各Edgeには、有限のアルファベットからの文字で注釈が付けられています。 1つ以上のノードは特別な「受け入れ」ノードであり、1つのノードは「開始」ノードです。各文字が特定のWordから読み取られると、マシン内の特定のEdgeをトラバースします。受け入れ状態になった場合、マシンはその言葉を「受け入れる」と言います。
正規表現は、常に同等の有限状態マシンに変換できます。つまり、正規表現と同じ単語を受け入れ、拒否するものです(実際には、一部の正規表現言語では任意の関数が許可されますが、これらはカウントされません)。
すべての回文を受け入れる有限状態マシンを構築することは不可能です。証明は、任意の数のノードを必要とする文字列、つまり文字列を簡単に構築できるという事実に依存しています
a ^ x b a ^ x(例:aba、aabaa、aaabaaa、aaaabaaaa、....)
ここで、a ^ xはx回繰り返されます。これには少なくともx個のノードが必要です。これは、「b」を確認した後、回文であるかどうかを確認するためにx回カウントバックする必要があるためです。
最後に、元の質問に戻って、インタビュアーに、ある有限の固定長より小さいすべての回文を受け入れる正規表現を書くことができると伝えることができます。パリンドロームの識別を必要とする実世界のアプリケーションが存在する場合、ほぼ確実に任意の長さのパリンドロームは含まれません。したがって、この答えは、理論上の不可能性を実世界のアプリケーションと区別できることを示します。それでも、実際の正規表現は非常に長く、同等の4行プログラムよりもはるかに長くなります(読者にとって簡単な演習:回文を識別するプログラムを作成します)。
[〜#〜] pcre [〜#〜] エンジンは再帰的な正規表現をサポートしますが( Peter Kraussによる回答 を参照)、-で正規表現を使用することはできません [〜#〜] icu [〜#〜] 追加コードなしでこれを達成するためのエンジン(たとえば、Appleが使用)。次のようなことをする必要があります。
これはパリンドロームを検出しますが、ループが必要です(正規表現はカウントできないため、ループが必要になります)。
$a = "teststring";
while(length $a > 1)
{
$a =~ /(.)(.*)(.)/;
die "Not a palindrome: $a" unless $1 eq $3;
$a = $2;
}
print "Palindrome";
不可能です。回文は通常の言語では定義されていません。 (I DID計算理論で何かを学ぶを参照)
Perl正規表現の場合:
/^((.)(?1)\2|.?)$/
しかし、多くの人が指摘しているように、厳密にしたい場合、これを正規表現と見なすことはできません。 正規表現は再帰をサポートしていません。
あなたがどれだけ自信があるかに応じて、私はこの答えをします:
私は正規表現でそれをしません。正規表現の適切な使用法ではありません。
既に述べたように、箱から出して一般的なパリンドロームを検出する正規表現はありませんが、特定の長さまでパリンドロームを検出したい場合は、次のようなものを使用できます
(.?)(.?)(.?)(.?)(.?).?\5\4\3\2\1
StackOverflowには、「正規表現ですか?いいえ、サポートしていません。ca n'tサポートします。」などの答えがたくさんあります。
真実は、正規表現は通常の文法とは関係ないということです。現代の正規表現は、再帰やバランスなどの機能を備えていますグループ、およびそれらの実装の可用性は増え続けています(たとえば、Rubyの例を参照)。私の意見では、私たちの分野の正規表現はプログラミング概念ではないという古い信念に固執しています。もはや最適ではないWordの選択を嫌うのではなく、物事を受け入れて先に進む時が来ました。
これは Larry Wallからの引用 、Perl自身の作成者です:
(…)一般に、「正規表現」と呼ばれるものに関係しています。これは、実際の正規表現にわずかに関連しています。それでも、この用語はパターンマッチングエンジンの機能とともに成長しているため、ここでは言語の必要性と戦うつもりはありません。ただし、通常は「正規表現」(またはアングロサクソンの気分では「正規表現」)と呼びます。
そして、ここに ブログ投稿 by PHPのコア開発者の一人 があります:
記事はかなり長かったので、ここで主要なポイントの要約を示します。
- プログラマーが使用する「正規表現」は、形式言語理論の文脈における元来の規則性の概念とほとんど共通点がありません。
- 正規表現(少なくともPCRE)は、すべてのコンテキストフリー言語に一致できます。そのため、整形式のHTMLや他のほとんどすべてのプログラミング言語とも一致します。
- 正規表現は、少なくとも一部の状況依存言語に一致します。
- 正規表現のマッチングはNP完全です。そのため、正規表現を使用して、他のNP問題を解決できます。
とはいえ、これを使用して回文と正規表現を一致させることができます:
^(?'letter'[a-z])+[a-z]?(?:\k'letter'(?'-letter'))+(?(letter)(?!))$
...これは明らかに通常の文法とは関係ありません。
詳細はこちら: http://www.regular-expressions.info/balancing.html
現在、Perlで実行できます。再帰的参照の使用:
if($istr =~ /^((\w)(?1)\g{-1}|\w?)$/){
print $istr," is palindrome\n";
}
ほぼ最後の部分に基づいて変更 http://perldoc.Perl.org/perlretut.html
Rubyでは、名前付きキャプチャグループを使用できます。このように機能します-
def palindrome?(string)
$1 if string =~ /\A(?<p>| \w | (?: (?<l>\w) \g<p> \k<l+0> ))\z/x
end
それを試して、それは動作します...
1.9.2p290 :017 > palindrome?("racecar")
=> "racecar"
1.9.2p290 :018 > palindrome?("kayak")
=> "kayak"
1.9.2p290 :019 > palindrome?("woahitworks!")
=> nil
Regex Golfの第5レベル (男、計画)に対する私の答えです。ブラウザの正規表現で最大7文字で動作します(Chrome 36.0.1985.143)を使用しています)。
^(.)(.)(?:(.).?\3?)?\2\1$
最大9文字までの1つです
^(.)(.)(?:(.)(?:(.).?\4?)?\3?)?\2\1$
使用できる最大文字数を増やすには、。?を(? :(。)。?\ n?)?。
PCRE発現について(MizardXから):
/^((.)(?1)\2|.?)$/
テストしましたか?私のPHP 5.3 Winの下でXP Proで失敗します:aaaba
/^((.)(?1)*\2|.?)$/
何が起こっているのかと思いますが、外側のペアのキャラクターは固定されていますが、残りの内側のキャラクターは固定されていません。 「aaaba」と「aabaacaa」を誤って渡しますが、「aabaaca」では正しく失敗するため、これは完全な答えではありません。
これに対する修正があるかどうか、また、Perlの例(JF Sebastian/Zsoltによる)は私のテストに正しく合格しますか?
ウィーンからのCsaba Gabor
Perlの場合( Zsolt Botykai's answer も参照):
$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
}
実際には、正規表現よりも文字列操作を使用する方が簡単です。
bool isPalindrome(String s1)
{
String s2 = s1.reverse;
return s2 == s1;
}
これはインタビューの質問に実際に答えているわけではないことを理解していますが、それを使用してタスクを実行するより良い方法を知っていることを示すことができます。 」
回文を含む文字列を検出するためのシンプルで自明のアルゴリズム:
_ (\w)(?:(?R)|\w?)\1
_
rexegg.com/regex-recursion で、チュートリアルでその仕組みを説明しています。
これはどの言語でも正常に動作します。ここでは、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";
}
_
出力
_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
_
正規表現^((\w)(?:(?1)|\w?)\2)$
は同じ仕事をしますが、yes/notとして代わりに「含む」。
PS:「o」はパリンブロームではなく、「able-elba」ハイフン形式はパリンドロームではなく「ableelba」という定義を使用しています。命名definition1.
「o」と「able-elba」がパリンドロンの場合、definition2と命名します。
別の「パリンドローム正規表現」と比較すると、
^((.)(?:(?1)|.?)\2)$
_\w
_制限なしの上記のベース正規表現。「able-elba」を受け入れます。
^((.)(?1)?\2|.)$
( @ LilDevil )definition2を使用(「o」と「able-elba」を受け入れるため、認識も異なる「aaaaa」および「bbbb」文字列)。
^((.)(?1)\2|.?)$
( @ Markus )「kook」も「bbbb」も検出されない
^((.)(?1)*\2|.?)$
( @ Csaba )definition2を使用します。
注:比較するには、_$subjects
_に単語を追加し、比較する正規表現ごとに行を追加します。
_ 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";
_
これは、指定された文字列が回文であるかどうかを正規表現を使用しているかどうかを判断するPL/SQLコードです。
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;
ZCHudson で指摘されているように、パリンドロームのセットは通常の言語ではないため、通常の正規表現ではできないパリンドロームかどうかを判断します。
私は Airsource Ltd に全く反対します。彼が「それは不可能だ」とはインタビュアーが探している種類の答えではないと言っているときです。面接中、私は良い候補者に直面したときにこの種の質問に出くわし、彼に何か間違ったことを提案したときに彼が正しい議論を見つけることができるかどうかを確認します。私は彼がより良いものを知っていれば間違った方法で何かをしようとする誰かを雇いたくありません。
オートマトン理論から、どんな長さのパリデンドロームにもマッチさせることは不可能です(なぜなら、それは無限のメモリを必要とするからです)。しかし、IT IS固定長のパリアンドロメに一致する可能性。長さが<= 5または<= 6などのすべてのパリアンドロメに一致するが、>限界は不明です
Rubyでは、\b(?'Word'(?'letter'[a-z])\g'Word'\k'letter+0'|[a-z])\b
を使用して、a, dad, radar, racecar, and redivider
などの回文単語に一致させることができます。ps:この正規表現は、長さが奇数の回文単語にのみ一致します。
この正規表現がレーダーとどのように一致するかを見てみましょう。単語の境界\ bは、文字列の先頭で一致します。正規表現エンジンは、キャプチャグループ「Word」に入ります。 [a-z]はrに一致します。rは、再帰レベル0のキャプチャグループ "letter"のスタックに格納されます。これで、正規表現エンジンはグループ「Word」の最初の再帰に入ります。 (? 'letter' [a-z])は、再帰レベル1で一致し、キャプチャします。正規表現は、グループ「Word」の2回目の再帰に入ります。 (? 'letter' [a-z])再帰レベル2でdをキャプチャします。次の2回の再帰の間に、グループはレベル3と4でaとrをキャプチャします。 [a-z]が一致する文字列に文字が残っていないため、5番目の再帰は失敗します。正規表現エンジンはバックトラックする必要があります。
正規表現エンジンは、グループ「Word」内の2番目の代替手段を試す必要があります。正規表現の2番目の[a-z]は、文字列の最後のrと一致します。エンジンは、成功した再帰から終了し、1レベルから3番目の再帰に戻ります。
一致(&Word)した後、エンジンは\ k'letter + 0 'に達します。正規表現エンジンがサブジェクト文字列の終わりに既に到達しているため、後方参照は失敗します。そのため、もう一度バックトラックします。 2番目の選択肢は、aに一致します。正規表現エンジンは、3番目の再帰を終了します。
正規表現エンジンは再び一致(&Word)したため、後方参照を再試行する必要があります。後方参照は、+ 0または現在の再帰レベル(2)を指定します。このレベルでは、キャプチャグループはdと一致しました。文字列の次の文字がrであるため、後方参照は失敗します。再びバックトラックすると、2番目の選択肢はdと一致します。
これで、\ k'letter + 0 'は文字列の2番目のaと一致します。これは、キャプチャグループが最初のaと一致した最初の再帰に正規表現エンジンが戻ってきたためです。正規表現エンジンは最初の再帰を終了します。
正規表現エンジンは、すべての再帰の外側に戻りました。このレベルでは、キャプチャグループはrを保存しました。後方参照は、文字列の最後のrと一致するようになりました。エンジンはもはや再帰の内側にないので、グループの後の正規表現の残りの部分に進みます。\bは、文字列の最後に一致します。正規表現の最後に到達し、レーダーが全体一致として返されます。
インラインでコメントする担当者はまだいませんが、MizardXによって提供され、Csabaによって変更された正規表現は、PCREで動作するようにさらに変更できます。私が見つけた唯一の障害は単一文字の文字列ですが、それについては個別にテストできます。
/^((.)(?1)?\2|.)$/
他の文字列で失敗させることができる場合は、コメントしてください。
キャプチャグループを使い果たす前に、正規表現でできる最善の方法:
/(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?).?\9\8\7\6\5\4\3\2\1/
これは、長さが19文字までのすべての回文と一致します。
すべての長さをプログラムで解決するのは簡単です。
str == str.reverse ? true : false
#!/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";
perlでできること: http://www.perlmonks.org/?node_id=577368
再帰を使用せずに実行することもできます。
\A(?:(.)(?=.*?(\1\2?)\z))*?.?\2\z
または空の文字列を除外するには:
\A(?=.)(?:(.)(?=.*?(\1\2?)\z))*?.?\2\z
Perl、PCRE、Ruby、Javaで動作します
パリンドロームからなる言語は通常の言語ではなく、文脈自由であると面接官に説明します。
すべての回文に一致する正規表現は、無限です。その代わりに、彼が受け入れるパリンドロームの最大サイズに制限することをお勧めします。または、すべてのパリンドロームが必要な場合は、少なくとも何らかのタイプのNDPAを使用するか、単純な文字列反転/等しい手法を使用します。
私の$ 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";
}
擬似コードでのAirsource Ltdのメソッドのわずかな改良:
WHILE string.length > 1
IF /(.)(.*)\1/ matches string
string = \2
ELSE
REJECT
ACCEPT
JavaScriptでは、次のように入力して行われます
function palindrome(str) {
var symbol = /\W|_/g;
str = str.replace(symbol, "").toLowerCase();
var palindrome = str.split("").reverse("").join("");
return (str === palindrome);
}
\b([a-z])?([a-z])?([a-z])?\2\1\b/gi
Referやkayakなどの5文字の回文と一致します。これは、3文字の(欲張りでない)マッチングを使用して行われ、その後に2番目と1番目の一致した文字が続きます。