티스토리 뷰

matlab

for문을 이해하는 새로운 방식

게으른 the lazy 2020. 6. 28. 16:16

for문은 보통 다음의 Code 1과 같은 방법으로 많이 사용됩니다.

 

% Code 1
%
% For a given vector, generate 
% a new vector whose element is
% 1 when the element of the given 
% vector at the same position is 
% greater than 0.5, otherwise 0.
%
% 벡터 a가 주어졌을 때, a의 각 요소가 
% 0.5보다 크면 1, 그렇지 않으면 0인, 
% a와 크기가 같은 새 벡터를 생성하라.

a = rand(1,10);
b = zeros(size(a));
for i = 1:length(a)
    if a(i) > 0.5
        b(i) = 1;
    end
end

즉, 벡터 a의 길이만큼 반복문을 수행하고 싶을 때 아래의 패턴이 됩니다.

 

% Code 2

for i = 1:length(a)
	...
	...
end

하지만 꼭 이 패턴으로 쓸 필요는 없습니다. 위 코드는 아래처럼 짜도 동일하게 동작합니다.

 

% Code 3

v = 1:length(a);
for i = v
	...
	...
end

Code 3에서는 1:length(a)를 미리 v로 만들어두고, for문을 시작할 때 i=v로 간단하게 끝냈습니다. 즉, Code 2에서 for문에 쓰인 1:length(a)for문에 쓰는 특별한 표현식이 아니라, 단순히 벡터를 만드는 표현식이라는 의미입니다. 이제 for문은 아래처럼 해석할 수 있습니다.

 

그림 1. for문의 내부 동작 순서도 - 인덱스를 사용하는 경우

위 그림의 j는 코드에서는 등장하지 않습니다. 매트랩 내부적으로 j를 증가시키며 v에서 요소를 하나씩 꺼내오는 것입니다. 또는 아래처럼 해석할 수도 있겠군요. (Y/N의 방향이 바뀐 것에 주의하시기 바랍니다.)

 

그림 2. for문의 내부 동작 순서도 - 인덱스를 사용하지 않는 경우

어느 방식이든 v에서 값을 가져올 수만 있으면 for문이 동작하므로, 이제 Code 1은 아래처럼 쓸 수도 있습니다.

 

a = rand(1,10);

% Code 4-1
%  append new element
%  for every iteration
b = [ ];
for i = a
    if i > 0.5
        b = [b 1];
    else
        b = [b 0];
    end
end


% Code 4-2
%  define the index j and
%  increase for every iteration
b = zeros(size(a));
j = 1;
for i = a
    if i > 0.5
        b(j) = 1;
    else
        b(j) = 0;
    end
    j = j + 1;
end

b에 값을 넣는 것은 if문을 쓰지 않고도 가능하지만, 여기서는 for문의 로직만을 보기 위해 생략하도록 하겠습니다. 위 방식의 아쉬운 점은, 인덱스와 값을 동시에 쓸 수 없다는 점입니다. 즉, for문 안에서 인덱스 ja(j)를 한번에 처리할 수 없습니다. Code 4-1에서는 j를 만들지 않았지만 대신 b의 크기를 미리 할당할 수 없으므로, a의 크기가 큰 경우 속도가 느려집니다. Code 4-2에서는 번거롭게 j를 만들어서 써야하니, 맨 처음 코드인 Code 1보다 나은 점이 보이지 않습니다. (매트랩에는 파이썬의 enumerate와 같은 기능이 없습니다.)

 

그럼 이런 방식은 언제 유리할까요? 아래는 난수 100개를 생성 후, j번째 난수가 0.5보다 클 때만 myfunc() 함수를 실행하는 코드입니다.

 

myfunc = @() fprintf('BIG!\n');
a = rand(1,100);

% Code 5-1
%  using both index and value
for i = 1:length(a)
    if a(i) > 0.5
        myfunc();
    end
end


% Code 5-2
%  using value only
for i = a
    if i > 0.5
        myfunc();
    end
end

Code 5-2가 Code 5-1보다 조금 더 간결한 것을 볼 수 있습니다. 사실 Code 5-2와 파이썬의 for문과 거의 같습니다. 사족이지만 위 그림 2도 파이썬의 for문 동작방식을 거의 그대로 설명한 것입니다.

 

cell 배열도 각 요소가 cell인 "행렬"이기 때문에 위와 같은 식으로 사용할 수 있습니다. 단, cell은 숫자와 달리 사칙연산이나 비교연산자를 쓸 수 없으므로 cell을 벗겨줘야 합니다.

 

myfunc = @() fprintf('BIG!\n');
a = num2cell(rand(1,10));

% Code 5-3
%  using value only from cell array
for i = a
    if i{1} > 0.5
        myfunc();
    end
end

 

for문의 동작과 관련하여 몇가지 알아두면 좋은 점이 있습니다. 아래와 같이 for문 안에서 i 값을 바꾸면 어떻게 될까요?

 

% Code 6

v = 1:3;
for i = v
    disp(['i = ', num2str(i)])
    i = 0;
    disp(['i = ', num2str(i)])
end

i 값을 바꾼 것이 for문에 영향을 줄까요? 결과를 보면...

 

i = 1
i = 0
i = 2
i = 0
i = 3
i = 0

영향을 주지 않습니다. for문 안에서 잠시 i 값이 바뀌었지만, 어차피 한번의 iteration이 끝나면 v에서 새로 값을 받아오기 때문입니다. 그럼 아래는 어떨까요? 아예 v를 loop 도중에 바꿔보았습니다.

 

% Code 7

v = 1:3;
for i=v
    disp(['i = ', num2str(i)])
    disp(['v = ', num2str(v)])
    v = v + 10;
    disp(['v = ', num2str(v)])
end

i 값을 가져올 v를 바꿔버렸으니 i도 바뀐 v에서 값을 가져올까요? 결과를 보면,

 

i = 1
v = 1  2  3
v = 11  12  13
i = 2
v = 11  12  13
v = 21  22  23
i = 3
v = 21  22  23
v = 31  32  33

loop 안에서 v를 바꾸든 말든 i는 항상 for문을 시작할 때의 v에서 값을 가져옴을 알 수 있습니다. v를 인덱스로 사용할 수도 있으니, for문을 일단 시작한 이후에는 v는 바뀌지 않도록 설계된 것으로 유추할 수도 있겠습니다.

 

마지막으로 한가지 유의할 점이 있습니다. 제가 앞에서 "for문에 쓰인 1:length(a)for문에 쓰는 특별한 표현식이 아니라, 단순히 벡터를 만드는 표현식"이라고 말했습니다. 포인트는 "행"벡터라는 점입니다. 아래와 같은 for문에서

 

for i=v
    ...
    ...
end

i가 가져오는 것은 사실 vj번째 값 v(j)가 아니라, vj번째 열 v(:,j)가 됩니다. (행렬의 인덱스를 표현하는 두 가지 방식에 대해서는 이 링크를 참고해주세요.) 즉, 그림 1과 2는 사실 아래와 같이 설명해야 더 정확합니다. (v가 3차원 이상인 경우는 표기가 약간 달라져야 하지만, 여기서는 생략하도록 하겠습니다.)

 

그림 3. for문의 내부 동작 순서도 - 인덱스를 사용하는 경우(수정)

 

그림 4. for문의 내부 동작 순서도 - 인덱스를 사용하지 않는 경우(수정)

 

 

지금까지 나온 예제는 v가 행벡터였으므로 vj번째 열이 스칼라가 되고 코드 동작에 문제가 없었습니다. 하지만 v가 2개 이상의 행을 가진 행렬이라면 부등호를 사용할 때 주의해야합니다. 대신 매트랩의 이러한 특이한 for문 동작방식은 활용만 잘 한다면 더 깔끔한 코드 작성에 도움이 됩니다.

 

지금까지 매트랩의 for문 동작방식을 살펴보았습니다.

 

- 게으른 맽랩

댓글