트랙
/
Julia
Julia
/
연습 문제
/
순서 대소동
순서 대소동

순서 대소동

학습 연습 문제

소개

Julia는 대부분의 프로그래밍 언어처럼 다양한 종류의 컬렉션을 제공해요.

일부는 본질적으로 순서가 있어요 (Vectors와 더 높은 차원의 Arrays 등). 그래서 위치로 원소에 접근할 수 있어요. 다른 컬렉션은 순서가 없고 (Sets, Dicts), 원소에 키나 값으로 접근해야 해요.

가장 단순한 경우로, Ints의 Vector가 있고 이를 오름차순으로 정렬하고 싶다고 해봐요.

julia> v = [3, 2, 4, 1]
4-element Vector{Int64}:
 3
 2
 4
 1

julia> sort(v)
4-element Vector{Int64}:
 1
 2
 3
 4

# sort descending
julia> sort(v, rev=true)
4-element Vector{Int64}:
 4
 3
 2
 1

간단하죠! sort() 함수는 이름만 봐도 알 수 있듯이, 겉보기에는 정렬된 Vector를 반환하는 것처럼 보여요.

농담이에요! 커리큘럼상 이 단계쯤이면, 우리가 논의해야 할 미묘한 점이 있다고 (제대로) 짐작하고 있을지도 몰라요.

변경 함수와 비변경 함수

sort(v)를 실행한 뒤, v는 어떻게 됐을까요?

julia> v
4-element Vector{Int64}:
 3
 2
 4
 1

julia> issorted(v)
false

원래 벡터는 그대로예요. sort()는 비변경 함수로, 입력의 _복사본_을 반환해요.

이것은 많은 상황에서 좋은 설계예요. 순수 함수라서, 병렬 분산 code에서도 동작을 추론하기 쉬워요.

반면에, 10억 개의 문자열로 이루어진 배열을 정렬한다고 해봐요.

정렬은 아마 느릴 거예요. 게다가 모든 것을 복사해야 하고 (느려요!), 복사본을 저장할 메모리 (수 기가바이트!)도 충분히 확보해야 해요.

아주 비슷한 다음 code와 비교해봐요:

# v is as previously

julia> issorted(v)
false

julia> sort!(v)
4-element Vector{Int64}:
 1
 2
 3
 4

julia> v
4-element Vector{Int64}:
 1
 2
 3
 4

julia> issorted(v)
true

이제 입력 Vector는 복사 없이 제자리에서 정렬되고, 원래 순서는 영구히 사라져요.

이 방식은 효율적일 수도 있고, bug가 생기거나 예상치 못한 부작용을 일으킬 수도 있으며, code를 사용하는 다른 사람을 혼란스럽게 할 수도 있어요. 신중하게 선택해요!

sort!()는 "변경 함수"이고, (아주 강한) 관례에 따라 입력을 변경하는 함수는 프로그래머에게 경고하는 의미로 이름 끝에 !를 붙여요.

정렬 인덱스 배열

정렬하려는 원소가 큰 객체라면, 각 원소의 위치 인덱스로 작업하는 편이 더 쉬울 수 있어요.

julia> langs = ["Julia", "C++", "Python", "Elixir"]
4-element Vector{String}:
 "Julia"
 "C++"
 "Python"
 "Elixir"

julia> inxs = sortperm(langs)  # => Integers
4-element Vector{Int64}:
 2
 4
 1
 3

julia> langs[inxs]  # sort the entire vector
4-element Vector{String}:
 "C++"
 "Elixir"
 "Julia"
 "Python"

julia> langs[inxs[1:2]]  # slice the indices
2-element Vector{String}:
 "C++"
 "Elixir"

langs가 Vector라서, 각 인덱스는 1:length(langs) 범위의 정수예요. sortperm()의 출력은 이 정수들의 순열이고, 이는 입력 벡터를 정렬한 결과에 대응해요.

지침

거북이용 이불을 만드는 회사에 보조 생산 엔지니어로 입사했어요.

주문은 하루 종일 고객 이름이나 번호, 그리고 주문 수량의 형태로 들어와요. 이 주문들은 그날의 생산이 시작되기 전 아침에 처리돼요.

그날의 주문이 모두 채워지면 급여는 그대로 받으면서 집에 갈 수 있어요. 그래서 근무 시간을 최대한 줄일 유인이 있죠. 각 주문은 기계 한 대가 처리하기 때문에, 가장 큰 주문을 먼저 일정에 넣으면 작업을 시작하는 데 드는 부담을 작은 주문 쪽으로 넘길 수 있어서 전체 생산 시간이 줄어들어요. 그래서 이런 일정 단축을 돕는 정렬 함수 몇 개를 만들기로 했어요. 그러면 집에 가서 거북이용 스웨터를 뜰 수 있으니까요.

1. 수량 정렬!

앞서 말했듯이, 수량 배열은 큰 것부터 작은 것 순으로 정렬해야 해요. 이 배열은 꽤 클 수 있으므로 제자리에서 정렬해야 해요. 모든 것을 올바르게 추적하려면 수량에 맞춰 고객 배열도 정렬할 수 있어야 하므로, 수량의 정렬 순열이 필요해요.

Integer 수량의 Vector를 받는 함수 sortquantity!(qty)를 작성해요. 이 함수는 입력 qty를 내림차순으로 제자리에서 정렬하고, 이 순서의 정렬 순열을 반환해요.

julia> qty = [8, 9, 5, 6]
4-element Vector{Int64}:
 8
 9
 5
 6

julia> sortquantity!(qty)
4-element Vector{Int64}:
 2
 1
 4
 3

julia> qty # original vector is sorted in-place
4-element Vector{Int64}:
 9
 8
 6
 5

2. 고객 정렬

위에서 말했듯이, 고객을 정해진 순서대로 추적해야 해요.

고객 cust의 Vector와 정렬 순열 srtperm의 Vector를 받는 함수 sortcustomer(cust, srtperm)를 작성해요. 이 함수는 정렬 순열이 정한 순서대로 고객을 담은 새 배열을 반환해요.

julia> cust = ["a", "b", "c"]
3-element Vector{String}:
 "a"
 "b"
 "c"

julia> sortcustomer(cust, [2, 3, 1])
3-element Vector{String}:
 "b"
 "c"
 "a"

julia> cust # original vector remains unsorted
3-element Vector{String}:
 "a"
 "b"
 "c"

3. 생산 일정!

이제 이 모든 것을 합쳐서 거북이들을 따뜻하게 지켜줄 수 있어요! 이제 수량과 고객을 제대로 정렬할 수 있지만, 배송을 위해 원래 순서를 되돌릴 수 있어야 해요. 이는 역 정렬 순열로 할 수 있어요.

고객의 Vector와 수량의 Vector를 받는 함수 production_schedule!(cust, qty)를 작성해요. 이 함수는 수량을 내림차순으로 제자리에서 정렬해요. 수량과 같은 순서로 정렬된 고객의 새 Vector와, 정렬된 벡터를 원래 순서로 되돌리는 데 쓸 수 있는 역 정렬 순열을 반환해요.

julia> cust, qty = ["a", "b", "c"], [2, 3, 1]
(["a", "b", "c"], [2, 3, 1])

julia> orderedcust, invperm = production_schedule!(cust, qty)
(["b", "a", "c"], [2, 1, 3])

julia> qty # has been sorted in-place
3-element Vector{Int64}:
 3
 2
 1

julia> orderedcust[invperm], qty[invperm] # inverse permutation gives original ordering
(["a", "b", "c"], [2, 3, 1])
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Julia Exercism

순서 대소동 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.