제가 대답 할 수없는 인터뷰 질문이었습니다.
정규식을 사용하여 문자열이 회문인지 확인하는 방법은 무엇입니까?
추신 이미 " 주어진 문자열이 회문인지 확인하는 방법? "이라는 질문이 있으며 다른 언어로 많은 답변을 제공하지만 정규 표현식을 사용하는 답변은 없습니다.
제가 대답 할 수없는 인터뷰 질문이었습니다.
정규식을 사용하여 문자열이 회문인지 확인하는 방법은 무엇입니까?
추신 이미 " 주어진 문자열이 회문인지 확인하는 방법? "이라는 질문이 있으며 다른 언어로 많은 답변을 제공하지만 정규 표현식을 사용하는 답변은 없습니다.
답변:
이 질문에 대한 대답은 "불가능하다"는 것입니다. 더 구체적으로 면접관은 당신이 계산 이론 수업에 관심을 기울 였는지 궁금해합니다.
계산 이론 수업에서 유한 상태 기계에 대해 배웠습니다. 유한 상태 머신은 노드와 에지로 구성됩니다. 각 모서리에는 유한 알파벳의 문자가 주석으로 표시됩니다. 하나 이상의 노드는 특수한 "수락"노드이고 한 노드는 "시작"노드입니다. 주어진 단어에서 각 문자를 읽을 때 우리는 기계의 주어진 가장자리를 횡단합니다. 수락 상태가되면 기계가 해당 단어를 "수락"한다고 말합니다.
정규식은 항상 동등한 유한 상태 기계로 변환 될 수 있습니다. 즉, 정규 표현식과 동일한 단어를 허용하고 거부하는 것입니다 (실제 세계에서 일부 정규 표현식 언어는 임의의 함수를 허용하지만 계산되지 않음).
모든 회문을 받아들이는 유한 상태 기계를 만드는 것은 불가능합니다. 증명은 임의로 많은 수의 노드가 필요한 문자열, 즉 문자열을 쉽게 만들 수 있다는 사실에 의존합니다.
a ^ xba ^ x (예 : aba, aabaa, aaabaaa, aaaabaaaa, ....)
여기서 a ^ x는 x 번 반복됩니다. 'b'를 본 후 회문인지 확인하기 위해 x 번 카운트해야하기 때문에 최소 x 개의 노드가 필요합니다.
마지막으로, 원래의 질문으로 돌아가서 면접관에게 유한 고정 길이보다 작은 모든 회문을 받아들이는 정규식을 작성할 수 있다고 말할 수 있습니다. 회문을 식별해야하는 실제 응용 프로그램이있는 경우 임의로 긴 응용 프로그램을 포함하지 않을 것이므로이 답변은 이론적 불가능 성을 실제 응용 프로그램과 구별 할 수 있음을 보여줍니다. 그래도 실제 정규 표현식은 동등한 4 줄 프로그램보다 훨씬 길다 (독자를위한 쉬운 연습 : 회문을 식별하는 프로그램 작성).
>=1.9
)에 대한 좋은 글이 있습니다
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";
불가능합니다. 회문은 일반 언어로 정의되지 않습니다. (참조, 나는 계산 이론에서 무언가를 배웠다)
Perl 정규식 사용 :
/^((.)(?1)\2|.?)$/
많은 사람들이 지적했듯이 엄격하게하려면 정규 표현식으로 간주 할 수 없습니다. 정규식 은 재귀를 지원하지 않습니다.
abababa
. PCRE 기반 정규식 엔진을 사용할 때 모든 입력에 대해 재귀로 작동하도록하는 것은 불가능합니다. Casimirs regex는 반복과 가변 상태를 사용하는 다른 접근 방식을 사용하며 매우 매력적입니다.
당신이 얼마나 확신하는지에 따라이 대답을 드리겠습니다.
정규 표현식으로는하지 않을 것입니다. 정규 표현식의 적절한 사용이 아닙니다.
StackOverflow는 "정규식? 아니요, 지원하지 않습니다. 지원할 수 없습니다 ." 와 같은 답변으로 가득 차 있습니다 .
진실은 정규 표현식이 더 이상 정규 문법 과 관련이 없다는 것입니다. 현대의 정규 표현식은 재귀 및 균형 그룹과 같은 기능을 특징으로하며 그 구현의 가용성은 계속 증가하고 있습니다 (예를 들어 여기에서 Ruby 예제 참조). 제 생각에는 우리 분야의 정규식이 프로그래밍 개념이 아니라는 오래된 믿음에 매달리는 것은 단지 비생산적입니다. 더 이상 가장 적절하지 않은 단어 선택 때문에 그들을 미워하는 대신, 우리가 사물을 받아들이고 나아가 야 할 때입니다.
다음 은 Perl 자체를 만든 Larry Wall 의 인용문입니다 .
(…) 일반적으로 실제 정규 표현식과 약간만 관련이있는 "정규 표현식"이라고 부르는 것과 관련이 있습니다. 그럼에도 불구하고이 용어는 패턴 매칭 엔진의 기능과 함께 성장 했으므로 여기서는 언어 적 필요성에 맞서 싸우지 않겠습니다. 그러나 저는 일반적으로 "정규식"(또는 앵글로색슨 분위기 일 때 "정규식")이라고 부를 것입니다.
다음 은 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을 기반으로 수정
루비에서는 명명 된 캡처 그룹을 사용할 수 있습니다. 그래서 이런 식으로 작동합니다.
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
실제로 정규 표현식보다는 문자열 조작으로 수행하는 것이 더 쉽습니다.
bool isPalindrome(String s1)
{
String s2 = s1.reverse;
return s2 == s1;
}
나는 이것이 인터뷰 질문에 실제로 답하지 않는다는 것을 알고 있지만, 당신은 그것을 사용하여 당신이 작업을 수행하는 더 나은 방법을 어떻게 아는지를 보여줄 수 있습니다. 그리고 당신은 모든 문제를 못처럼 보는 전형적인 "망치를 가진 사람이 아닙니다." . "
Regex Golf의 5 단계 (A man, a plan)에 대한 제 답변 입니다. 브라우저의 정규식 (Chrome 36.0.1985.143을 사용하고 있음)으로 최대 7 자까지 작동합니다.
^(.)(.)(?:(.).?\3?)?\2\1$
최대 9 자용입니다.
^(.)(.)(?:(.)(?:(.).?\4?)?\3?)?\2\1$
작동하는 최대 문자 수를 늘리려면 반복적으로 .? 와 (:? () \ n..??)? .
회문을 포함하는 문자열을 감지하는 간단하고 자명 한 알고리즘 :
(\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)$
은 동일한 작업을 수행하지만 "포함"대신 예 / 아니오로합니다.
추신 : "o"가 palimbrome이 아니라 "able-elba"하이픈 형식이 회문이 아닌 "ableelba"가있는 정의를 사용하고 있습니다. 이름 지정 definition1 .
"o"및 "able-elba"가 palindrone 인 경우 이름을 definition2로 지정 합니다 .
다른 "palindrome regexes"와 비교하면,
^((.)(?:(?1)|.?)\2)$
위의 기본 정규식은 \w
제한 없이 "able-elba"를 허용합니다.
^((.)(?1)?\2|.)$
( @LilDevil ) definition2를 사용하십시오 ( "o"및 "able-elba"를 허용하므로 "aaaaa"및 "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";
재귀를 사용하지 않고도 할 수 있습니다.
\A(?:(.)(?=.*?((?(2)\1\2|\1))\z))*?.?\2\z
단일 문자 허용 :
\A(?:(?:(.)(?=.*?((?(2)\1\2|\1))\z))*?.?\2|.)\z
Perl, PCRE와 함께 작동
Java의 경우 :
\A(?:(.)(?=.*?(\1\2\z|(?<!(?=\2\z).{0,1000})\1\z)))*?.?\2\z
PCRE 식 (MizardX에서 제공) :
/^((.)(?1)\2|.?)$/
테스트 했습니까? Win XP Pro의 내 PHP 5.3에서는 다음과 같은 오류가 발생합니다. aaaba 실제로 식 표현식을 약간 수정하여 다음과 같이 읽습니다.
/^((.)(?1)*\2|.?)$/
무슨 일이 일어나고 있는지는 외부 문자 쌍이 고정되어 있지만 나머지 내부 문자는 고정되어 있지 않다는 것입니다. 이것은 "aaaba"와 "aabaacaa"를 잘못 전달하지만 "aabaaca"에서는 제대로 실패하기 때문에 완전한 답이 아닙니다.
이에 대한 수정이 있는지 궁금합니다. 또한 Perl 예제 (JF Sebastian / Zsolt 작성)가 내 테스트를 올바르게 통과합니까?
비엔나에서 온 Csaba Gabor
Perl에서 ( 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
}
ZCHudson이 지적한 대로 회문 집합이 일반 언어가 아니기 때문에 회문이 일반적인 정규식으로 수행 할 수없는 것이 회문인지 확인합니다.
나는 Airsource Ltd 가 "불가능하다"는 것이 면접관이 찾고있는 종류의 대답이 아니라고 말했을 때 완전히 동의하지 않습니다. 인터뷰에서 나는 좋은 후보자를 만날 때 이런 종류의 질문을하는데, 우리가 그에게 잘못된 일을 제안했을 때 그가 올바른 주장을 찾을 수 있는지 확인합니다. 나는 그가 더 나은 것을 안다면 잘못된 방법으로 무언가를 시도 할 사람을 고용하고 싶지 않습니다.
perl로 할 수있는 일 : http://www.perlmonks.org/?node_id=577368
#!/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";
오토마타 이론에서 어떤 길이의 팔리 안드로메와도 일치하는 것은 불가능합니다 (무한한 양의 메모리가 필요하기 때문입니다). 그러나 고정 길이의 Paliandromes와 일치하는 것이 가능합니다. 길이가 <= 5 또는 <= 6 등인 모든 팔리 안드로메와 일치하지만 상한이 불분명 한 경우> = 5 등이 아닌 정규식을 작성할 수 있다고 말합니다.
루비에서는 사용할 수있는 \b(?'word'(?'letter'[a-z])\g'word'\k'letter+0'|[a-z])\b
등 회문 단어를 일치 a, dad, radar, racecar, and redivider
. ps :이 정규식은 홀수의 문자 길이 인 회문 단어와 만 일치합니다.
이 정규식이 레이더와 어떻게 일치하는지 봅시다. 단어 경계 \ b는 문자열의 시작 부분과 일치합니다. 정규식 엔진이 캡처 그룹 "단어"에 들어갑니다. [az]는 재귀 수준 0에서 캡처 그룹 "문자"에 대한 스택에 저장되는 r과 일치합니다. 이제 정규식 엔진은 그룹 "단어"의 첫 번째 재귀를 입력합니다. (? 'letter'[az])는 재귀 수준 1과 일치하고 캡처합니다. 정규식은 그룹 "단어"의 두 번째 재귀를 입력합니다. (? 'letter'[az])는 재귀 수준 2에서 d를 캡처합니다. 다음 두 번의 재귀 동안 그룹은 레벨 3과 4에서 a와 r을 캡처합니다. 문자열에 [az] 일치 할 문자가 없기 때문에 다섯 번째 재귀가 실패합니다. 정규식 엔진은 역 추적해야합니다.
정규식 엔진은 이제 그룹 "단어"내에서 두 번째 대안을 시도해야합니다. 정규식의 두 번째 [az]는 문자열의 마지막 r과 일치합니다. 이제 엔진이 성공적인 재귀에서 빠져 나와 한 단계 뒤로 세 번째 재귀로 이동합니다.
일치 (& 단어) 후 엔진은 \ k'letter + 0 '에 도달합니다. 정규식 엔진이 이미 제목 문자열의 끝에 도달했기 때문에 역 참조가 실패합니다. 그래서 다시 한 번 역 추적합니다. 두 번째 대안은 이제 a와 일치합니다. 정규식 엔진은 세 번째 재귀에서 종료됩니다.
정규식 엔진이 다시 일치 (& word)되었으며 역 참조를 다시 시도해야합니다. 역 참조는 +0 또는 현재 재귀 수준 (2)을 지정합니다.이 수준에서 캡처 그룹은 d와 일치합니다. 문자열의 다음 문자가 r이기 때문에 역 참조가 실패합니다. 다시 역 추적하면 두 번째 대안이 d와 일치합니다.
이제 \ k'letter + 0 '은 문자열의 두 번째 a와 일치합니다. 이는 정규식 엔진이 캡처 그룹이 첫 번째 a와 일치하는 첫 번째 재귀에 다시 도착했기 때문입니다. 정규식 엔진은 첫 번째 재귀를 종료합니다.
이제 정규식 엔진이 모든 재귀 외부로 돌아 왔습니다. 이 수준에서 캡처 그룹은 r을 저장했습니다. 역 참조는 이제 문자열의 마지막 r과 일치 할 수 있습니다. 엔진이 더 이상 재귀 내부에 있지 않기 때문에 그룹 이후의 나머지 정규식을 진행합니다. \ b는 문자열의 끝에서 일치합니다. 정규식의 끝에 도달하고 레이더가 전체 일치로 반환됩니다.
다음은 주어진 문자열이 회문인지 정규 표현식을 사용하지 않는지를 알려주는 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;
my $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";
}
이 정규식은 공백, 탭, 쉼표 및 따옴표를 무시하고 최대 22 자의 회문을 감지합니다.
\b(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*\11?[ \t,'"]*\10|\10?)[ \t,'"]*\9|\9?)[ \t,'"]*\8|\8?)[ \t,'"]*\7|\7?)[ \t,'"]*\6|\6?)[ \t,'"]*\5|\5?)[ \t,'"]*\4|\4?)[ \t,'"]*\3|\3?)[ \t,'"]*\2|\2?))?[ \t,'"]*\1\b
여기에서 플레이하세요 : https://regexr.com/4tmui