2009/03/25 09:00

두 문자열에서 중복되는 부분 찾기

sng2nara님의 낚시에 파닥파닥 낚여서 만들어보게 되었다.

문제는 다음과 같은 문자열들이 있을때

my $common = 'ABC';
my @str;
$str[0] = 'AA BBCD ABC bbbc aaaa';
$str[1] = 'BBAA BBCD ABC bbb';
$str[2] = '123BBAA BBCD ABC bbFSKFSJLK';
$str[3] = '239I2P3IBBAA BBCD ABC bbb FLSKJFLSKJDFLJS';
$str[4] = 'ABC bbb';


@str의 문자열들에서 ABC를 포함하면서 공통되는 가장 큰 문자열 셋을 찾는 것.
이경우네는 ABC bbb 가 된다.

물론 효율을 따지면,, 한글자한글자 비교해야 겠지만
일단 효율 무시, 정규표현식을 써보자

두 문자열의 공통된 부분을 찾는 건 꽤 쉽다. 일단 두 문자열을 합친 다음에 다음처럼 만들면 된다.

($max_common) = (($str[0].$str[1]) =~ /^.*?(.*$common.*).*?\1.*?$/)


자세한 설명은.. 생략
한가지만 얘기하자면 첫번째 ()에서 찾은것이 \1이 된다는 정도.
사실 경우에 따라 몇가지 버그를 가지고 있기는 하다
한 문자열에 ABC가 여러개 있다면 우리가 원하는 대로 나오지 않을 수 있다.
이경우에는 한 문자열에 ABC가 하나만 있다고 가정하자. (혹은 그렇게 만들 루틴을 가지고 있다고)

그럼 여러개의 문자열에 대해서는 어떻게 할까?
여러개의 문자열을 모두 합쳐서 찾으면 좋겠지만, 그럼 위와 같은 문제가 다시 발생한다.
어쩔수 없이 정규표현식을 문자열 개수만큼 돌려야 한다.

my $max_common = shift @str;
($max_common) = (($max_common.$_) =~ /^.*?(.*$common.*).*?\1.*?$/) for @str;
print $max_common;

우왕 굿.

그러나 효율에는 심각한 문제가 있다.
이 정규표현식의 경우는 backtracking을 이용해서 꽤 과도하게 검색을 하기 때문에 긴 문자열에는 적합하지 않다.

테스트로 17kbytes 문서 두개를 이용해서 비교해 봤는데 10분 동안 돌린후에 테스트를 포기했다.
문자열의 길이에 대해 어느정도 효율을 가지는지 테스트해보고 싶지만,, 귀차니즘으로 생량.

그것은 그대의 손에!!















크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 0
2009/03/24 18:41

일전한 글자수의 단어 세기

http://kldp.org/node/104031 를 보고 aero님과 얘기하다가 시작되었다.

문제의 내용(사실은 문제의 일부)는 다음과 같은 문자열에서 AA, AT, AG, AC....의 개수를 세는것

ATCGATCGATCGATAAAC
GATCTAGCTAGCATAACC

앞에서 부터 두글자씩 세는건 아주 단순하다.

$count{$_}++ while $str =~ /(..)/g


하지만 aero님의 문제제기,

앞에서 부터 2글자씩 세는 것이 아니라 임의의 두글자라면 어떻하죠?

먼저 aero님의 구현은
 while ( $str =~ /(..)/g ) {
    $count{$1}++;
    pos $str -= 1;
}
오 멋지다.  마지막으로 두글자의 다음 위치인 pos($str) 의 위치를 뒤로 한글자 돌려서 다시 검색한다.
2글자가 아니라 N글자라면 pos $str -= N-1 이라고 해주고 정규표현식을 조금 바꿔주면 끝.

나의 구현도 매우 비슷하다. 다만 perl 정규표현식의 \G앵커를 사용했다는 정도

pos($str) = 1;
while ( $str =~ /(.\G.)g ) {
    $count{$1}++;
}


처음 pos를 정해놓고 현재 pos위치를 의미하는 \G앵커를 이용하여 검색.. (자세한 내용은 perldoc perlre)
N글자의 단어를 찾으려면 pos위치를 N-1로 하고 /(.{$N-1}\G.)/g 로 바꾸면 끝..
N글자에서는 aero님의 코드가,, 몇글자 짧아지긴 하겠군요

글자수도 거의 같고, 효율도 거의 비슷하겠군.

원라이너 용으로 내 코드를 정리해 보면

perl -nle'pos=1;$c{$1}++ while /(.\G.)/g}{print"$_\t$c{$_}"for sort keys %c' filename


되겠다.

추가로 Luz♡lunA 님의 말씀

 <Luz♡lunA> [hanIRC] c식으로 하면 그냥 한글자씩 뽑으면서 prev를 저장해가면서 진행하면.....

맞습니다.
이게 정석인거죠.

크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 6
2008/06/05 17:45

정규표현식과 코드골프에 관한 IRC로그

어제 IRC에서 나온 뼈가되고 살이되는 펄 얘기 입니다.
주로 간단한 정규 표현식으로 뭘 할 수 있는지 에 관한 얘기죠.
재미있는 로그라 모두 긁어왔는데 좀 기네요. 132줄!!

more..


하지만 읽기 귀찮으신 분들을 위해서 정리도 해드리는 센스!!
두개의 문제가 나왔는데

1.  연숙된 문자열에서 숫자의 앞뒤에 공백 넣기

"11aa11bbcc222d" 를 "11 aa 11 bbbb 222 d" 로 바꾸는 거죠.
간단한 문제이니 만큼 답도 간단합니다.
s/(\d+)/ $1 /g

또는
perl -pe 's/(\d+)/ $1 /g'


2. perldoc 문서 이름을 위키네임으로 바꾸기

twiki에 perldoc관련 플러그인을 만들다가 나온 문제입니다.
perldoc은 perlsub, perlreftut처럼 perl로 시작하고 모두 소문자로 되어있는데
이걸 PerlSub, PerlReftut 처럼 perl의 P와 perl 뒤으 문자 하나 총 2개의 대문자를 가진 문자열로 바꾸는 문제입니다.
다음 코드들에서 print가 나왔다가 안나왔다가 하는 이유는 저는 문자열 반환이 목적이었고, saillinux님과 aero님은 출력이 목적이었기 때문입니다. 알아서 잘 해석해주세요^^
 문자열이 $_에 저장되어 있다고 가정하면
처음에 나온 코드는  saillinux 님의
print "Perl".join '', map {ucfirst}split(/perl/, "$_");

오 훌륭하군요,
약간 바꿔보면
 print join '', map ucfirst, /(perl)(.*)/;

이렇게도 가능하지요.

제가 정규표현식을 써서 만든 코드는 
s/(perl)(.*)/ucfirst$1.ucfirst$2/e


이걸 코드 골프용으로 고치면,, 보기에는 지저분 하지만
/l/;ucfirst$`.'l'.ucfirst$

이렇게 만들 수 있습니다.

이걸 보여드렸더니 aero님께서 다른 아이디어를 제시하시더군요
print map "\u$_",/(perl)(.*)/


호~~ \u가 있었군요. 저는 \U는 알고 있었는데, \U는 한문자에만 적용되는 것이 아니라 \E와 쌍을 이루는 것이어서 못쓰고 있었거든요. 그래서.. 잽싸리 저도 코드를 바꿔봤습니다.
/l/;"\u$`l\u$'"

오호. 마지막 코드를 보신 aero님께서 한 말씀 날려주시더군요
ㅂㅌ

추가:2008/06/08
오늘 아침에 나온 아이디어입니다.
s/(perl|.*)/\u$1/g

우왕~~


코드에 대한 자세한 설명은 saillinux 님께서 해주시리라 믿습니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 1 Comment 1