ADJUST-ARRAY — 배열의 차원·내용 조정

ADJUST-ARRAY — 배열의 차원·내용 조정

ADJUST-ARRAY는 함수예요. 기존 배열의 차원이나 내용을 바꿀 때 써요. 배열을 처음 만들 때 :adjustable t로 조정 가능하게 만들어 두면, 이 함수가 원래 배열을 그대로 조정해서 돌려줘요. 조정이 가능하지 않은 배열이라면 새 배열을 만들어 반환하기도 하죠. make-array가 "처음부터" 배열을 만든다면, adjust-array는 "이미 있는 배열을 재구성"한다고 보면 돼요.

출처: CLHS Function ADJUST-ARRAY

본문

시그니처는 꽤 많은 키워드 인자를 받아요.

adjust-array array new-dimensions &key element-type initial-element
            initial-contents fill-pointer displaced-to
            displaced-index-offset => adjusted-array

인자들을 하나씩 보죠.

  • array — 조정할 배열.
  • new-dimensions — 유효한 배열 차원 하나, 또는 유효한 차원들의 리스트.
  • element-type — 타입 스펙시파이어. 결과 배열의 원소 타입을 지정해요.
  • initial-element — 객체. 단, initial-contentsdisplaced-to를 주면 함께 제공하면 안 돼요.
  • initial-contents — 객체. 배열의 rank가 0보다 크면 중첩 시퀀스로 구성되고 그 깊이는 rank와 같아야 해요. rank가 0이면 단일 원소를 제공해요. initial-element이나 displaced-to를 주면 안 돼요.
  • fill-pointer — 만들 배열의 유효한 필 포인터. 또는 tnil. 기본값은 nil이에요.
  • displaced-to — 배열 또는 nil. 제공되면 initial-elementsinitial-contents를 함께 쓰면 안 돼요.
  • displaced-index-offset(fixnum 0 n) 타입 객체(여기서 n은 (array-total-size displaced-to)). displaced-to가 제공될 때만 쓸 수 있어요.
  • adjusted-array — 결과 배열.

동작 방식

adjust-arrayarray의 차원이나 원소를 바꿔요. 결과는 array와 같은 타입·같은 rank의 배열로, 두 가지 경우가 있어요. 하나는 수정된 원래 배열이고, 다른 하나는 방금 새로 만들어져 array가 변위(displaced)될 수 있는 배열이에요. 어느 쪽이든 지정된 new-dimensions를 갖죠.

  • element-type을 주면 결과 배열의 원소 타입이 돼요. 다만 element-type의 상위 배열 원소 타입(upgraded array element type)이 array의 실제 배열 원소 타입과 다르면 결과는 불특정(unspecified)이에요.
  • initial-contents를 주면 make-array와 같은 방식으로 처리돼요. 이 경우 원래 배열의 내용은 결과 배열에 전혀 남지 않아요.
  • fill-pointer가 정수면 결과 배열의 필 포인터가 돼요. t면 결과 배열의 크기를 필 포인터로 쓰겠다는 뜻이고, nil이면 필 포인터를 그대로 둬요.
  • displaced-tonil이 아니면 변위 배열(displaced array)을 만들어요. 결과 배열은 displaced-to가 가리키는 배열과 내용을 공유하고, 그 배열보다 많은 원소를 담을 수는 없어요. displaced-to를 주지 않거나 nil이면 결과는 변위 배열이 아니에요.

변위 배열과의 상호작용

adjust-array와 변위 배열의 관계는 네 가지 경우로 나눠 정리돼 있어요. 배열 A, B, C 세 개가 있다고 생각해 볼게요.

A가 호출 전에도 후에도 변위되어 있지 않은 경우(adjust-array A ...)

A의 차원이 바뀌고 내용이 적절히 재배열돼요. 새로 생긴 원소들은 initial-element에서 가져와요. initial-contents를 쓰면 기존 내용은 모두 버려져요.

A가 호출 전엔 변위되어 있지 않지만 호출 후 C에 변위되는 경우(adjust-array A ... :displaced-to C)

이후 A에 기존 내용이 하나도 남지 않고, A는 C의 내용을 그대로 담아요(C 자체의 재배열은 없음).

A가 호출 전에 B에 변위되어 있고 호출 후 C에 변위되는 경우(adjust-array A ... :displaced-to B), (adjust-array A ... :displaced-to C)

B와 C가 같을 수도 있어요. B의 내용은, 그 내용이 우연히 C에도 있다 하더라도, 이후 A에 나타나지 않아요. displaced-index-offset을 주지 않으면 기본값 0이 되고, B로의 기존 오프셋은 유지되지 않아요.

A가 호출 전에 B에 변위되어 있고 호출 후엔 변위되지 않는 경우(adjust-array A ... :displaced-to B), (adjust-array A ... :displaced-to nil)

A는 새 "데이터 구역"을 얻고, 기존 내용을 유지하도록 B의 내용이 복사돼요. 추가 원소는 initial-element에서 가져와요. 다만 initial-contents를 쓰면 기존 내용이 모두 버려지죠.

displaced-index-offset을 주면 결과 배열이 변위 대상 배열의 시작점에서 얼마나 떨어져 있는지를 지정해요. 주지 않으면 0이에요. 결과 배열의 크기 더하기 오프셋 값은 변위 대상 배열의 크기를 넘을 수 없어요.

결과 배열과 원소 초기화

new-dimensionsinitial-element만 제공하면, array의 경계 안에 있던 원소들은 결과 배열에 그대로 나타나요. 경계를 벗어난 새 원소는 initial-element로 초기화되는데, initial-element를 주지 않았다면 그런 새 원소를 초기화 전에 읽으면 결과는 정의되지 않아요(undefined).

initial-contentsdisplaced-to를 주면 원래 배열의 내용은 결과 배열에 하나도 나타나지 않아요.

주의할 결과 불특정 상황이 몇 가지 있어요.

  • 배열을 fill-pointer 인자 없이 필 포인터보다 작게 조정하면, 필 포인터가 제대로 조정되지 않아 결과가 불특정이에요.
  • A가 B에 변위되어 있는데, B가 A에게 더 이상 충분한 원소를 제공하지 못하도록 조정되면 결과가 불특정이에요.

조정 가능한 배열에 적용하면 반환 배열은 원래 배열과 identical해요. 반환 배열이 원래 배열과 다르다면, 원래 배열 인자는 그대로(변경 없이) 남아요.

한 가지 더 기억할 점이 있어요. A가 B에 변위되고, B가 다시 C에 변위되는 상황에서 B가 adjust-array로 변경되면, A는 반드시 조정된 B의 내용을 가리켜야 해요. 즉 구현체가 "A가 곧바로 C를 가리킨다"고 체인을 접어 버리면 안 돼요. 물론 여기 규정된 의미론을 지키는 한 캐싱 기법은 허용돼요.

예제

먼저 크기와 내용을 조정해 봐요.

(adjustable-array-p (setq ada (adjust-array (make-array '(2 3)
                                          :adjustable t
                                          :initial-contents '((a b c) (1 2 3)))
                              '(4 6)))) => T
(array-dimensions ada) => (4 6)
(aref ada 1 1) => 2

(2 3) 배열을 (4 6)으로 키운 결과, 기존 내용은 유지되고 차원이 늘어났죠. 이제 변위 배열을 만들어 볼게요.

(setq beta (make-array '(2 3) :adjustable t)) => #2A((NIL NIL NIL) (NIL NIL NIL))
(adjust-array beta '(4 6) :displaced-to ada)
=> #2A((A B C NIL NIL NIL)
       (1 2 3 NIL NIL NIL)
       (NIL NIL NIL NIL NIL NIL)
       (NIL NIL NIL NIL NIL NIL))
(array-dimensions beta) => (4 6)
(aref beta 1 1) => 2

betaada에 변위되면서 ada의 내용을 공유하는 걸 볼 수 있어요. 마지막으로 initial-element를 쓰는 예제를 볼게요. 4×4 배열 m이 다음과 같다고 해 보죠.

#2A((alpha beta gamma delta)
    (epsilon zeta eta theta)
    (iota kappa lambda mu)
    (nu xi omicron pi))

(adjust-array m '(3 5) :initial-element 'baz)의 결과는 3×5 배열이고 내용은 이렇게 돼요.

#2A((alpha beta gamma delta baz)
    (epsilon zeta eta theta baz)
    (iota kappa lambda mu baz))

예외 상황으로는, fill-pointer가 제공되고 nil이 아닌데 배열에 필 포인터가 없다면 error 타입 오류가 발생해요. 관련 함수로는 adjustable-array-p, make-array, array-dimension-limit, array-total-size-limit, array가 있어요.