티스토리 뷰

matlab

매트랩에서의 array unpacking

게으른 the lazy 2020. 7. 19. 01:49

그림 0. cell unpacking

 

0. 들어가며

지난 글에서 cell array의 특별한 활용법을 몇 가지 말씀드렸습니다. 이번 글에서는 어떻게 그게 가능한지, 그리고 그 뒤에 숨어있는 매트랩의 흥미로운 문법에 대해 말씀드리겠습니다. 놀랄 준비 되셨나요? 그럼 시작하겠습니다.

1. deal 함수 활용하기

잘 알려지지 않은 함수가 하나 있습니다. deal 함수를 이용하면 아래와 같이 우변의 여러 값을 좌변의 여러 변수에 하나씩 집어넣을 수 있습니다.

 

그림 1. 코드를 한 줄만 써서 여러 변수에 값 넣기

 

그림 1-1. deal 함수의 동작

원래는 여러 줄에 걸쳐 변수에 값을 할당해야 하지만, deal을 이용하면 한줄에 끝낼 수 있습니다. 마치 카드를 딜(deal) 하듯 변수에 값이 하나씩 들어가는 걸 볼 수 있습니다. 이 방법은 함수 내부에서 varargin을 여러 변수로 쪼갤 때 특히 강점을 발휘합니다.

 

또한 deal은 여러 개의 변수를 한번에 초기화할 때도 쓰일 수 있습니다.

 

그림 2. 여러 변수를 한번에 초기화하기

 

이 기능은 사실 deal의 특별한 기능은 아닙니다. deal.m 파일을 열어보면 입력이 하나인 경우 출력 인자 개수만큼 입력을 복사하도록 짜여있습니다.

 

그림 1을 잘 보면, 좌변은 컴마로 분리된 변수들이, 우변은 컴마로 분리된 값들이 나열되어 있습니다. 즉, deal은 컴마로 분리된 변수와 값을 하나씩 대응시켜주는 기능이라고도 볼 수 있습니다.

2. cell을 여러 변수로 분리하기

cell에는 재밌는 기능이 몇 가지 있습니다. 그 중 하나로, 아래와 같이 cell을 분해하면서 한번에 여러 변수에 할당할 수 있습니다.

 

그림 3. cell을 여러 변수로 분리하기

 

그림 3-1. cell을 여러 변수로 분리하기

 

MATLAB 7.0 이전에는 deal 함수를 써야 했습니다만, 그 이후로는 deal을 쓰지 않고도 위와 같이 좀 더 간단하게 할 수 있습니다.

3. structure array의 field 값들을 변수에 나눠담기

cell과 비슷하게, structure array의 특정 field 값들도 분리해서 변수에 나눠담을 수 있습니다. 아래 그림에서 bebop은 비밥호에 타고 있는 5명의 정보를 담고 있는 structure array입니다. 각 원소(사람)는 name, birthyear, heightfield로 가지고 있습니다. (아인은 귀여운 웰시코기이지만, 사람과 장기도 둘 수 있으므로 여기서는 사람으로 치겠습니다.)

 

그림 4. name, birthyear, height를 field로 갖는 structure array

 

그림 4-1. 비밥호에 타고 있는 다섯 명(?)을 원소로 하는 structure array bebop

 

그림 4와 같이 bebop.name을 출력하면 각 원소의 name 값이 하나씩 따로따로 출력이 됩니다. 여기에 cell에서 했던 것과 같은 방식으로 bebop.name을 변수에 나눠담을 수 있습니다.

 

그림 5. structure array의 특정 field 값을 변수에 나눠담기

 

그림 5-1. bebop.name을 여러 변수에 나눠담기

 

지금까지 나온 코드들을 보면 공통점이 있습니다. 좌변은 항상 변수가 컴마로 구분되어 [ ]로 감싸여 있습니다. 우변의 값들은 다양한 방법으로 처리되었는데, 일반 값(리터럴)들의 목록은 deal로, cell array는 {:}로, structure array는 .fieldname으로 분리했습니다. 여기에 어떤 공통점이 있는 걸까요?

4. comma-separated list

명령창에 아래와 같이 컴마로 구분된 리터럴들을 쓰면, 각각을 따로 실행한 것과 동일하게 동작합니다.

 

그림 6. 컴마로 구분된 리터럴

 

그런데 cell array 전체를 출력하기 위해 {:}를 사용해도 동일한 형태의 출력이 나옵니다.

 

그림 7. cell array의 요소를 분리하기

 

또한 structure array의 특정 field 값을 출력해도 마찬가지 결과가 나옵니다.

 

그림 8. structure array의 특정 field를 분리하여 뽑아내기

 

즉, cell array는 {:}를 이용하면 각 원소를 컴마로 구분한 것처럼 쓸 수 있고, structure array는 .fieldname을 이용하면 각 원소의 해당 field 값들을 컴마로 구분한 것처럼 가져올 수 있다는 뜻이 됩니다. 이게 무슨 뜻인지 헷갈릴 수 있으니, 각 경우에 대해서 좀 더 자세히 풀어보겠습니다.

 

그림 6의 코드에는 deal을 쓸 수 없습니다. deal을 쓰려면 분리한 것을 받아줄 변수가 필요하기 때문입니다. 그 점만 제외하면 그림 6은 그림 1 좌변의 리터럴들을 컴마로 구분하여 쓴 것과 같습니다.

 

그림 6. 컴마로 구분된 리터럴

 

그림 1. 코드를 한 줄만 써서 여러 변수에 값 넣기

 

그림 3과 그림 7을 같이 보면, cell array는 {:}를 통해서 값을 컴마로 분리된 것처럼 꺼내고 이를 좌변의 변수에 하나씩 할당한다는 것을 알 수 있습니다.

 

그림 7. cell array의 요소를 분리하기

 

그림 3. cell을 여러 변수로 분리하기

 

그림 5와 그림 8을 같이 보면, structure array에 .fieldname을 붙이면 해당 field 값들을 컴마로 분리한 것처럼 가져온 후 이를 좌변의 변수에 하나씩 할당하는 것을 볼 수 있습니다.

 

그림 8. structure array의 특정 field를 분리하여 뽑아내기

 

그림 5. structure array의 특정 field 값을 변수에 나눠담기

 

이제 지금까지 나온 케이스들이 한 가지 패턴에 딱 들어맞는 것이 보이시나요? 정리하자면, 우변에 무엇이 오든 - 리터럴, cell array, structure array - 컴마로 구분된 리터럴의 형태로 만들 수 있으면 [ ]로 감싼 좌변의 변수에 하나씩 deal 할 수 있습니다. cell array는 {:}를 이용하여 분리할 수 있으며 내부적으로 아래와 같이 해석됩니다.

 

C{:} -> C{1}, C{2}, ..., C{end}

 

실제로 관련 문서를 보면 아래의 표현이 나오는데, 바로 cell의 원소를 컴마로 구분된 목록(comma-separated list)으로 만들 수 있다는 뜻입니다.

“Extracting multiple elements from a cell array yields a comma-separated list.”

structure array의 특정 field는 .fieldname을 붙여서 분리할 수 있으며, 이때 아래와 같이 해석됩니다.

 

bebop.name -> bebop(1).name, bebop(2).name, ..., bebop(end).name

 

이렇게 컴마로 구분된 목록을 부르는 명칭이 있습니다. comma-separated list라고 부르는데요. 명칭이 길고 마음에 들지 않습니다. 그래서 아예 제 마음대로 용어를 만들어봤습니다.

5. cell unpacking, structure unpacking

위에서 이미 보여드린 예제들을 unpacking이라는 용어를 써서 다시 설명해보겠습니다. (참고로 unpacking이라는 단어는 파이썬의 unpacking operator에서 빌려왔습니다. 둘이 하는 일이 비슷하거든요.)

1) cell unpacking

cell array에 {:}를 붙이면 array의 각 원소들을 unpacking 할 수 있습니다. 이는 좌변의 변수에 하나씩 할당할 수 있습니다.

 

그림 3. cell을 여러 변수로 분리하기


2) structure array unpacking

structure array에 .fieldname을 붙이면 array 전체의 해당 field 값들을 unpacking 할 수 있습니다. 이는 좌변의 변수에 하나씩 할당할 수 있습니다.

 

그림 5. structure array의 특정 field 값을 변수에 나눠담기

 

6. structure unpacking – 예제

이제 이전 글에서 보여드린 코드들을 다시 살펴보겠습니다. 현재 폴더 내 모든 tif 파일들의 목록을 아래와 같이 structure array로 만들었습니다.

 

그림 9. 현재 폴더 내 모든 tif 파일들의 목록

 

filesname 값들을 unpacking 할 때 아래와 같이 좌변에 변수를 하나만 쓰면 이 변수에는 files.name 중 첫번째 값만 들어갑니다. 5개의 name을 deal 해야 하는데 좌변에는 그걸 받을 변수가 하나밖에 없기 때문입니다.

 

그림 10. files.name 중 첫 번째 결과만 names에 할당

 

그럼 아래 코드는 어떨까요?

 

그림 11. files.name을 unpacking 하여 names의 unpacking 결과에 하나씩 할당

 

우변은 filesname 값들을 unpacking 했습니다. 그리고 좌변에서는 cell array인 names를 unpacking 했습니다. 지금까지는 좌변이 변수였는데, 이렇게 cell array를 unpacking 한 것을 둘 수도 있습니다. 어떻게든 양쪽 모두 적절히 unpacking 하기만 하면 우변의 결과를 좌변에 하나씩 deal 할 수 있습니다.

 

아래는 filesbytes 값을 unpacking 했습니다.

 

그림 12. files.bytes를 unpacking 하여 numeric array로 만들기


위에서 말한 바와 같이 우변은 아래와 같이 해석됩니다.

 

[files.bytes] -> [files(1).bytes, files(2).bytes, ..., files(end).bytes]

 

해석된 결과를 보면 매우 자연스러운 numeric array입니다. 그럼 files.name을 unpacking하면 어떨까요? files.name은 문자열이므로 unpacking을 해도 구분되지 않고 한줄로 이어붙습니다.

 

그림 13. 문자열인 files.name을 unpacking 한 결과

 

그림 13-1. files.name을 unpacking 했을 때 한 줄로 이어붙는 이유

 

7. 함수의 출력을 여러 변수에 나눠담기

매트랩의 많은 함수들은 출력이 2개 이상입니다. 예를 들어 max 함수는 array의 최대값과 그 최대값의 인덱스를 반환합니다. fileparts는 파일명을 경로, 파일명, 확장자로 분리합니다.

 

그림 14. fileparts 함수의 출력 3개

 

실제로 fileparts.m 파일을 열어보면 값 3개를 반환하는 것을 알 수 있습니다.

 

그림 15. fileparts.m 파일의 앞부분

 

그런데 반환값의 개수가 정해지지 않았을 땐 어떻게 할까요? 앞에서 설명한 deal 함수의 경우, 입력 개수에 맞춰서 출력 개수가 정해져야 합니다. 출력이 몇개가 될지도 모르는데 무작정 if문을 써서 모든 경우에 대비할 수도 없습니다.

 

이때 필요한 것이 varargoutnargout입니다. varargout은 함수의 반환값을 모두 모아둘 수 있는 cell array를 일컫는 예약어입니다. nargout는 함수가 호출될 때 함수 출력이 몇 개 요청되었는지가 담겨있는 예약어입니다. varargoutnargout의 의미와 사용법에 대해서는 다른 글에서 더 자세히 다루도록 하겠습니다.

 

여기서 중요한 것은 varargout이 cell array라는 점입니다. cell array인 varargout에 함수의 반환값을 모두 담을 수 있다는 말은, 함수의 반환값을 하나의 cell array로 볼 수도 있다는 뜻이 됩니다. 실제로 함수의 반환값이 여러 개일 때 이 값들이 각 변수에 나뉘어 들어가는 것을 보면, 마치 cell array를 unpacking 한 것처럼 동작한다는 걸 알 수 있습니다. 예로 든 fileparts 함수의 경우, 함수의 반환값은 pathstr, name, ext의 3개이지만 이 3개를 순서대로 원소로 갖고 있는 cell array로도 볼 수 있습니다. 이제 이 함수를 아래와 같이 호출하면, 함수의 반환값은 {pathstr, name, ext}를 unpacking 한 것처럼 path, name, ext에 나뉘어 할당(deal)됩니다.

 

그림 14. fileparts 함수의 출력 3개

 

그림 14-1. fileparts 함수의 출력 3개가 unpacking 되는 모습

 

지난 글에서 말씀드렸듯이 cell array를 사용하면 함수의 반환값을 몇 개 받을지를 dynamically하게 바꿀 수 있습니다. 아래 코드를 보면 max 함수의 출력이 {maxVal, maxIdx}를 unpacking 한 것처럼 반환되고 그것이 좌변의 out{:}에 deal 되는 걸 알 수 있습니다. out{:}은 미리 만들어둔 cell array인 out을 unpacking 한 것입니다.

 

그림 16. max 함수의 반환값 개수를 dynamically하게 바꾸기

 

8. cell unpacking, structure array unpacking의 다양한 활용법

지금까지 cell array에 {:}를, structure array에 .fieldname을 이용하여 unpacking 할 수 있다는 것, unpacking 된 값들은 마치 컴마로 구분한 것처럼 받아들여진다는 것도 말씀드렸습니다. 이제 이걸 매우 흥미롭게 활용할 수 있는 방법을 몇 가지 소개하겠습니다.

1) cell unpacking을 이용한 structure 변수 생성

structure 변수는 보통 아래와 같은 두 가지 방법으로 만듭니다.

 

그림 17. structure 변수를 만드는 전형적인 방법1

 

그림 18. structure 변수를 만드는 전형적인 방법2

 

그런데 그림 18을 자세히 보니 struct() 안에 있는 값들이 컴마로 구분되어 있군요. 그래서 structure 변수는 아래와 같은 방법으로도 만들 수 있습니다.

 

그림 19. cell unpacking을 이용한 structure 변수 생성

 

2) structure array에 field 추가하기 및 바꾸기

이번에는 structure array인 bebop에 새로운 field인 'gear'를 추가해보겠습니다. 각 캐릭터에 장비(gear)를 속성으로 추가하는 셈인거죠. 새 field를 만들어야 하니 좌변에는 bebop.gear를 [ ]로 감싸줍니다. 그리고 gears라는 cell array를 만든 후 {:}로 unpacking을 해보겠습니다. 그럼 아래와 같은 방식이 됩니다.

 

그림 20. cell unpacking을 이용하여 structure array에 새 field 생성하기

 

그림 2에서 deal을 이용하면 여러 변수를 한번에 초기화할 수 있음을 보여드렸는데요. structure array에 새 field를 만들면서 한 값으로 초기화하거나 기존 field를 한 값으로 바꾸는 데에도 deal을 사용할 수 있습니다.

 

그림 21. deal을 이용하여 structure array의 특정 field를 한 값으로 초기화

 

3) 2차원 cell의 unpacking

2차원 cell도 얼마든지 unpacking 할 수 있습니다. 우선 bebop을 원래대로 바꾸고 아래처럼 cell로 만들어보겠습니다.

 

그림 22. bebop structure array로부터 cell array 생성

 

여기서 이름에 해당되는 첫번째 column만 unpacking으로 가져오려면 아래처럼 하면 됩니다.

 

그림 23. 2차원 cell array를 unpacking하기

 

즉, bebopCell{:,1}은 아래처럼 해석됩니다.

 

bebopCell{:,1} -> bebopCell{1,1}, bebopCell{2,1}, ..., bebopCell{end,1}

 

참고로 { }가 아닌 ( )를 쓰면 unpacking이 되지 않고 cell array의 일부가 그대로 반환됩니다.

 

그림 24. 2차원 cell array의 특정 column을 cell 형태로 가져오기

 

4) 함수의 옵션 지정에 unpacking 활용하기

plot 함수에는 많은 옵션을 설정할 수 있습니다. 대표적으로 Color, LineWidth, Marker, MarkerSize 등이 있지만 이 외에도 20여 가지 옵션이 있습니다. figure에 3개의 plot을 그리는데 모두 같은 옵션을 설정하는 경우 아래와 같이 plot마다 같은 옵션을 반복해서 적을 수 있습니다.

 

그림 25. 여러 plot에 동일한 옵션 적용하기

 

하지만 같은 코드가 반복되는 것은 일반적으로 좋은 코드가 아닙니다. 물론 plot을 한 번만 호출하면서 3개의 plot을 그리는 방법은 이미 있습니다만, plot마다 옵션이 조금씩 달라진다면 그 방법도 깔끔하지는 않습니다. plot의 옵션들을 따로 관리할 수 있으면 좋지 않을까요? 여기서도 cell unpacking을 활용할 수 있습니다.

 

그림 26. cell unpacking을 이용하여 plot의 옵션 설정하기

 

위 코드를 보니 왠지 다른 함수에도 테스트를 해보고 싶네요. 이 plot에 legend를 달아보겠습니다.

 

그림 27. cell unpacking을 이용한 legend 생성

 

사용자 정의 함수를 만들다 보면 옵션을 다양하게 적어야 할 일이 종종 생깁니다. 저는 이미지가 binary로 저장되어 있는 파일을 읽어오는 함수 fromfile을 아래와 같이 만들어서 쓰는데요. (파이썬 numpy의 fromfile 함수를 따라 만들었습니다.) 이 함수에는 기본적으로 이미지의 크기와 자료형이 입력으로 들어가야 합니다.

 

그림 28. binary 형태로 저장된 이미지 파일을 읽는 함수

 

그런데 자주 쓰는 옵션의 조합이 몇 가지 정해져있습니다. 예를 들어 A라는 카메라로 얻은 영상은 항상 [1024, 1024] 크기의 uint16이고, B라는 카메라로 얻은 영상은 항상 [320, 480] 크기의 double입니다. 이미지를 불러올 때마다 매번 옵션을 적어주는 것보다는 옵션을 미리 설정해두고 필요할 때 그 옵션만 붙이면 훨씬 편하지 않을까요?

 

그림 29. 자주 쓰는 옵션을 cell로 미리 설정해두고 cell unpacking 활용하기

 

9. 마치며

모든 것은 matlab cody에 올라온 한 문제에서 시작되었습니다.

 

그림 30. 매트랩에 3항 연산자라고?!

이 한 문제의 풀이에 고급스킬이 꽤 들어갑니다. 그 중 하나가 본 글에서 설명드린 함수 반환값에 대한 cell unpacking입니다.

 

그림 31. expr 함수의 반환값을 cell array인 varargout에 unpacking

 

이렇게 짜면 된다는 걸 검색을 통해 알아내긴 했는데, ‘되는데 왜 되는지 모르겠는’ 상황이었던거죠. 그래서 본격적인 자료조사와 개인 연구를 시작했습니다.

 

그림 32. 이게 왜 되지??

 

매트랩의 장점 중 하나가 충실한 도움말이지만 의외로 문서화가 되어 있지 않거나 문서는 있지만 잘 알려지지 않은 부분도 많이 있습니다. 함수 반환값이 어떻게 cell array에 알아서 나뉘어 들어가는지를 조사하던 중 흥미로운 문서를 두어개 찾게 되었고, 그걸 바탕으로 관련 문법의 근본을 파고들어 보았습니다. 이해하고 나니 굉장히 쓸모가 많은 문법이라는 생각이 듭니다. 제 마음대로 unpacking이라는 이름까지 지어주었습니다만 충분히 그럴만한 가치가 있는 문법인 것 같습니다. unpacking이 언젠가 여러분의 매트랩 코드를 더 이쁘게 만들어주길 바라면서 이 글을 마치겠습니다. 긴 글 읽어주셔서 고맙습니다.

감사의 글

저와 함께 이 문제를 고민해주신 코봉이 님께 감사말씀 드립니다.

참고한 곳들

https://www.mathworks.com/company/newsletters/articles/whats-the-big-deal.html

https://www.mathworks.com/help/matlab/matlab_prog/comma-separated-lists.html

https://kr.mathworks.com/help/matlab/ref/deal.html

https://lazymatlab.tistory.com/86

https://www.mathworks.com/matlabcentral/answers/366057-how-to-combine-multiple-output-from-a-function-into-1

https://www.mathworks.com/matlabcentral/cody/problems/44243/

https://realpython.com/python-kwargs-and-args/

http://undocumentedmatlab.com/

https://cowboybebop.fandom.com/wiki/Main_Page

https://numpy.org/doc/stable/reference/generated/numpy.fromfile.html

https://www.rfblues.com/Characters/major.htm

이미지 출처

https://imgbin.com/png/uG2y3VK3/faye-valentine-drawing-fan-art-png
https://www.kindpng.com/imgv/iRwbxbi_transparent-cowboy-bebop-png-jet-black-cowboy-bebop/
https://dlpng.com/png/6508010
https://www.mangajam.com/cowboy-bebop-2/how-draw-edward-cowboy-bebop
https://memegenerator.net/instance/27971833/programmer-my-code-works-i-have-no-idea-why

댓글