티스토리 뷰

펄의 복수 명사

2002-7-21

배열(array)

한 개짜리 무엇을 담는 변수인 스칼라(scalar)가 있다면 여러 개의 무엇을 담는 변수로 배열(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 . 를 주의하세요.

 

한 가지 주의할 점은 배열, 스칼라, 해시(hash) 등은 각각의 네임스페이스(name space)가 있다는 점입니다.  @name$name은 아무런 상관이 없는 것입니다. $name[0]@name이라는 배열의 첫 번째 원소로 $name이라는 스칼라 변수와는 아무런 상관이 없습니다.

 

그리고 배열의 대괄호 [] 안에는 꼭 숫자만 넣을 수 있는 것은 아닙니다. 숫자가 할당된 변수가 들어갈 수도 있습니다. 그리고 소수를 넣은 경우 소수점 아래 자리는 버려집니다.

 

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

 

Source: geeksforgeeks.org

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

 

$#sample

 

예를 들어,

 

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

 

위의 $#aa와 같은 것은 배열의 크기를 알아보는데 사용할 수 있을 것입니다.


$#aa + 1@aa 배열의 크기입니다. 배열은 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) = ("myunghun", "author");

 

위의 2가지를 조합하면 이것도 가능합니다.

 

@names = qw/lee park kim jung/;
# @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];

 

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

배열과 관련된 연산자: push, pop, shift, unshift, reverse, sort

배열과 관련된 연산자 중에 자주 쓰이는 것은 네 가지입니다. 

먼저 배열의 오른쪽에서 뭔가를 집어넣을때는 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) { }

 

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

@people = ("이아무개","김아무개,"박아무개","정아무개","최아무개");
foreach $name (@people) {
$name .= "씨";
}
print "@people";

 

$name@people이라는 배열의 각 원소를 가리키므로 위의 코드는

$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) 의존적인 언어입니다. 똑같은 변수라도 주변 문맥에 따라 전혀 다르게 취급될 수 있습니다. "Learning Perl" 책(라마 북)에서는 이것이 책 전체를 통틀어 가장 중요하다고까지 써놨습니다.

 

간단하게 이런것입니다. 스칼라로 취급 할 문맥이면 스칼라로, 리스트로 취급할 문맥이면 리스트로 취급합니다.

예를 들면,

 

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 가 들어가게 됩니다

 

어떤 게 스칼라 컨텍스트고 어떤 게 리스트 컨텍스트인지는 다음과 같습니다.

 

컨텍스트

 

주의할 부분은 ($a,$b) = something입니다. 이처럼 왼 편에 리스트를 만들어 놓으면 오른쪽 것은 리스트로 여겨집니다. 특히, 리스트 컨텍스트 중 위에서 세 번째 것, 스케일라 변수 한 개일지라도 괄호로 묶는 순간 리스트 컨텍스트가 된다는 점을 주의하세요. 괄호로 묶는 것은  리스트 컨텍스트로 쓰겠다는 의미를 의미입니다. 하나 더 예를 살펴보고 넘어갑시다.

 

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

 

이제 펄 사용자 입력 처리에서 공부한 내용이 이번 시간에 배운 컨텍스트와 어떻게 연결되는지 설명드립니다. 그 글에서 <STDIN>은 표준입력에 있는 것을 한 줄 읽어오는 기능을 한다고 했었습니다.그런데 만약 <STDIN>을 리스트 문맥에서 사용한다면 어떻게 될까요?

 

리스트 문맥에서는 표준입력에 있는 모든 것을 다 읽어들이게 됩니다.

 

chomp(@lines = <STDIN>);

 

@lines에는 한 줄이 아니라 모든 줄이 다 들어갑니다.

반응형
댓글