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()は非破壊的な関数で、入力の_コピー_を返します。
多くの場面でこれはよい設計です。純粋な関数であり、並列・分散コードであっても動作を推論しやすいからです。
一方で、10億個の文字列からなる配列を並べ替えたいとします。
並べ替えにはおそらく時間がかかります。 さらに、すべてをコピーする必要があり(遅い!)、そのコピーを保存するために十分なメモリ(数ギガバイト!)を確保しなければなりません。
よく似た次のコードと比べてみましょう。
# 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がコピーなしでその場で並べ替えられ、元の順序は永久に失われます。
これは効率的なこともありますが、バグを生んで予期しない副作用を引き起こすこともあり、コードを使うほかの人を混乱させることもあります。 慎重に選びましょう!
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
前述のとおり、顧客を決まった順序で管理する必要があります。
顧客のVectorであるcustと、ソート順列のVectorであるsrtpermを受け取る、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"
これで、すべてを組み合わせて、あのカメたちを暖かくしてあげられます! これで数量と顧客を正しくソートできますが、発送のために元の並び順を取り戻せる必要もあります。 これは逆ソート順列を使えばできます。
顧客の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])