IDL/배열 생성 및 처리

배열 생성 관련 특수 예제

이상우_idl 2018. 2. 2. 20:54
728x90
반응형

오늘은 배열 생성 예제를 하나 소개해보고자 합니다. 배열 내 구성 원소들의 분포 형태를 내가 직접 결정하고 그 형태에 맞는 배열을 생성하는 예제인데요. 배열의 모든 구성원소들 중 지정된 갯수만큼의 값들이 임의의 인덱스에 분포하도록 해보고자 합니다. 구체적인 예를들어보면, 어떤 배열의 구성원소의 총 갯수가 10개인데 이들 중 3개의 값이 1이고 나머지는 0이 되도록 하는 경우입니다. 물론 1이란 값들의 갯수는 정확히 3개가 되어야 하고, 각각의 배열 내 위치(배열 인덱스)는 임의가 되도록 해야 합니다. 즉 다음과 같은 형태들 중 하나가 되도록 배열을 인위적으로 만들어보는 작업입니다.


0 1 0 0 0 1 1 0 0 0

0 0 0 1 0 1 0 0 1 0

1 1 0 0 0 0 1 0 0 0


얼핏 보면 그리 복잡하지 않은 일처럼 보이긴 하는데, 막상 코딩으로 구현하려면 어떻게 하는 것이 좋을지는 좀 생각이 필요한 문제입니다. 가장 핵심적인 부분은 임의의 인덱스 세 개를 만들어내는 작업이 될 겁니다. 이것만 된다면 나머지 작업이야 각 위치에 1을 대입하면 되니까요. 그렇다면 임의의 인덱스들을 어떻게 생성해야 할까요? 아무래도 난수 발생용 내장함수를 사용하는 것이 좋을 것입니다. 따라서 여기서 가장 적합한 것은 RANDOMU 함수가 되지 않을까 싶습니다. RANDOMU 함수에 관한 자세한 내용은 제가 이 블로그에서 이미 소개한 바 있으므로 궁금하신 분들은 아래 링크의 내용을 참조하시기 바랍니다.


RANDOMU 함수 소개 1

RANDOMU 함수 소개 2


그러면 먼저 기본적인 변수들부터 정의하면서 시작합시다. 다음과 같이 배열 내 구성원소 총 갯수를 nt라고 하고, 값이 1인 원소의 갯수를 ns라고 정의합니다.


nt = 10

ns = 3


이 배열은 총 10개의 원소들을 갖고 있으므로 인덱스는 0~9가 됩니다. 따라서 0~9의 범위내에서 임의의 세개의 인덱스에 해당되는 숫자값들을 생성하기 위하여 RANDOMU 함수를 사용한다면 일단 다음과 같은 형태를 생각해볼 수 있습니다.


inds = ROUND(RANDOMU(seed, ns)*(nt-1))


이렇게 약간 복잡해 보이게 된 이유는, RANDOMU 함수는 0~1 범위내의 실수값을 생성하게 되어 있기 때문입니다. 그래서 정수형의 인덱스 값을 생성하기 위하여 실수를 반올림하여 정수로 변환해주는 역할을 하는 ROUND 함수를 사용하였습니다. 위와 같은 내용을 IDL 콘솔창에서 한번 테스트해보면 다음과 같이 세개의 정수형 값들이 산출되는 것을 확인할 수 있습니다.


IDL> PRINT, ROUND(RANDOMU(seed, 3)*(10-1))

           1           9           3


물론 랜덤으로 생성되기 때문에 여러분이 해보시면 그리고 여러번 해보시면 매번 값들은 다르게 나옵니다. 어쨌든 우리의 작업에서는 이 세개의 정수형 값들이 1이 되도록 해주면 된단 얘기입니다. 따라서 다음과 같이 나머지 내용을 작성하면 됩니다.


data = INTARR(nt)

data[inds] = 1

PRINT, data


이렇게 하여 얻어진 data라는 배열의 구성원소 값들을 출력해보면, 우리가 의도했던대로 세개의 1이란 값들이 임의의 위치에 분포한 배열을 만들 수 있습니다. 그런데!! 이렇게 끝나면 좋겠지만 사실 한가지 문제가 있습니다. 위와 같이 RANDOMU 함수를 기반으로 하여 랜덤으로 생성되는 인덱스 값들이 간혹 다음과 같이 생성되는 경우가 있습니다.


IDL> PRINT, ROUND(RANDOMU(seed, 3)*(10-1))

           2           2           4


이런 경우라면 생성된 숫자 자체는 세개이지만 실질적으로는 두개만 생성된 것이나 마찬가지입니다. 더 심한 경우에는 다음과 같이 세개다 똑같은 값이 나올 수도 있습니다.


IDL> PRINT, ROUND(RANDOMU(seed, 3)*(10-1))

           5           5           5


과연 이런 경우에 대해서는 어떻게 대처를 해야 할까요? 좀 단순하고 허무한 답일 수도 있겠지만, 다시 하면 됩니다. 그런데 만약 다시 했는데도 또 위와 같은 경우가 나오면 어떻게 해야 할까요? 그러면 또 다시 하면 됩니다. 언제까지 하냐고요? 될 때까지입니다. 즉 서로 다른 세개의 값들이 온전하게 생성될 때까지 계속 한단 얘기입니다. 난수의 발생 결과는 아무도 예측할 수 없기 때문에 어느 정도는 감수해야 할 부분이라고 봅니다. 물론 실제로 해보면, 서로 다른 세개의 값이 생성될 확률이 훨씬 더 높기 때문에 소요시간은 얼마 안걸립니다. 행여나 시간 엄청 잡아먹지 않을까 하는 걱정은 전혀 안하셔도 됩니다.


이러한 작업은 반복형 구문을 사용해야 하는데, 일반형인 FOR보다는 REPEAT라는 것을 사용하는 것이 가장 적합합니다. 일단 구현된 코드부터 소개하면 다음과 같습니다.


REPEAT BEGIN

  inds = ROUND(RANDOMU(seed, ns)*(nt-1))

  wq = UNIQ(inds, SORT(inds))

  np = N_ELEMENTS(wq)

  PRINT, np

ENDREP UNTIL np EQ ns


REPEAT 구문은 조건형 반복문이라고 볼 수 있는데, 반복 횟수를 정해놓고 작업하는 FOR와는 다른방식입니다. FOR는 ENDFOR로 마무리되지만, REPEAT는 ENDREP UNTIL로 마무리가 됩니다. UNTIL 뒤에 제시된 논리식이 참이 될 때까지 반복을 계속 진행하라는 의미입니다. 즉 "~~~일때까지 계속"이란 의미로 보면 됩니다. 물론 논리식이 참이 되는 순간 반복은 종료됩니다. 따라서 위의 코드의 의미는 랜덤으로 0~9 범위내의 세개의 정수들을 생성하되, 서로 다른 값 세개가 될 때까지 계속 반복하란 얘기입니다. 혹시라도 서로 다른 값들이 두개 또는 한개만 나올 경우에는 다시 반복되고, 세개가 나오면 반복을 더 이상 하지 않습니다.


그리고 위의 내용에서는 UNIQ라는 내장함수도 사용되었는데, 어떤 배열 내에서 서로 배타적인 값들만을 골라내는 역할을 합니다. 예를 들어 배열이 [1, 5, 3, 7, 3]과 같다면 중복되는 값들은 하나로 퉁치고 [1, 3, 5, 7]이란 결과를 돌려주는 기법이 여기서 사용되었다고 보시면 됩니다. 이 함수에 관한 자세한 내용은 IDL 도움말을 참조하시기 바랍니다.


어쨌든 위와 같은 REPEAT 구문을 사용함으로써 정확히 세개의 임의의 값들을 얻을 수 있게 됩니다. 전체적으로 정리해보면 다음과 같은 내용이 될 것입니다.


nt = 10

ns = 3

p = FLOAT(ns)/nt

REPEAT BEGIN

  inds = ROUND(RANDOMU(seed, ns)*(nt-1))

  wq = UNIQ(inds, SORT(inds))

  np = N_ELEMENTS(wq)

  PRINT, np

ENDREP UNTIL np EQ ns

data = INTARR(nt)

data[inds] = 1

PRINT, 'Total number of 1s :', FIX(TOTAL(data))

ww = WHERE(data EQ 1)

PRINT, ww

PRINT, data


물론 여기서 nt와 ns의 값을 변경하면 내가 원하는 크기의 배열에 대하여 원하는 갯수만큼의 임의의 인덱스에 1이란 값이 위치하도록 하는 것이 가능합니다. 만약 1000개의 값들로 구성된 배열 내에서 20개의 1이란 값들이 임의의 위치에 존재하고 나머지 980개는 0이 되도록 하려면 nt, ns의 값을 각각 1000, 20으로 설정하면 됩니다.

반응형