2007. 11. 19. 13:53

map, grep 그리고 //g의 함정

문제는 펄스터디 모임중 다음 코드에서 시작되었다.
#!/usr/bin/perl
@str = qw(test test1 test28 test88 test102 test9209);
@grep_ret = grep m/test[0-9]{2}/g, @str;
print("grep_ret : @grep_ret\n");

@str = qw(test test1 test28 test88 test102 test9209);
@map_ret = map m/test[0-9]{2}/g, @str;
print("map_ret : @map_ret\n");
결과 :
grep_ret : test28 test88 test102 test9209
map_ret : test28 test88 test10 test92


이 코드는  map과 grep의 차이를 잘 보여주는 코드이다. 그런데 문제는 두번째 @str을 주석처리 하자 map의 결과가 사라졌다는 것이다.
#!/usr/bin/perl
@str = qw(test test1 test28 test88 test102 test9209);
@grep_ret = grep m/test[0-9]{2}/g, @str;
print("grep_ret : @grep_ret\n");
#@str = qw(test test1 test28 test88 test102 test9209);
@map_ret = map m/test[0-9]{2}/g, @str;
print("map_ret : @map_ret\n");
결과 :
grep_ret : test28 test88 test102 test9209
map_ret :
처음에는 매우 의아한 결과라고 생각했지만 찬찬히 살펴보자 //g의 문제라는 것을 알았다. 첫 grep 에서 g에의해서  매치 포지션이 뒤로 이동해 버린 것이 문제였다.  이를 증명 및 해결하기 위해서 다음 코드를 사용했다.

#!/usr/bin/perl
@str = qw(test test1 test28 test88 test102 test9209);
@grep_ret = grep m/test[0-9]{2}/g, @str;
print("grep_ret : @grep_ret\n");
map{pos=0}@str;
@map_ret = map m/test[0-9]{2}/g, @str;
print("map_ret : @map_ret\n");
결과 :
grep_ret : test28 test88 test102 test9209
map_ret : test28 test88 test10 test92
pos를 이용해서 매치 포지션을 0으로 바꾸자 우리가 원하던대로 코드가 작동했다.
이에 대해 몇가지 얘기들이 있었고 본인이 잘못알고 있던 것을 아는 척하다가(grep에서 반환값이 리스트라는. 엉터리 정보를 주장했다.) grep과 map의 순서를 바꾸자 더 황당한 일이 벌어졌다.
#!/usr/bin/perl
@str = qw(test test1 test28 test88 test102 test9209);
        @map_ret = map m/test[0-9]{2}/g, @str;
print("map_ret : @map_ret\n");
  @grep_ret = grep m/test[0-9]{2}/g, @str;
 print("grep_ret : @grep_ret\n");
 @map_ret = map m/test[0-9]{2}/g, @str;
 print("map_ret : @map_ret\n");
결과 :
map_ret : test28 test88 test10 test92
grep_ret : test28 test88 test102 test9209
map_ret :
이럴수가, 왜 map은 grep에 영향을 주지 않았는데 grep는 map에 영향을 주는 걸까.

이제 문제를 일으킨 //g와 map, grep의 특성들을 자세히 살펴보자.
//g 는 하나의 문자열에 대해서 여러번(global) 매치를 실행하기 때문에 매치가 이루어질때마다 매치가 이루어진 다음의 위치를 메모리에 저장한다. 이 위치는 pos함수로 확인할 수 있고 또 pos에 값을 대입해서 이 위치를 수정할 수 있다. 그런데 매치를 다 찾아서 더이상 매치가 없는 문자열에 다사  //g를 적용하면 어떻게 될까. 예를 통해 확인해보자.
#!/usr/bin/perl
use strict;
my $str="test12test34";
my $count;
$count = $str=~/test/g; print $count, "\t", pos $str, "\n";
$count = $str=~/test/g; print $count, "\t", pos $str, "\n";
$count = $str=~/test/g; print $count, "\t", pos $str, "\n";
$count = $str=~/test/g; print $count, "\t", pos $str, "\n";

결과:
1       4          # 매치가 되었다는 뜻의 1과 다음 매치위치인 "1"의 위치 4가 출력되었다.
1       10        # 마찬가지로 1과 다음 매치위치인 "3"의 위치인 10이 출력되었다.
                    # 더이상 매치할 것이 없기때문에 실패의 의미인  undef가 출력되었고 pos역시 undef.
1       4          # 바로 이거다. 한번더 //g가 호출되면 처음부터 다시 매치를 시작한다.

//g를 리스트에 저장하면 어떻게 될까.
#!/usr/bin/perl
use strict;
my $str="test12test34";
my @result;
@result = $str=~/test/g; print @result, "\t", pos $str, "\n";
@result = $str=~/test/g; print @result, "\t", pos $str, "\n";

결과
testtest
testtest

//g는 모든 매치를 실행해서 매치된 내용을 @list에 집어넣었고 매치가 모두 이루어졌기 때문에 pos는 undef를 반환했다. 앞의 결과와 비슷하게  pos가 undef가 된후 다시 매치를 실행하자 처음부터 매치가 이루어진다.
그렇다면 앞의 코드를 다시 인용해 모든 문제를 짚어보자.

#!/usr/bin/perl
@str = qw(test test1 test28 test88 test102 test9209);
        @map_ret = map m/test[0-9]{2}/g, @str;
print("map_ret : @map_ret\n");
  @grep_ret = grep m/test[0-9]{2}/g, @str;
 print("grep_ret : @grep_ret\n");
 @map_ret = map m/test[0-9]{2}/g, @str;
 print("map_ret : @map_ret\n");
결과 :
map_ret : test28 test88 test10 test92
grep_ret : test28 test88 test102 test9209
map_ret :
첫번째  map에서 map은 list를 반환한다. 따라서 //g는 리스트 컨텍스트로 작동하고 모든 매치된 내용을 리턴 한 후에 매치 포지션을 초기화 시킨다. 따라서 grep은 아무 문제없이 작동한다. 하지만 grep은 스칼라 데이터를 반환하기 때문에 매치 위치를 뒤로 옮겨버립니다.(사실 이말은 틀린 말이네요. grep의 반환값이 아니라 grep이 입력된 리스트 요소들에 대해서  True/False를  판단하기 위해서  마지막  expression의  스칼라 값을  사용한다는 것이 맞겠지요. 예를 들어 극단적으로 grep{(1,2,3,0)}@InputLIst 의 경우에 첫번째 스터디에서 공부했던 것 처럼 (1,2,3,0) 의 마지막 값인 0을 사용해 모두 False라고 판단하게 됩니다.) 따라서 마지막 map에서는 이동된 매치 포지션을 가지고 매치를 실행하게 되고 더이상 매치 할 것이 없으므로 undef만 줄기차게 반환하게 되겠지요. 다음 예제를 보면 좀더 분명해 지리라 봅니다. 매치의 정규표현식과 데이터를 살짝 바꿔봤습니다.
#!/usr/bin/perl
@str = qw(1and2 3and4and5 6 7 8 9 nodigit);
        @map_ret = map m/\d/g, @str;
print("map_ret : @map_ret\n");
  @grep_ret = grep m/\d/g, @str;
 print("grep_ret : @grep_ret\n");
 @map_ret = map m/\d/g, @str;
 print("map_ret : @map_ret\n");
결과 :
map_ret : 1 2 3 4 5 6 7 8 9
grep_ret : 1and2 3and4and5 6 7 8 9
map_ret : 2 4 5
두번째 grep에서 매치 위치를 옮긴 후에도 매치될 내용이 남아 있는 요소 에 대해서는 마지막 map에서 모두 매치해서 반환해 버립니다.

몇가지 요소가 복합적으로 얽혀있는데다가 쭈루룩 쓴 글이라 좀 복잡하네요.
요약하자면
1. //g는 매치가 이루어질때마다 다음 매치를 위해 매치위치를 옮긴다.
2. 매치 위치는 pos를 이용해 알아내거나 수정할 수 있다.
3. 스칼라문맥에서 //g는 호출될때마다 매치성공여부를 반환하고 매치위치를 옮긴다. 매치가 다 이루어진 후에는 //g가 한번더 호출되면 undef를 반환하고 매치 위치를 undef로 설정한다(매치 위치가 문자열의 처음으로 재설정 되는 것과 같은 효과). 따라서 다음 //g 는 문자열의 처음부터 다시 시작된다.
4. 리스트 문맥에서 //g는 가능한 모든 매치를 실행하여 매치된 값들의 리스트를 반환한다. 그리고 매치 위치도 undef로 바꾼다. 따라서 리스트 문맥 이후에 //g는 문자열의 처음부터 매치를 시작한다.
5. grep에서 //g 스칼라 문맥이고 map에서 //g는 리스트 문맥이다. 따라서 grep에서는 매치 위치가 바뀌에 되고 map이후에는 매치위치가 문자열의 처음이 된다.

끝입니다.^^

---
글을 올리고 나서 너무 미진한 부분이 많이 보여서 수정및 추가를 해야하나 하고 고민하고 있었는데 마침 aero님께서 제 고민을 한방에 날려주셨습니다. 감사. 역시 고수의 글은 무섭군요!!





'PerlMania' 카테고리의 다른 글

3번째 펄마니아 스터디 모임  (5) 2007.11.18
펄마니아 스터디 후기  (9) 2007.11.04
2007. 11. 18. 16:35

3번째 펄마니아 스터디 모임

이번주 스터디도 연대에서 있었습니다. 아침부터 지갑을 잃어버리는 등의 악재가 겹쳐서 4시에 딱 맞춰 가는 바람에 범규가 혼자 고생이 많았네요.
예상대로(?) 뜨거운 스터디 시간이었습니다. 4시 30분경에 시작해서 9시 30분까지 5분쉬고 다섯 시간을... 술자리를 위해서 말리고 싶은 마음도 있었지만 발표하시는 분들의 열기에 말도 꺼낼 수가 없었습니다.(사실은 저의 소심함이 가장 큰 원인이었죠.)
지난 시간부터 문제가 되었던 grep,map 문제는 제가 정확히 모르고 떠벌이다가 창피를 당하기도 했습니다.
그러나 기대하시라.. 해결했습니다. 관련 포스트를 따로 올리기로 하지요. 포인트는  grep의 반환값은 스칼라이고 map의 반환값은 리스트라는 것입니다.

스터디가 끝나고 지지난 주부터  계획만하던 "스터디 회원 사진찍기"를 마침내 실행에 옮겼습니다.
신비주의를 원하시는 분들이 있을까봐 걱정했었는데 다행히 모두들 찬성하시더군요.
아쉽게도 이번에 참석하지 못하신 혁진님과 성일님(맞나요?)께서는 다음주를 기약하셔야 겠습니다.
사진은 왼쪽 위에 키가 제일 크신 분부터 시계방향으로 박민영님, 황찬님(고기스님), 이성찬님, 김범규님(bkkim), 임도형님, 저 장범수(pung96), 정현기님(simryang), 김대호님, 김도형님(keedi) 입니다.
임도형님은 이번에 처음 참석하셨는데 내공이 남다르시더군요. 특히 사근사근 설명해 주시는것이 전직이 의심스워지네요(선생님?^^).
사용자 삽입 이미지

펄마니아 스터디 사진


아쉽게도 너무 늦게 끝난데다가 다들 급한 일이 있으셔서 뒷풀이는 저, 김도형님, 김대호님, 이성찬님, 황찬님 이렇게 5명이 진행했습니다. 주종목은.. 역시 제가 좋아하는 삼겹살에 소주였습니다. 도형님은 자전거를 타고 오신 관계로 맥주만 드셨습니다. 막판에 결심을 바꾸시고 소주로 바꾸시려고 했는데 제가 술취하면 말이 많아지는 관계로 이런 저런 얘기에 열을 올리느라 신경을 못써드려서 그냥 맥주만 드셨네요.
무사히 보내 드려서 죄송합니다.
다음번에는 꼭 소주를 열심히 권해드리겠습니다.용인까지 자전거 타고 가신다던데..  대단하십니다.
저는 다른 모임이 있어서 2차를 갔다가 너무 취해 버리는 바람에 쓰러지기 일보직전에 가방을 움켜쥐고 인사도 없이 집으로 뛰어갔다가 선배들에게 부지런히 욕먹는 중입니다.
다들 수고하셨습니다.

'PerlMania' 카테고리의 다른 글

map, grep 그리고 //g의 함정  (3) 2007.11.19
펄마니아 스터디 후기  (9) 2007.11.04
2007. 11. 4. 15:40

펄마니아 스터디 후기

어제(10월3일) 펄마니아 스터디가 있었습니다.
권혁진님, 김대호님, 김도형님, 박민영님, 저, 제 후배 2명, 그리고 한분(이름이 잘 생각이...) 이렇게 8분이 오셨습니다. 맞나요?  카메라를 가져가지 않아서 사진은 하나도 못찍었네요.

모임 장소인 숭실대가 신촌에서 가까울 거라고 마음대로 상상해 버렸다가 모임시간이 3시에서 20분이나 늦게 도착했습니다. 게다가 "웨스터민스터 관" 을 찾느라고 이리 저리 해매고, 다른 건물에 올라갔다가 다시 내려오는 일을 반복하느라 굉장히 늦어버렸는데 다행히(?) 세미나실 빌리는 문제로 모임이 한시간 가량 지체되어서 마음속으로 만들었던 수많은 변명들을 하지 않아도 되었습니다.
모임장소 확보하시느라 고생하신 도형님께 박수를!!

"펄 제대로 배우기 2판"이 교재여서 금방 스터디가 끝날 줄 알았는데 참석자 분들의 뜨거운 열의로 결국 4챕터를 진행하는데 3시간이 넘는 시간이 걸렸죠.  특히 도형님과  혁진님의 물흐르는 듯한  친절하고 자세한 설명은  매우 감동적이었습니다.

스터디가 다 끝나고 나서 다음 스터디 계획을 좀 세우고 나서는 헤어졌습니다.
원래 첫 스터디고 해서 술 한잔 하자고 말하려고 했는데 도형님께서 먼저 "책 한권 끝날때 마다 책거리 하자"고 말씀하시는 통해 원래 소심함 pung96은 쑥 들어가 버렸습니다.
오면서 후회도 되고 해서.. 그냥 신촌에서 후배 둘과 함께 삼겹살을...

펄 제대로 배우기 교재를 쓴다고 해서 재밌을까 걱정도 되었었는데 생각보다 훨씬 재미있었습니다.
어제 정말 재대로 배운것이 두가지가 있습니다.
    1. 역시 공부는 혼자해서는 한계가 있다.
    2. 아.. 러닝펄에서 내가 모르는게 이렇게 많았구나.



'PerlMania' 카테고리의 다른 글

map, grep 그리고 //g의 함정  (3) 2007.11.19
3번째 펄마니아 스터디 모임  (5) 2007.11.18