Hash(Associative Array)

Hash는 본래 Associative array라고 하는 건데 문자대로 말하면 "연상 배열" (혹은 조합 배열 ?)이 되겠죠. 즉, 참조 번호가 아닌 키워드에서 어떤 값을 연상해 내거나, 키워드와 조합된 어떤 값을 참조하는 방식을 생각하시면 됩니다. Hash, 잡동사니라고도 합니다. 순서에 의한 값의 참조가 아니라는 의미라 생각되는군요.

Hash의 각 요소도 배열의 요소처럼 일반변수(scalar)입니다.

# score.pl

$scores{"engl"} = 75;   # hash의 각 요소의 값에는 당연히
$scores{"math"} = 80;   # $가 붙습니다. 일반변수이니까요.
$scores{"geo"}  = 65;

foreach ( keys( %scores ) )  # 여기서는 hash그 자체를 가리키므로
{                            # % 부호가 붙습니다.
  print "$_ : ", $scores{"$_"}, "\n";
}
keys 함수는 hash의 key들만을 모아서 배열로 만들어 return한다고 했습니다. 그러면 값들만을 모아서 배열로 만들어 주는 함수는 없을까요 ? 물론 있습니다.
# test.pl

$scores{"engl"} = 75;
$scores{"math"} = 80;
$scores{"geo"}  = 65;

@keys = keys %scores;
@values = values %scores;   # 바로 여기있군요 !

while( $#keys >= 0 ) # 으잉 ???  pop을 한번씩 할 때마다 마지막
{                    # 요소의 참조번호는 1씩 작아집니다. 당연하죠 ?
  print pop( @keys ), " : ", pop( @values ), "\n";
}      # 요 위의 pop들을 shift로 바꾸어서 실행해 보세요.
부연하지 않아도 이해가 되시죠 ?

괄호는 어디에?

똑같은 함수를 사용하면서도 어디에는 괄호를 씌우고 어디에는 시원하게 벗겨놓고 ... Perl에서는 괄호를 선택적으로 사용합니다. 굳이 없어도 혼동되지 않는다면 함수의 인자들도 괄호로 둘둘 말 이유가 없습니다. 그래서
print 8 + 3, "\n";
print( 8 + 3, "\n");
는 같은 결과를 출력합니다. 단,
print ( 8 + 3 ), "\n";
은 약간 이상한 모양이 됩니다. 즉, "\n"이 출력되지 않습니다.
Perl은 , 연산자를 보면 ,의 왼쪽을 먼저 계산합니다(evaluating). 그래서
$a = 5, $a = 4, $a = 3;
의 경우 $a에는 결국 3이 저장되는 것입니다. 또 함수와 그에 따라오는 ()부호(List operator)는 전체로서 하나의 Term을 이루며 Term은 그 어떤 연산자보다 큰 우선순위를 갖기 때문에 위의 print함수의 예에서는 print ( 8 + 3 )이 먼저 계산되고 그 다음에 "\n"이 계산됩니다. 그 계산의 결과로 나타나는 것은 아무것도 없지만요. (계산된다는 의미를 calculation으로만 생각하지 마시고, evaluation, manipulation 등으로도 생각하십시오.)
다음 명령문들을 실행해 보시고 출력물의 의미와 왜 그런 결과가 나왔는지를 생각해 보시기 바랍니다.
print "Fore\n", print "Aft\n";
print ( 5 + 3 ) * 5, "\n";
어떤 결과를 쉽게 예측할 수 없으시는 경우에 가장 훌륭한 해결방법은 간단한 예제를 만들어 실행해 보는 것입니다.



Hash를 배열에, 배열을 다시 Hash에 저장할 수 있습니다. 제 순서를 지켜서 찾아들어 가지요.
# test.pl

$scores{"engl "} = 75;
$scores{"math "} = 80;
$scores{"geo  "} = 65;
$scores{"music"} = 98;

@arr = %scores;
print "\@arr : @arr\n";
$count = ( $#arr + 1 ) / 2;
print "Number of data in %scores : $count\n";

%scores2 = @arr;
foreach ( keys %scores2 ) { print "$_ : ", $scores2{"$_"}, "\n"; }
keys와 values함수외에 each라는 함수가 있습니다. 이 함수는 hash의 key와 value를 짝지어 한 배열 안에 넣어줍니다. each함수가 한번씩 불려질때마다 hash의 요소가 순서대로 리턴되고, 모든 요소가 다 리턴 되고나면 그 다음은 NULL 배열이 되돌려집니다. 그러므로 아래의 while의 조건문은 제대로 작동됩니다.
# each.pl

%scores = ( "engl", 75, "math", 80,
            "geo", 65,  "music", 98 );

while( ($key, $value) = each %scores ) {
  print "$key :\t$value\n";
}
조금 긴 예제를을 한번 보시고 hash이야기를 마치겠습니다. 학생 개인의 데이타와 학과 성적이 각각의 파일에 다음과 같이 저장되어 있습니다. 자료파일은 그저 평범한 텍스트 파일입니다. 첫번째 파일은 stufile이고 각 줄은 한 학생의 id, 이름, 학년이 : 로 분리되어 있습니다.
123:Jongpil:2
246:Inhyon:1
357:Jungkwang:3
212:Kwanghoon:2
또 두번째 파일은 scores이고 각 줄은 학생의 id, 학과번호, 시험점수가 공백으로 분리되어 있습니다.
123 1 87
246 1 76
123 3 77
212 2 99
246 3 58
357 2 69
123 2 84
357 3 97
212 1 74
246 2 98
212 3 88
이 자료파일들의 내용을 다음과 같이 출력하려 합니다.
ID  Name      year     1   2   3    Totals

123 Jongpil       2   87  84  77       248
212 Kwanghoon     2   74  99  88       261
246 Inhyon        1   76  98  58       232
357 Jungkwang     3    0  69  97       166
    Total            237 350 320       907
맨 윗줄의 1, 2, 3은 학과 번호입니다.
조금 복잡해 보일지 몰라도 예제를 자세히 보시면 크게 어려울 것도 없습니다.
다음은 예제입니다.
# test.pl

$stufile = 'stufile';
$scorefile = 'scores';

$maxnamelength = 0;   # 이 두줄은 꼭 필요한 것은 아니지만
$maxexamno = 0;       # 명확히 해주기 위해 넣었습니다.

open( Hstufile, "<$stufile" )
    || die "Can\'t open $stufile.\n";  # 열어라, 아니면 죽어라.
while( <Hstufile> )  # $_ = <Hstufile>과 같습니다.
{
  chop;              # 맨 끝의 개행문자를 잘라 냅니다.
  last if( length( $_ ) < 3 );  # 3은 큰 의미는 없지만 3 자도
                        # 안되면 자료가 아닌것으로 간주합니다.
  ( $stuid, $name, $year ) = split( ':', $_ );
  $students{$stuid} = $name;  # hash
  $studyear{$stuid} = $year;  # hash
  if( $maxnamelength < length( $name ) )    # 가장 긴 이름에 맞
    {  $maxnamelength = length( $name );  } # 추어 출력.
}
close Hstufile;  # 닫는것 기억하시죠 ?

open( Hscorefile, "<$scorefile" )
    || die "Can\'t open $scorefile.\n";
while( <Hscorefile> )
{
  chop;
  last if( length( $_ ) < 3 );
  ( $stuid, $examno, $score ) = split;
  $scores{ $stuid, $examno } = $score;  # 키가 2개입니다.
                                        # $stuid는 중복되므로...
  if( $examno > $maxexamno )  # 학과가 몇개인지 셈.
     { $maxexamno = $examno; }
}
close Hscorefile;

printf( "%3s %-${maxnamelength}s %4s", "ID", "Name", "year" );
  # ${maxnamelength}처럼 { }를 사용하여 둘러 쌉니다.
  # 그렇지 않으면 $maxnamelengths 로 인식되겠죠. (s자 보이죠?)
foreach ( 1..$maxexamno )
  {  printf( "%4d", $_ );  }
printf( "%10s\n\n", 'Totals' );

foreach $stuid ( sort ( keys %students ) )  # sort함수는 배열내의
{                              # 각 요소간의 문자열 비교로 전체 
                               # 배열을 정렬한 배열을 리턴합니다.
  printf( "%3d %-${maxnamelength}s %4d", $stuid,
                      $students{$stuid}, $studyear{$stuid} );

  $total = 0;
  foreach $examno (1..$maxexamno)
  {
    printf( "%4d", $scores{ $stuid, $examno } );
    $total += $scores{ $stuid, $examno };
    $examtotal{ $examno } += $scores{ $stuid, $examno };
  }
  printf( "%10d\n", $total );
}

printf( "%3s %-${maxnamelength}s %4s", '', "Total", '' );
$total = 0;
foreach $examno (1..$maxexamno)
{
  printf( "%4d", $examtotal{ $examno } );
  $total += $examtotal{ $examno };
}
printf( "%10d\n\n", $total );
이렇게 보면 데이타베이스 프로그램도 못 만들건 없겠군요. 사실 Perl에는 database와 연동하기 위한 함수들이 많이 있습니다. 그리고 Internet에 보면 많은 database를 위한 Perl 라이브러리나 스크립트들이 많이 있습니다.
여러분도 좋은 라이브러리들을 만들어서 다른 Perl 개발자들에게 큰 도움되는 분이 되시길 바랍니다.

${maxnamelength} ?

일반변수의 이름 주위에 {}로 둘러싼 새로운 모습이 좀 이상하군요. 그러나 그 내용은 $maxnamelength와 하나도 다를 바가 없습니다. 단지 예제에서 그 뒤에 있는 s와의 접촉으로 $maxnamelengths가 되어 해석이 안되는 것을 막기위하여 {}부호가 사용되는 것입니다. 이런 경우는 또 있습니다.
$fruit = "apple";
print "I have five $fruits\n";  ???
print "I have five ${fruit}s\n";  !!!
위의 경우처럼 변수와 어떤 문자를 바로 연결하여 사용할 때에는 반드시 {}부호로 변수이름을 둘러싸야 한다는 사실을 기억하시기 바랍니다.


이전 | 목록 | 다음
Comments