배열
배열 (Arrays)
OCaml에서 배열(array)은 한 타입의 요소들을 모아 둔 자료구조예요. 리스트와 달리 배열은 같은 타입의 다른 값으로 요소를 바꿔치기할 수 있어요(가변적이라는 뜻이죠). 다만 크기를 바꿀 수는 없어요. 또 배열은 어떤 위치의 요소에든 효율적으로 접근할 수 있어요.
이런 차이에도 불구하고 배열에서 바로 쓸 수 있는 함수 상당수가 리스트에서 쓰는 함수와 비슷해요. 이 함수들의 자세한 내용은 List 튜토리얼과 문서를 참고하세요.
이 튜토리얼에서는 OCaml의 배열이 무엇인지 소개하고, 가장 유용한 함수와 쓰임새를 보여드릴게요.
배열은 OCaml에서 이런 작업에 흔히 쓰여요.
- 많은 양의 데이터를 저장하고 처리하기
- 요소에 대한 임의 접근(random access)과 수정이 필요한 알고리즘을 구현하기
- 행렬(matrix)이나 그 밖의 다차원 자료구조를 다루기
출처: OCaml 공식 문서
본문
배열 만들기 (Creating Arrays)
OCaml에서 배열을 만들려면 [| ...; ... |] 문법을 쓸 수 있어요. 이 문법으로 각 요소의 값을 직접 지정할 수 있답니다. 예를 들어 1, 2, 3, 4, 5라는 값을 가진 배열을 만들려면 [| 1; 2; 3; 4; 5 |]라고 쓰면 돼요.
# [| 1; 2; 3; 4; 5 |];;
- : int array = [|1; 2; 3; 4; 5|]
배열을 만드는 다른 방법으로 Array.make 함수가 있어요. 이 함수는 인자 두 개를 받는데, 배열의 길이와 각 요소의 초기값이에요. 길이 5짜리 배열을 모든 요소를 0으로 초기화해서 만들려면 이렇게 써요.
# let zeroes = Array.make 5 0;;
val zeroes : int array = [|0; 0; 0; 0; 0|]
Array.init은 주어진 길이의 배열을, 배열의 각 인덱스(0부터 시작)에 함수를 적용해서 만들어 내요. 다음 코드는 인자를 두 배로 만드는 함수를 이용해 처음 5개의 짝수를 담은 배열을 생성해요.
# let even_numbers = Array.init 5 (fun i -> i * 2);;
val even_numbers : int array = [|0; 2; 4; 6; 8|]
배열 요소에 접근하기 (Accessing Array Elements)
배열의 개별 요소에 접근할 땐 .(index) 문법을 쓰고, 괄호 안에 접근하려는 요소의 인덱스를 넣어요. 첫 번째 요소의 인덱스는 0이고, 마지막 요소의 인덱스는 배열 크기에서 1을 뺀 값이에요. 예를 들어 위에서 만든 even_numbers 배열의 세 번째 요소에 접근하려면 이렇게 써요.
# even_numbers.(2);;
- : int = 4
배열 요소 수정하기 (Modifying Array Elements)
배열의 요소를 수정하려면 인덱싱 연산자로 그 요소에 새 값을 할당하면 돼요. 예를 들어 위에서 만든 even_numbers 배열의 세 번째 요소 값을 42로 바꾸려면 이렇게 써요.
# even_numbers.(2) <- 42;;
- : unit = ()
이 연산이 unit을 돌려준다는 점을 주목하세요. 수정된 배열 자체를 돌려주는 게 아니에요. even_numbers는 부수 효과(side effect)로 제자리에서 수정돼요.
표준 라이브러리의 Array 모듈 (The Standard Library Array Module)
OCaml은 배열을 다루는 유용한 함수를 여럿 제공해요. 그중 가장 흔한 것들을 살펴볼게요.
배열의 길이 (Length of an Array)
Array.length 함수는 배열의 크기를 돌려줘요.
# Array.length even_numbers;;
- : int = 5
배열에 이터레이트하기 (Iterate on an Array)
Array.iter는 배열의 각 요소에 함수를 하나씩 차례로 적용해요. 주어진 함수는 부수 효과로 동작하며 unit을 돌려줘야 해요. 위에서 만든 zeroes 배열의 모든 요소를 출력하려면 각 요소에 print_int를 적용하면 돼요.
# Array.iter (fun x -> print_int x; print_string " ") zeroes;;
0 0 0 0 0 - : unit = ()
배열에 이터레이트하는 것은 for 루프로도 할 수 있어요. 다음은 루프를 쓴 같은 예시예요.
# for i = 0 to Array.length zeroes - 1 do
print_int zeroes.(i);
print_string " "
done;;
0 0 0 0 0 - : unit = ()
배열에 맵하기 (Map an Array)
Array.map 함수는 배열의 각 요소에 주어진 함수를 적용해 새 배열을 만들어 내요. 예를 들어 even_numbers 배열의 각 숫자에 제곱을 적용한 배열을 얻을 수 있어요.
# Array.map (fun x -> x * x) even_numbers;;
- : int array = [|0; 4; 1764; 36; 64|]
배열 폴딩하기 (Folding an Array)
배열의 모든 요소를 하나의 결과로 합치려면 Array.fold_left와 Array.fold_right 함수를 쓸 수 있어요. 이 함수들은 이항 함수(binary function)와 초기 누적값(accumulator), 그리고 배열을 인자로 받아요. 이항 함수는 누적기의 현재 값과 배열의 현재 요소 두 개를 인자로 받아 새 누적값을 돌려줘요. 두 함수 모두 배열을 훑지만 그 방향이 반대예요. 본질적으로 List.fold_left와 List.fold_right와 같답니다.
Array.fold_left의 시그니처는 다음과 같아요.
# Array.fold_left;;
val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b array -> 'a = <fun>
fold_left f init a는 f (... (f(f init a.(0)) a.(1)) ...) a.(n-1)를 계산해요.
마찬가지로 인자의 순서가 뒤바뀐 Array.fold_right 함수도 쓸 수 있어요.
# Array.fold_right;;
val fold_right : ('b -> 'a -> 'a) -> 'b array -> 'a -> 'a = <fun>
fold_right f a init는 f a.(0) (f a.(1) ( ... (f a.(n-1) init) ...))를 계산해요.
이 함수들은 배열 전체에서 하나의 값을 도출해 내요. 예를 들어 배열의 최댓값을 찾는 데 쓸 수 있어요.
# Array.fold_left Int.max min_int even_numbers;;
- : int = 42
배열 정렬하기 (Sorting an Array)
배열을 정렬하려면 Array.sort 함수를 쓸 수 있어요. 이 함수는 인자로 다음을 받아요.
- 비교 함수
- 배열
주어진 비교 함수 기준으로 배열을 제자리에서 오름차순으로 정렬해요. Array.sort가 수행하는 정렬은 배열의 내용을 수정하기 때문에 unit을 돌려줘요. 예를 들어 위에서 만든 even_numbers 배열을 정렬하려면 이렇게 쓸 수 있어요.
# Array.sort compare even_numbers;;
- : unit = ()
# even_numbers;;
- : int array = [|0; 2; 6; 8; 42|]
배열의 일부를 다른 배열로 복사하기 (Copying Part of an Array into Another Array)
Array.blit 함수는 배열의 연속된 일부를 다른 배열로 효율적으로 복사해요. array.(x) <- y 연산과 비슷하게 이 함수는 대상(destination)을 제자리에서 수정하고 unit을 돌려줘요. 수정된 배열을 돌려주는 게 아니랍니다. ones의 일부를 zeroes로 복사하고 싶다고 가정해 볼게요.
# let ones = Array.make 5 1;;
val ones : int array = [|1; 1; 1; 1; 1|]
# Array.blit ones 0 zeroes 1 2;;
- : unit = ()
# zeroes;;
- : int array = [|0; 1; 1; 0; 0|]
이 코드는 ones의 인덱스 0부터 시작하는 요소 두 개(이 배열 슬라이스는 [| 1; 1 |])를 zeroes의 인덱스 1부터 시작하는 위치로 복사해요. 제공한 두 인덱스가 각각의 배열에서 유효한지, 또 복사할 요소 개수가 각 배열의 경계 안에 있는지는 여러분이 확인해야 할 책임이에요.
이 함수로 배열의 일부를 자기 자신 위로 복사할 수도 있어요.
# Array.blit zeroes 1 zeroes 3 2;;
- : unit = ()
# zeroes;;
- : int array = [|0; 1; 1; 1; 1|]
이 코드는 zeroes의 인덱스 1부터 시작하는 요소 두 개를 zeroes의 인덱스 3부터 시작하는 끝부분으로 복사해요.
결론 (Conclusion)
이 튜토리얼에서는 OCaml 배열의 기초를 다뤘어요. 배열을 만들고 조작하는 방법, 그리고 가장 유용한 몇몇 함수와 쓰임새를 살펴봤죠. Array 모듈 함수의 전체 목록을 보려면 표준 라이브러리 문서를 참고하세요.
더 알아보기
- OCaml 공식 문서 - Arrays
- List 튜토리얼 — 배열과 닮은 리스트 자료구조와 함수들
- Array 모듈 — 표준 라이브러리의 배열 함수 전체 목록
- 연습문제: English Number Words, Sudoku