반복 실행 - for, foreach, while, until

A. for

홍길동과 108장사들의 아름다운 이야기를 기억하십니까 ? 이런 내용이었지요.

... 전략
for( $i = 0; $i < 109; $i++ )
{
  print "따악 ";
}  # 109번을 따악 따악 따악 따악 따악 따악 따악 !!!
... 후략
for제어문은 뒤따라 오는 {}블록내의 명령들을 일정한 횟수만큼 반복하여 실행시켜주는 제어문입니다.
for 다음의 괄호 안의 내용은 ; 기호에 의하여 3부분으로 나뉘어져 있습니다.
for( 초기화; 조건; 조건 비교 후 취하는 행동 )
{
  매번 해야할 일;
}
홍길동의 예제를 보면, $i라는 일반 변수를 0으로 초기화한 다음에 $i의 값이 109보다 작은지를 검사하고 만일 작으면 블록 안의 내용을 실행한 후 $i의 값 을 1만큼 증가시킵니다. 그리고 다시 $i의 값을 조사, 실행, 증가를 $i의 값 이 109보다 같거나 클 때까지 반복합니다. 홍길동의 예제를 보시고 또 다른 예제들을 만들어 보십시오.

숙제를 내 드릴까요 ?
참새 10마리가 전깃줄 위에서 노래합니다.
첫째 참새는 "짹", 둘째는 "째잭", 셋째는 또 "짹".... 열째는 "째잭".
for loop와 % 연산자 그리고 if를 이용하여 노래를 시켜보세요. 한글을 띄우기 귀찮으면 zag, zazag 해도 되겠지요.

홍길동의 이야기를 이렇게 쓸 수도 있습니다.

for ( 1..109 )
  { print "딱 "; }
간단하죠. 이 형태는 어떤 목록(1..109는 1에서 109까지의 목록입니다.)의 각 요소마다 한번씩 일정 명령문을 반복하는 제어방법을 보여줍니다.

이런 것도 있습니다.

for ( 1..10, " hello ", "world ", "again\n" )
  { print; }
결과는 "12345678910 hello world again"으로 출력됩니다. 이건 좀 이상하죠 ? 이것만 알아두세요. 일반변수(scalar)가 있어야할 자리에 아무것도 보이지 않으면 대개는 그 자리에 "$_"라는 변수가 있는 것으로 가정이 됩니다. for( 1..109 )에서는 $_가 1로 초기화되어 109까지 계속하여 2, 3, 4로 한번씩 할당됩니다. 그렇게 한번씩 할당될 때마다 print "딱"이 실행 되는것이고, for ( 1..10, " hello ", "world ", "again\n" )에서는 1에서 10까지 (점이 두 개 보이시죠 ? 1..10 ) 그 다음에는 " hello ", "world ", "again\n"이 한번씩 $_에 할당됩니다. 실행되는 부분에서는 또 print에 넘겨지는 인자가 없으므로 $_가 있는 것으로 가정하는 것이지요. print;를 print($_);로 바꾸어 써도 결과는 같이 나옵니다.
할 수 있는 한, 직접 실행을 시켜 보세요. 혹시 제가 거짓말을 하고 있는지도 모르지 않습니까 ?

B. foreach

배열의 마지막 참조번호는 $#array_name으로 알 수 있다고 했습니다.

for ( $elem = 0; $elem <= $#myArray; $elem++ )
{
  print $myArray[$elem];   # 각 요소는 일반변수이므로
                           # @가 아닌 $를 붙인다 했습니다.
}
위의 예제는 배열 @myArray의 각 요소를 출력하는 문장입니다. foreach는 이러한 상황에서 세상살이를 좀 더 넉넉하게 해 줍니다.
foreach $elem ( 0..$#myArray )
{
  print $myArray[$elem];   # 각 요소는 일반변수이므로
                           # @가 아닌 $를 붙인다 했습니다.
}
foreach는 목록으로 생각되는 것들에 적용할 수 있습니다. 배열, hash, 텍스트 파일 등등..

hash (associative array)의 예를 들어 보도록 하지요.

# hash.pl

%myhash = (
  "apple", 3,
  "pear", 10,
  "banana", 4,
  "monkey", 2,
  );

$total = 0;
foreach $eachKey ( keys %myhash ) # key라는 함수는 hash의 '키'들
                                  # 만을 모아 배열로 묶어줍니다.
{
  print "$eachKey\t", $myhash{$eachKey}, "\n";
  $total += $myhash{$eachKey};
}
print "\nTotal\t$total\n";   # \t는 탭입니다.
출력이 어떻게 되는지 직접 해 보시기 바랍니다. 백견이 불여 일행이라니까요... 위의 foreach $eachKey ( keys %myhash )가 헷갈리는 분들을 위하여 간단히 말씀드립니다. key라는 함수는 hash의 '키'들( 위 예문의 경우 apple, pear, banana, monkey가 key입니다.)만을 모아 배열로 묶어줍니다. $myhash{$eachKey}는 각 키에 대한 값( 위 예문의 경우 각각 3, 10, 4, 2 )을 리턴합니다. 쉽게 풀어 놓으면,
...
@arrayOfKey = keys %myhash;
foreach $eachKey ( @arrayOfKey )
...
와 같습니다. 배열에서 사용하는 foreach와 똑같은 내용이지요.

foreach는 심지어 파일내의 각 줄에 대해서도 적용이 됩니다 !

# mytype3.pl

if( $#ARGV < 0 )
  { die "Supply a file name, please.\n"; }
if( $#ARGV > 0 )
  { die "Too many parameter.\n"; }

$fileName = shift( @ARGV );

if( -d $fileName )
  { die "$fileName is a directory.\n"; }

-e $fileName || die "$fileName is not exist.\n";

-T $fileName || die "$fileName is not a text file.\n";

open( fileHandle, $fileName ) || die "Cannot open $fileName.\n";
foreach $aLine (<fileHandle>)
{
  print $aLine;
}
close( fileHandle );  # 꼭, 꼭, 꼭, 꼭 닫읍시다.
참 편리한 물건이지요 ? 또 for ( 1..10, " hello ", "world ", "again\n" )의 for를 foreach로 바꾸어도 같은 결과를 낳습니다.

C. while

어떤 조건식이 참인 동안 뒤따라 오는 블록내의 명령들이 계속해서 실행됩니다.

$i = 0;
while ( $i < 109 )
{
  print "딱 ";
  $i++;
}
어디선가 본 듯하지요 ? $i가 109보다 작은 동안만 print "딱 ";과 $i++;가 계속 실행됩니다.

D. until

until은 while의 반대 개념입니다. 즉 조건이 거짓인 동안만 실행이 됩니다.

$i = 0;
until ( $i >= 109 )
{
  print "딱 ";
  $i++;
}

위의 while과 until의 형식에서는 조건식이 먼저 테스트 됩니다. 따라서 맨 처음부터 조건식이 거짓이거나(while) 참인(until) 경우에 블록은 전혀 실행되지 않습니다. 그런 형태의 실행이 필요하면 그렇게 쓰시면 됩니다. 그러나, 일단 한 번 실행이 되고난 후에 테스트 되어야 할 경우에는 아래처럼 do와 함께 사용합니다.

E. do 그리고 while/until

while, until과 큰 차이는 없습니다. 암호를 점검하는 간단한 프로그램을 봅시다.

# pwd.pl

do {
  print "Enter password : ";
  $password = <STDIN>;   # 조금 색다른 것들이 나오네요.
  chop( $password );
} while( $password ne "hack" ); # ne는 문자열을 비교하는 not equal
                                # 연산자라고 했습니다.
print "Well done !\n";
블록내의 명령들은 눈치 보지않고 일단 실행이 됩니다. 그 다음에 조건식이 참인지 시험되고 참이면 불록의 명령들이 반복이 되고 거짓이면 블록의 다음으로 넘어갑니다. While과 다른점은 블록의 명령들이 일단 한번은 실행이 된 후에 조건식이 시험된다는 점입니다.
# pwd2.pl

do {
  print "Enter password : ";
  $password = <STDIN>;  # 자꾸 신경쓰지 마세요. 말씀드릴테니.
  chop( $password );    # 이것도 ...
} until( $password eq "hack" );  # until과 eq이 쓰였지요 ?
                                 # eq는 equal이란걸 다 아시는군요.
print "Well done !\n";
pwd.pl과 pwd2.pl이 다른것은 while과 until, ne와 eq 뿐입니다.

파일 읽기 할 때 $aLine = <fileHandle>를 기억하시죠 ? 유닉스에서는 모든 깡통(키보드, 디스플레이, 프린터 등등)들을 파일로 생각한다는 것은 이미 잘 아실겁니다. PERL또한 그 동네에서 출생된지라 글자를 키보드로 입력 받을 때 키보드에 STDIN이라는 파일 핸들을 할당시켜서 그 핸들을 통해 입력을 받습니다. 그래서 $password = <STDIN>하면 화면에 커서가 깜박이면서 입력을 기다렸다가 마지막에 리턴키를 받으면 $password에 입력받은 문자열과 리턴(\n)문자를 저장시킵니다. 그런데 맨 끝에 보면 $password를 "hack"과 비교를 하는 부분이 있는데, 문자열 "hack"에는 "\n"이 포함되어 있지 않지요. 그 "hack"과 비교시키기 위해 $password에 저장된 문자열의 맨끝에 틀림없이 저장되어있을 "\n"문자를 없애기 위해 chop이라는 함수를 사용합니다. chop은 문자열의 맨 뒤에 있는 한문자를 잘라주는 함수입니다. 라면 끓일 때 파를 뚜껑이 열린 냄비위에 대고 칼로 chop, chop, chop쳐서 썰어 넣는것을 "chop up"이라 한다는군요.

F. loop의 한 가운데에서 . . .

loop이 반복실행되고 있는 중간에 조금 빗나가고 싶은 때가 있습니다. 간단히 소개하도록 하지요.

next
C언어의 continue와 같습니다.
# next.pl

for ( 1..9 )
{
  print "fore $_\n";
  next unless $_ % 3;  # 3의 배수에서만 맨처음으로 돌아가서 계속.
  print "aft  $_\n";   # $_이 3의 배수이면 이 행은 실행되지 않음.
}
print "End.\n";
while같은 것으로도 실험해 보세요. (숙제)

last
C언어의 break와 같습니다.
# last.pl

for ( 1..9 )
{
  print "fore $_\n";   # $_는 위에서 설명 드렸습니다.
  last unless $_ % 3;  # 3의 배수에서 블록 다음으로 넘어감.
  print "aft  $_\n";
}
print "End.\n";
아래는 위 예제의 c 버젼이겠지요.
/* break.c */

...

for ( int i = 1; i <= 9; i++ )
{
  printf( "fore %d\r\n", i );
  if( ! ( i % 3 ) ) break;
  printf( "aft  %d\r\n", i );
}
printf( "End.\r\n" );
.
.
위의 예제에서 4에서 9까지는 아예 얼굴도 못 내밉니다. 실험해보세요.

redo
redo는 next와 비슷하지만 전체 블록의 재실행전의 인수의 증가 등을 다시 하지않습니다. 말이 좀 꼬이네요.
즉 for ( $i = 0; $i < 10; $i++ )에서 $i++를 하지 않는다는 뜻입니다. 예제를 보시고 직접 실행해 보십시오. 예제를 잘 분석해 보시면 제가 무슨말을 하려는지 잘 아실것입니다.

다음은 암호를 받는데 있어서 10번의 기회를 주되, 마지막 한자를 잘못 쳐 넣었을 경우에는 우연한 실수임을 가정하여 10번의 count에서는 제외된 3번의 추가 기회를 주는 프로그램입니다.

# pwd2.pl

$count = 0;
for ( 1..10 )
{
  print "Type password ($_): ";
  chop( $password = <STDIN> );
  if( $password eq "hack" )
  {
    print "Right !\n";
    last;
  }
  chop($password);
  if( $password eq "hac" )
  {
    last if( $count++ >= 3 );
    print "Almost correct. try more\n";
    redo;
  }
}

print "End.\n";
위의 예제를 실행시키면서 암호를 묻는 프롬프트에 a, err, shit등 쓰잘데 없는 암호를 몇번 입력하다가 haco, hact, hacc등 hack과 끝 글자 한자만 다른 암호를 2번쯤 입력해 보십시오. 그러면 redo가 있는 블록이 실행되면서 for(1..10)에서 $_가 더 이상 커지지 않는것을 알 수 잇습니다. 이해가 가시죠 ?

+ Recent posts