이명헌 경영 스쿨
[펄] 펄의 배열 (array) 및 배열 관련 연산자, 구문
펄에서의 배열 (array, list)
이명헌 [ 2002-7-21 ]

배 열

한 개짜리 무엇을 담는 변수인 '스케일라' 가 있다면 여러개의 무엇을 담는 변수로 배열(array, list)이 있습니다. 배열은 다른 프로그래밍 언어에도 다들 갖춰져 있어서 많이 익숙하실 겁니다. 배열은 우리가 중고딩때 배운 '집합' 처럼 생각하시면 이해가 쉽습니다. 집합은 집합인데 원소의 '순서' 가 있는 집합이 바로 배열 입니다.

@array1 = (1, 2, 3, 4, 5);
@array2 = ('a', "b", " ", 34);

이런것이 펄의 배열 입니다. 먼저 좌측에 있는 변수를 배열 변수(array variable)라 합니다. 우측의 괄호로 묶인 부분은 리스트(list)라 합니다. 배열과 리스트는 크게 봐서 같은 의미로 쓰입니다. 우선은 그렇게 알아두시면 편리하실것 같습니다.

배열변수는 @ 로 시작하는 변수라는 점을 주목하십시오. array 의 "a" 와 @ 이 닮았다는 것으로 기억하시면 됩니다. 스케일라가 "s" 를 닮은 $ 로 시작하는 것처럼요.
또 여러 원소를 묶어주는 기호로 괄호 ( ) 를 쓰고 있다는 점도 잘 봐두세요.
배열을 만드는 건 아주 간단하죠?
그런데 배열은 원소의 순서가 있다고 했죠.
이것을 어떻게 표현하는가 하면 원소 '하나'는 스케일라(scalar)가 되므로, (펄에서도 다른 언어처럼 0 부터 센다는 점만 주의하시면 됩니다)
$array1[0]@array1의 첫번째 원소가 됩니다. 앞이 $로 시작하죠? 그리고 대괄호 [ ]를 쓰구요. 원소 하나니까 $ 로 시작하는 스케일라가 됩니다. 헷깔리지 마세요.
같은식으로 @array2 배열의 4 번째 원소는 $array2[3] 입니다.

그러므로 배열 내의 어떤 원소에 접근하거나 새로운 원소를 집어넣는 것은 다음과 같이 하시면 됩니다.

@sample = (1, 2, "1", 'perl');
print $sample[3]; # @sample 의 4번째 원소 출력, perl 이 찍혀나오겠죠?
print $sample[0] = 'a'; # 첫번째 원소인 1 을 문자열 a 로 바꿈
# 즉 @sample 은 이제 ('a',2,"1",'perl')이 됨
$sample[7] = "fly"; # 6 번째 원소로 fly 라는 문자열을 넣어줌
# @sample 은 이제 ('a',2,"1",'perl',,"fly")
print @sample; # 배열변수 앞에 바로 print 구문을 쓸 수도 있습니다.
$sample[3] = ''; # 4 번째 원소를 지움
# 이제 @sample 은 ('a',2,"1",,,"fly")
$sample[7] .= " by night"; # fly 는 fly by night 으로 바뀝니다.
# concatenation operator . 를 주의하세요

한 가지 주의하실 점은 배열, 스케일라, 해쉬 등등은 각각의 네임스페이스(name space) 가 있다는 점입니다. 무슨 얘기냐면요. @name$name은 아무런 상관이 없다는 얘깁니다. 주의하셔야 합니다. $name[0]@name 이라는 배열의 첫번째 원소로 $name이라는 스케일라 변수와는 아무런 상관이 없습니다.

자.. 그리고 배열의 대괄호 [] 안은 꼭 숫자가 들어가야 하는건 아닙니다. 숫자가 할당된 변수가 들어갈 수도 있습니다. 그리고 소수는 소숫점 아래자리가 버려집니다.

$number = 1;
print $sample[$number];
print $sample[$number+1.5]; # $sample[2]와 같은 의미 입니다.

배열의 마지막 원소가 몇번째인가는 다음과 같은 방법으로 표시할 수 있습니다.

$#sample

예를들어,

@aa = (2,4,8,10,12);print $#aa; # 4 가 출력됩니다. 마지막 원소는 $aa[4] 이므로

위의 $#aa와 같은 것은 배열의 크기를 알아보는데 사용할 수 있을 것입니다.
$#aa + 1 이 바로 @aa 배열의 크기가 되죠. 원소가 5 개. 배열은 0 부터 세니까 $#aa 에다 1을 더해주는 것입니다.
배열의 크기를 표시해주는 다른 방법으로는 scalar 라는 연산자가 있습니다.

@aa = (2,4,8,10,12);
print scalar @aa;

5가 출력 됩니다.

결국 배열의 마지막 원소를 출력해주는 '일반식'은 이렇게 될 겁니다.

print $blah[$#blah];

조금 어려운 내용으로, $#arrayname 은 배열의 크기를 미리 설정하는데 사용될 수도 있습니다. 즉, $#someArray = 10; 이라고 하면 @someArray 라는 배열을 위해 미리 11 칸의 메모리를 준비하게 되는 것이죠. 이처럼 크기를 미리 아는 어떤 데이타를 배열에 담을때 미리 크기를 설정해두면 메모리를 이곳저곳 분산해서 확보하는 일이 덜어지므로 프로그램 실행속도가 더 빨라질 수 있게 됩니다. 일상적인 작은 프로그램을 만들때에는 별 관계가 없는 내용이지만, 복잡하고 큰 데이타를 다루는 프로그램을 만드는 경우 이런 부분도 중요해질 수 있습니다. 참고삼아..

리스트 표현 방식

위에서 @array = (1,2,3);과 같은 것이 배열이라고 했는데 좀 더 엄밀하게 얘기하면 @array는 배열 변수이고 이 배열 변수에 할당되는 괄호로 묶인 부분은 리스트(list) 라고 합니다. 리스트는 꼭 배열 변수에 할당되어야만 하는건 아닙니다. 그냥 (1,2,3) 의 형태로도 존재할 수 있습니다. 어쨌든, 이 리스트 표현방식에는 몇 가지 편리한 것이 있습니다.

첫째 .. 입니다.(점두개) 다음을 보세요.

(1..5) # (1,2,3,4,5) 와 똑같습니다
(1,2,3..6,7) # (1,2,3,4,5,6,7) 과 똑같습니다

연속되는 부분은 점 두개로 대체할 수 있는 것입니다. 이것도 됩니다.

('a'..'z') # (a,b,c,d..z) 와 똑같습니다. a 부터 z 까지

거꾸로는 안됩니다.

(10..1) # () 와 똑같습니다. 아무것도 안들어있게 됨

또하나 리스트 표현방식에서 자주 쓰이는 중요한 것으로 qw 가 있습니다. 이건 따옴표를 대체해줍니다. 보시죠. ("a","b","c","d","e") qw/a b c d e/와 똑같습니다. 일일이 따옴표를 쓰지 않아도 되므로 문자열을 담는 배열을 만들때 매우 매우 편리합니다. qw 다음에는 꼭 슬래쉬가 와야하는건 아닙니다. 앞 뒤가 똑같기만 하면 됩니다. 즉.

qw # a b c d e #;
qw ! a b c d e !;
qw { a b c d e };
qw [ a b c d e ];
("a","b","c","d","e");

이건 모두 다 똑같습니다. { }[ ]는 앞뒤가 다르지만 한 쌍이므로 됩니다.

리스트는 배열변수에 할당되지 않고도 존재할 수 있다는 말씀드렸었죠? 그러므로 다음과 같은 방식이 가능합니다.

($name, $value) = ("myonghon", "author");idiom

위의 2가지를 조합하면 이런것도 되겠죠.

@names = qw/lee park kim jung/; idiom
# @names = ("lee","park","kim","jung") 과 같습니다

배열의 원소는 반드시 스케일라인것은 아닙니다. 배열내에 배열을 담을 수도 있고 배열내에 해쉬(아직 구경해보진 않았습니다)를 담을 수도 있습니다. 이런 방식을 통해서 펄에서도 복잡한 데이타 구조를 설계할 수 있습니다만 그건 나중에 다룰 기회가 있을 겁니다. 우선 간단한 것만 구경해보죠.

@small = (2..5);
@large = ("a", @small, "z");
# @large = ("a",2,3,4,5,"z") 와 같습니다

그리고 리스트의 각 원소를 접근하는데 있어 꼭 배열변수에 할당될 필요도 없습니다. $a = ('a'..'z')[3];
print $a; # d 가 출력됩니다

이런건 어떻습니까. localtime 함수를 이용해서 오늘날짜를 년도, 월, 일별로 변수에 담는 코드 입니다.

($day,$month,$year) = (localtime)[3,4,5];idiom

대괄호 안의 콤마에 주의하세요.

배열과 관련된 연산자

배열과 관련된 연산자 중에 매우 자주 쓰이는 것으로 4가지가 있습니다. 이 함수 모두 배열내의 어떤 원소를 뽑아내거나 배열에 새로운 원소를 집어넣는 기능을 합니다.

먼저 배열의 오른쪽에서 뭔가를 집어넣을때는 push를 씁니다. 뺄때는 pop을 씁니다. (push-pop)

@test = (1..10);
$last = pop(@test);

$last라는 스케일라 변수에는 10이 담깁니다. 배열의 가장 오른쪽 원소(마지막원소)를 튕겨내는 것 (pop) 입니다. 그러면 마지막 원소를 튕겨낸 뒤에 배열은 어떻게 될까요. 당연히 원소 하나가 줄어듭니다. 위의 예에서는 $last에는 10 이 담기고, @test는 (1..9) 가 되는 거죠.

반대로 오른쪽끝에 뭔가를 밀어넣으려면 push 를 씁니다. 구문의 형태에 주의하세요.

push(@test,100); # @test 는 (1..9,100) 이 됩니다

배열에 배열을 밀어넣을 수도 있습니다.

@small = ('a'..'c');
@large = ('o'..'z');
push (@large, @small);

어떻게 될지는 짐작이 되시죠? @large의 오른쪽에 @small을 밀어넣습니다.

이번에는 왼쪽에서 집어넣고 빼는 것을 해보죠. 왼쪽에서 빼내는 것은 shift 입니다. 왼쪽에서 집어넣는 것은 unshift 입니다. (shift-unshift)

@shifting = ('a'..'z');
$first = shift(@shifting);

$first 스케일라 변수에는 'a'가 담기게 되고 @shifting 배열은 ('b'..'z') 로 바뀝니다.unshiftpush와 비슷합니다.

unshift(@shifting, "zzz");

밀어넣고 빼는 것은 감 잡으셨죠?

다음으로 배열을 몽땅 역순으로 바꾸는 연산자로 reverse가 있습니다.

@abc = (1..5);
@abc = reverse @abc;

@abc 에는 (5,4,3,2,1) 이 들어있게 됩니다.

마지막으로 배열을 정돈해주는 sort 연산자.

@os = qw/ prodos dos cp-m msdos macos windos os2 nextstep bsd systemv linux osx expee/;
@os = sort @os;
print "@os";

숫자는 작은것에서 큰것순서로 문자는 아스키코드가 작은것에서 큰것 순서로 정렬해줍니다. 위의 경우는 abc 순서대로 bsd cp-m dos .. 가 출력됩니다. 여기서 자주 실수하는 부분이 하나 있습니다. 위의 두 번째 줄처럼 @os = sort @os; 라고 하지 않고 그냥 sort @os; 라고만 하면 @os 배열은 정렬이 되지 않습니다. 주의하세요. 자주 틀리는 부분입니다.

참고로 print @array;는 각 원소를 따닥따닥 붙여서 쭉 출력해주지만 배열명을 큰 따옴표로 묶으면 각 원소 사이에 스페이스 한 칸을 넣어서 출력하게 됩니다.

배열과 관계되는 조절문 : foreach

스케일라와 관계되는 조절문으로 if , while 을 알아봤었습니다. 이번엔 배열 입니다. 배열과 함께 자주 쓰이는 것은 foreach 입니다. 이것은 list 원소 하나 하나를 가져옵니다. 예를들면,

foreach $number (1..10) {
print $number , "\n"; # 참고 : print "$number\n"와 똑같습니다
}

위 코드는 1 부터 10 까지 쭉 출력해 줍니다. foreach 다음에 각 원소를 담을 스케일라 변수를 써주고 배열명이나 리스트를 써주면 됩니다. 몇 개 더 볼까요?

foreach $name (qw/lee park kim jung/) { }
foreach $file (@files) { }

예를들어 이름이 담긴 리스트에서 각각의 이름 뒤에 "씨" 를 붙여주는 코드는 이렇게 될 겁니다.

@president = ("이숭만","윤버선,"장묜","박정히","최구하","존두한","너태우","기묭삼","김데중");
foreach $name (@president) {
$name .= "씨";
}
print "@president";

$name@president 라는 배열의 각 원소를 가르키게 되므로 위의 코드는 $president[0] .= "씨" ,$president[1] .= "씨" , . . . 하고 똑같은 것입니다.

여기서 중요한 점 하나는요. foreach를 사용할 때 배열 원소 하나 하나가 담기는 변수 (위에서의 $name 같은) 는 일종의 alias라는 것입니다. 즉 $name에 어떤 수정을 가하게 되면 $name이 가르키는 바로 그 원소를 직접 변경된다는 얘기죠. 위의 예에서도 그렇죠? $name에 "씨" 자를 붙이면 @president 원소 하나 하나를 직접 변경하는 것이 됩니다.

이것이 뜻하는 바는 이렇습니다. 배열 원소를 직접 변경할 필요가 있는 경우 위처럼 foreach 다음에 나오는 변수 이름을 바로 사용해도 됩니다만 만약 배열은 그대로 둔 채로 그 배열 원소에 어떤 작업을 해서 다른 결과물을 만들려는 경우엔 반드시 또다른 변수 하나를 더 사용해야 한다는 것입니다. 중요한 이야기 입니다

이제 재밌는것을 하나 해봅시다. 첫시간에 말씀드린 펄의 특징 중에 특별히 명시하지 않으면 담기게 되는 implicit 한 것이 있다고 했습니다. 펄에서 특별히 명시하지 않으면 담기는 변수로 $_가 있습니다. 매우 중요한 것입니다.

다음의 foreach 구문을 보세요.

foreach (1..10) {
print "$_\n";
}

이 코드는 1부터 10까지 줄을 바꿔주면서 출력해주는 코드 입니다. 눈여겨 보실 점은 foreach 다음에 특별히 스케일라 변수를 써주지 않았다는 점이죠. 이렇게 스케일라 변수를 생략하게 되면 펄에서는 default variable로 쓰고 있는 $_ 를 사용하게 됩니다. 그래서 print 구문에서는 $_ 를 이용해서 출력하는 것이구요. 이렇게 생략해버려도 담길 곳이 있다는 점때문에 펄은 매우 축약된 형태로 코딩이 가능합니다. 우선 맛뵈기로 말씀드린것입니다. 앞으로 유사한 것이 여러차례 언급될 겁니다. $_는 꼭 외워두세요. 명시하지 않으면 자동으로 사용되는 스케일라 변수 입니다.

또 한가지 중요한 것은 다음과 같은 펄 코딩 스타일에 빨리 익숙해지는게 좋다는 것입니다. 보시죠.

print foreach (1..10);

위 코드는 언뜻 보아서는 꼭 에러가 날것 같은 이상한 형태입니다만 바로 위와 같은 스타일이 펄만의 독특하면서도 매력적인 스타일 입니다. 위 코드는 1에서 10까지 쭉 출력을 해주는 코드인데 결국 다음과 같은 코드임을 아실 수 있을 겁니다.

foreach (1..10) { print }

foreach 다음에 스케일라 변수를 생략했기 때문에 펄의 디폴트 변수인 $_에 1 부터 10 까지 순차적으로 담기게 되고, print 구문역시 따로 변수를 명시하지 않았으므로 펄의 디폴트 변수 $_에 담긴 값을 출력하는 것입니다. 이 부분까지만 해도 펄의 간소하면서도 독특한 스타일이 이채롭습니다만은 펄은 여기서 한 발 더 나아가서 마치 자연어처럼 어순을 바꿀 수 있습니다. 그 결과가 바로,

print foreach (1..10);

이죠. { }가 없고, 앞에서부터 뒤로 마치 영어문장 읽듯이 쭉 코딩을 할 수 있는 것입니다. 이런 스타일은 앞으로 여러가지 조절문에서 계속 나오게 되므로 빨리 익숙해지시는 것이 좋습니다. 일반적으로 같은 기능을 하는 경우 { }가 없고 코드 길이가 짧을수록 실행속도도 빠릅니다. 따라서 가능하면 위와 같이 코딩하는 것이 좋습니다. 참고로 { }가 있는 구문을 복문이라하고 없는 구문을 단문이라 합니다. 가급적이면 복문을 피하고 단문으로 코딩하는게 좋습니다. 자세히 알아보시려면 펄의 구문과 루프 을 참고하세요.

컨텍스트 (Context)

마지막으로 중요한 내용 입니다. 중요하지 않은 것이 없군요. ^_^
펄은 자연어처럼 context 의존적인 언어라고 했습니다. 똑같은 변수라도 주변 문맥에 따라 전혀 다르게 취급될 수 있는 것입니다. 라마북에서는 이부분이 책 전체를 통틀어 가장 중요하다고까지 얘기해놨군요.

간단하게 이런것입니다. 스케일라로 취급할 문맥이면 스케일라로 취급하고, 리스트로 취급할 분위기-_-면 리스트로 취급한다 입니다. 예를 보면 쉽습니다. (라마북에 있는걸 옮겨보죠)

5 + something # somthing 은 스케일라로 취급됩니다. + 가 스케일라 연산자니까
sort something # something 은 리스트로 취급됩니다. sort 가 리스트 연산자니까

그런데 어떤 연산자의 경우 양쪽 컨텍스트 모두에서 쓰이면서 각기 다른 결과를 가져오기도 합니다.reverse 가 대표적인 예입니다.

@congtext = qw/aa bb cc dd ee/;
@leest = reverse @congtext; # @congtext 는 리스트로 다뤄집니다.
# 즉 @leest 에는 ("ee","dd","cc","bb","aa") 가 들어가게 되죠
$skala = reverse @congtext; # @congtext 는 스케일라로 취급됩니다
# $skala 에는 eeddccbbaa 가 들어가게 됩니다

그러면 어떤게 스케일라 컨텍스트고 어떤게 리스트 컨텍스트냐. 대략 이렇습니다. 아마 예상한대로 일겁니다.

스케일라 컨텍스트 리스트 컨텍스트
$s = something
$s[3] = something
123+something
if(something) { .. }
while(something) { .. }
@a = something
($a,$b) = something
($a) = something
foreach $a (something) { .. }
sort something
push @aa something

주의하실 것은 ($a,$b) = something 입니다. 이처럼 왼편에 리스트를 만들어 놓으면 오른쪽 것은 리스트로 다뤄지게 됩니다. 특히, 리스트 컨텍스트 중 위에서 세번째 것, 스케일라 변수 한 개일지라도 괄호로 묶는 순간 리스트 컨텍스트가 된다는 점은 잘 기억해두세요. 괄호로 묶는 것은 곧 리스트 컨텍스트로 쓰겠다는 의미를 갖습니다.

하나 더 예를 살펴보고 넘어갑시다.

($x) = (4, 5, 6); # 리스트 문맥이므로 $x 는 4 가 됩니다
$x = (4, 5, 6); # 스케일라 문맥이므로 $x 는 6 이 됩니다

이제 펄 사용자 입력 처리에서 공부한 내용이 이번 시간에 배운 컨텍스트와 어떻게 맞물려 돌아가는지 보시죠. 그 글에서 <STDIN>은 표준입력에 있는 것을 '한 줄' 읽어오는 기능을 한다라고 했었습니다.그런데 만약 <STDIN>을 리스트 문맥에서 사용한다면 어떻게 될까요?

예측하신대로 리스트 문맥에서는 표준입력에 있는 모든 것을 (한 줄이 아니라) 다 읽어들이게 됩니다.

chomp(@lines = <STDIN>);idiom

@lines 에는 한 줄이 아니라 모든 줄이 다 담기는 것입니다.

지금 계신 곳은: TECH > [펄] 펄의 배열 (array) 및 배열 관련 연산자, 구문