ADJUST-ARRAY — 배열의 차원·내용 조정
ADJUST-ARRAY — 배열의 차원·내용 조정
ADJUST-ARRAY는 함수예요. 기존 배열의 차원이나 내용을 바꿀 때 써요. 배열을 처음 만들 때 :adjustable t로 조정 가능하게 만들어 두면, 이 함수가 원래 배열을 그대로 조정해서 돌려줘요. 조정이 가능하지 않은 배열이라면 새 배열을 만들어 반환하기도 하죠. make-array가 "처음부터" 배열을 만든다면, 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-contents나displaced-to를 주면 함께 제공하면 안 돼요. - initial-contents — 객체. 배열의 rank가 0보다 크면 중첩 시퀀스로 구성되고 그 깊이는 rank와 같아야 해요. rank가 0이면 단일 원소를 제공해요.
initial-element이나displaced-to를 주면 안 돼요. - fill-pointer — 만들 배열의 유효한 필 포인터. 또는
t나nil. 기본값은nil이에요. - displaced-to — 배열 또는
nil. 제공되면initial-elements와initial-contents를 함께 쓰면 안 돼요. - displaced-index-offset —
(fixnum 0 n)타입 객체(여기서 n은(array-total-size displaced-to)).displaced-to가 제공될 때만 쓸 수 있어요. - adjusted-array — 결과 배열.
동작 방식
adjust-array는 array의 차원이나 원소를 바꿔요. 결과는 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-to가nil이 아니면 변위 배열(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-dimensions와 initial-element만 제공하면, array의 경계 안에 있던 원소들은 결과 배열에 그대로 나타나요. 경계를 벗어난 새 원소는 initial-element로 초기화되는데, initial-element를 주지 않았다면 그런 새 원소를 초기화 전에 읽으면 결과는 정의되지 않아요(undefined).
initial-contents나 displaced-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
beta가 ada에 변위되면서 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가 있어요.