【Julia入門】「Order.Reverse」でソートを降順にする方法と応用例
Order.Reverse
は、Julia のソート関連機能で、要素の順序を「逆順」に指定するために使用される概念です。これは、主に sort
や sort!
関数などの引数として登場します。
もう少し具体的に説明すると、以下の2つの方法で逆順を指定できます。
-
rev=true キーワード引数
これが最も一般的で簡単な方法です。sort
関数やsort!
関数にrev=true
を渡すと、デフォルトの昇順ではなく、降順にソートされます。例
arr = [2, 3, 1] sorted_arr_desc = sort(arr, rev=true) println(sorted_arr_desc) # 出力: [3, 2, 1]
by
キーワード引数と組み合わせることもできます。by
で指定した関数が適用された結果に基づいてソートされ、その結果がrev=true
によって逆順になります。# 絶対値でソートし、その結果を逆順にする v = [0.2, -0.5, 0.3, -0.8] sorted_v_abs_desc = sort(v, by=abs, rev=true) println(sorted_v_abs_desc) # 出力: [-0.8, -0.5, 0.3, 0.2] (絶対値の降順)
-
order=Order.Reverse (より高度な使い方)
これは、Base.Order
モジュールで定義されているOrdering
型の一つです。通常はrev=true
で十分ですが、ソートの「順序」をより明示的に、かつより細かく制御したい場合に使用されます。Juliaのソート関数は、
lt
(less than) 関数とby
関数、そしてorder
オブジェクトを組み合わせてソートの基準を定義します。Order.Reverse
は、このorder
オブジェクトとして渡すことで、比較のロジック全体を反転させます。これは、カスタムの比較ロジック(
lt
関数)を定義している場合など、より複雑なソートシナリオで役立つことがあります。ただし、ほとんどのユースケースではrev=true
の方が直感的で使いやすいでしょう。
- より低レベルなソート関数の引数として
order=Order.Reverse
を使うこともできますが、これは通常はrev=true
で代替可能です。 - 最も一般的な使い方は、
sort
やsort!
関数にrev=true
というキーワード引数を渡すことです。 Order.Reverse
は、Juliaでのデータのソート順序を逆転させるための概念です。
Order.Reverse
自体が直接エラーを引き起こすことは稀で、通常はソート対象のデータ型や、by
や lt
(less than) といったソートの基準をカスタマイズする際に問題が発生することが多いです。
rev=true の誤解
最も一般的な逆順ソートの方法は rev=true
キーワード引数を使用することです。これに関連する誤解や小さな問題は以下の通りです。
問題
- 「
rev=true
を指定したのに、期待通りの逆順にならない」 これは、ソート対象の要素にカスタムの比較ロジックが適用されている場合に発生しやすいです。例えば、構造体のソートで独自のisless
メソッドを定義している場合などです。
トラブルシューティング
- by と rev の組み合わせを理解する
このように、struct MyStruct value::Int id::String end data = [MyStruct(10, "B"), MyStruct(5, "A"), MyStruct(15, "C")] # valueの昇順でソート (デフォルト) sorted_asc = sort(data, by=x -> x.value) println(sorted_asc) # [MyStruct(5, "A"), MyStruct(10, "B"), MyStruct(15, "C")] # valueの降順でソート sorted_desc = sort(data, by=x -> x.value, rev=true) println(sorted_desc) # [MyStruct(15, "C"), MyStruct(10, "B"), MyStruct(5, "A")]
by
で指定した変換後の値の順序がrev=true
で逆転します。期待するソート順序がこのロジックに合致しているか確認してください。 - ソートの基準を確認する
sort
関数は、デフォルトでは要素の自然な順序 (isless
関数によって定義される) に基づいてソートします。もし、by
キーワード引数を使ってソートの基準となる値を変換している場合、rev=true
はその変換された値に対して逆順を適用します。
カスタムの比較関数 (lt) と Order.Reverse の組み合わせ
lt
キーワード引数を使って独自の比較関数を渡す場合、Order.Reverse
との組み合わせで混乱が生じることがあります。
問題
- 「
lt
関数で定義した比較ロジックがrev=true
と矛盾する」lt
(less than) 関数は、a < b
が真であればtrue
を返すように定義します。つまり、a
がb
より「小さい」場合にtrue
を返します。rev=true
はこの「小さい」という定義を逆転させます。もしlt
関数自体が既に降順のロジックで書かれている場合、rev=true
を適用すると、結果的に昇順に戻ってしまう可能性があります。
トラブルシューティング
- Order.Reverse を直接使う場合(稀なケース)
order=Order.Reverse
を使う場合は、そのorder
オブジェクトが全体の比較ロジックを逆転させます。lt
やby
との相互作用は複雑になる可能性があるため、特別な理由がない限りrev=true
の使用が推奨されます。 - lt 関数は常に「昇順」の比較ロジックで書く
lt
関数は、常に「もしa
がb
より小さければtrue
」という昇順の比較ロジックで記述するのがベストプラクティスです。そして、逆順にしたい場合はrev=true
を使うようにします。# カスタムの比較関数 (昇順のロジックで書く) my_lt(a, b) = abs(a) < abs(b) # 絶対値で比較 (昇順) v = [0.2, -0.5, 0.3, -0.8] # 絶対値の昇順でソート sorted_abs_asc = sort(v, lt=my_lt) println(sorted_abs_asc) # [0.2, 0.3, -0.5, -0.8] (絶対値の昇順) # 絶対値の降順でソート sorted_abs_desc = sort(v, lt=my_lt, rev=true) println(sorted_abs_desc) # [-0.8, -0.5, 0.3, 0.2] (絶対値の降順)
イテレータの逆順に関する混乱
Juliaには reverse
関数と Iterators.reverse
関数もあります。これらはソートとは直接関係なく、コレクションの要素の順序を物理的に反転させる(または反転したビューを提供する)ものです。
問題
- 「
reverse
関数とsort(..., rev=true)
を混同している」reverse
は既存の配列や文字列の要素の順序を反転させるものであり、ソートは要素の値に基づいて並べ替えるものです。
トラブルシューティング
-
- 単に配列の要素の並びを物理的に逆順にしたい場合は
reverse(arr)
またはreverse!(arr)
を使用します。 - 要素の値に基づいて、昇順ではなく降順に並べ替えたい場合は
sort(arr, rev=true)
を使用します。
arr = [1, 2, 3, 4] reversed_arr = reverse(arr) println(reversed_arr) # [4, 3, 2, 1] (要素の並びが反転) unsorted_arr = [2, 1, 4, 3] sorted_desc = sort(unsorted_arr, rev=true) println(sorted_desc) # [4, 3, 2, 1] (値に基づいて降順ソート)
- 単に配列の要素の並びを物理的に逆順にしたい場合は
Order.Reverse
自体の問題ではありませんが、ソートに関連してよく発生するエラーです。
問題
MethodError: no method matching isless(...)
ソートしようとしている要素の型が、Juliaのデフォルトの比較関数 (isless
) をサポートしていない場合に発生します。例えば、自作の構造体をそのままソートしようとするとこのエラーが出ることがあります。
トラブルシューティング
- by キーワード引数を使用する
isless
を定義する代わりに、ソートの基準となる値をby
で指定することもできます。これが多くの場合、より簡潔な方法です。struct Point x::Int y::Int end points = [Point(1, 2), Point(3, 1), Point(1, 1)] # x座標の降順でソート sorted_by_x_desc = sort(points, by=p -> p.x, rev=true) println(sorted_by_x_desc) # [Point(3, 1), Point(1, 2), Point(1, 1)]
- isless メソッドを定義する
自作の型をソート可能にするには、その型に対するBase.isless
メソッドを定義する必要があります。struct Point x::Int y::Int end # Point 型の比較方法を定義 # x座標で比較し、xが同じ場合はy座標で比較する function Base.isless(p1::Point, p2::Point) if p1.x != p2.x return p1.x < p2.x else return p1.y < p2.y end end points = [Point(1, 2), Point(3, 1), Point(1, 1)] sorted_points = sort(points) # これでソートできる println(sorted_points) # [Point(1, 1), Point(1, 2), Point(3, 1)] # 逆順も可能 sorted_points_desc = sort(points, rev=true) println(sorted_points_desc) # [Point(3, 1), Point(1, 2), Point(1, 1)]
Order.Reverse
や rev=true
に関連するトラブルは、主に以下の点に注意することで回避できます。
- カスタムのデータ型をソートする場合は、
isless
を定義するか、by
を使用して比較可能な値に変換する。 reverse
とsort
の目的の違いを把握する。rev=true
はその比較ロジックを逆転させるものと理解する。lt
関数は常に昇順の比較ロジックで書く。- ソートの基準(
by
やlt
)を明確にする。
Order.Reverse
は主にソート関数の引数として使用され、データの並び順を降順にするために使われます。最も一般的な方法は rev=true
キーワード引数です。
基本的な配列の逆順ソート (rev=true)
最も単純な例として、数値の配列を降順にソートします。
# 昇順の配列
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# 降順にソート (rev=trueを使用)
sorted_desc = sort(arr, rev=true)
println("元の配列: ", arr)
println("降順ソート: ", sorted_desc)
# sort! を使って元の配列を直接変更する
arr_mutable = [3, 1, 4, 1, 5, 9, 2, 6]
sort!(arr_mutable, rev=true)
println("in-place 降順ソート: ", arr_mutable)
出力
元の配列: [3, 1, 4, 1, 5, 9, 2, 6]
降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
in-place 降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
by キーワード引数との組み合わせ
特定の基準でソートし、その結果を逆順にする例です。ここでは、数値の絶対値でソートし、その結果を降順にします。
numbers = [0.2, -0.5, 0.3, -0.8, 0.1]
# 絶対値の昇順でソート
sorted_abs_asc = sort(numbers, by=abs)
println("絶対値昇順: ", sorted_abs_asc)
# 絶対値の降順でソート
sorted_abs_desc = sort(numbers, by=abs, rev=true)
println("絶対値降順: ", sorted_abs_desc)
出力
絶対値昇順: [0.1, 0.2, 0.3, -0.5, -0.8]
絶対値降順: [-0.8, -0.5, 0.3, 0.2, 0.1]
-0.8
と -0.5
の絶対値はそれぞれ 0.8
と 0.5
であり、絶対値の降順では 0.8
が 0.5
より大きいため、-0.8
が -0.5
より先に並びます。
カスタム構造体のソートと rev=true
ユーザー定義の構造体をソートし、特定のフィールドで逆順ソートを行う例です。
struct Person
name::String
age::Int
end
# Person オブジェクトの配列
people = [
Person("Alice", 30),
Person("Bob", 25),
Person("Charlie", 35),
Person("David", 25)
]
println("元の配列: ", people)
# 年齢の降順でソート
# 年齢が同じ場合は名前の昇順 (デフォルトのislessが適用される)
sorted_by_age_desc = sort(people, by=p -> p.age, rev=true)
println("年齢降順: ", sorted_by_age_desc)
# まず年齢の降順、次に名前の降順でソート
# 複数の基準でソートする場合は、ソートキーのタプルを作成し、`rev=true` を適用する
# ただし、タプルの要素ごとに降順を指定したい場合は工夫が必要
# ここでは、`by` でタプルを返し、そのタプル全体の `isless` が逆転する
sorted_by_age_name_desc = sort(people, by=p -> (p.age, p.name), rev=true)
println("年齢降順、名前降順: ", sorted_by_age_name_desc)
# もし「年齢は降順、名前は昇順」のように、要素ごとに異なる順序を指定したい場合、
# `by` 関数でソートキーを適切に変換する必要があります。
# 例: 年齢を負の値にして昇順ソートする(結果的に降順になる)
sorted_age_desc_name_asc = sort(people, by=p -> (-p.age, p.name))
println("年齢降順、名前昇順: ", sorted_age_desc_name_asc)
出力
元の配列: [Person("Alice", 30), Person("Bob", 25), Person("Charlie", 35), Person("David", 25)]
年齢降順: [Person("Charlie", 35), Person("Alice", 30), Person("Bob", 25), Person("David", 25)]
年齢降順、名前降順: [Person("Charlie", 35), Person("Alice", 30), Person("David", 25), Person("Bob", 25)]
年齢降順、名前昇順: [Person("Charlie", 35), Person("Alice", 30), Person("Bob", 25), Person("David", 25)]
年齢降順、名前昇順
の例では、年齢を負の値にすることで、sort
関数のデフォルトの昇順ソートが年齢に対しては降順に作用します。名前はそのまま昇順で比較されます。年齢降順、名前降順
の例では、(35, "Charlie")
、(30, "Alice")
、(25, "David")
、(25, "Bob")
というタプルが作られ、これがrev=true
により逆順ソートされるため、David
がBob
の前に来ます。年齢降順
の例では、Bob
(25歳) とDavid
(25歳) の順序は、デフォルトの文字列比較 ("Bob"
<"David"
) によりBob
が先に来ます。
sortperm と rev=true
sortperm
はソートされた順序に対応するインデックスの順列を返します。これも rev=true
をサポートします。
data = [30, 10, 20]
# データの値の昇順に並べ替えるためのインデックス
perm_asc = sortperm(data)
println("昇順インデックス: ", perm_asc)
println("ソートされたデータ (昇順): ", data[perm_asc])
# データの値の降順に並べ替えるためのインデックス
perm_desc = sortperm(data, rev=true)
println("降順インデックス: ", perm_desc)
println("ソートされたデータ (降順): ", data[perm_desc])
出力
昇順インデックス: [2, 3, 1]
ソートされたデータ (昇順): [10, 20, 30]
降順インデックス: [1, 3, 2]
ソートされたデータ (降順): [30, 20, 10]
通常は rev=true
で十分ですが、Base.Order
モジュールから ReverseOrdering
を明示的に使うこともできます。これは、より複雑なソートロジックをカプセル化する際に役立つことがあります。
using Base.Order
arr = [3, 1, 4, 1, 5]
# Order.Reverse を明示的に指定
sorted_with_order_reverse = sort(arr, order=ReverseOrdering())
println("Order.Reverse を使った降順ソート: ", sorted_with_order_reverse)
# rev=true と Order.Reverse は同じ結果を返します
println("rev=true を使った降順ソート: ", sort(arr, rev=true))
Order.Reverse を使った降順ソート: [5, 4, 3, 1, 1]
rev=true を使った降順ソート: [5, 4, 3, 1, 1]
比較関数 (lt) をカスタム定義して降順ロジックを直接記述する
sort
関数の lt
(less than) キーワード引数に、独自の比較関数を渡すことで、ソートロジックを完全に制御できます。この関数内で降順の比較ロジックを直接記述します。
- 考え方
通常のlt
関数はa < b
の場合にtrue
を返しますが、降順にしたい場合はa > b
の場合にtrue
を返すようにします。
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# 降順ソートのためのカスタム比較関数
# a が b より「大きい」場合に true を返す
my_desc_lt(a, b) = a > b
sorted_desc_custom_lt = sort(arr, lt=my_desc_lt)
println("カスタムlt関数による降順ソート: ", sorted_desc_custom_lt)
# 出力: カスタムlt関数による降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
利点
- 特定の複雑な条件に基づいて降順ソートを行いたい場合に役立ちます。
- ソートロジックを非常に細かく制御できます。
欠点
- 一般的な降順ソートにはオーバーキルです。
rev=true
に比べてコードが冗長になり、直感性に欠けます。
ソートキーを変換して「負の値」などで降順を表現する
by
キーワード引数を使ってソートキーを生成する際に、数値の場合は負の値に変換することで、デフォルトの昇順ソートが結果的に降順になります。
- 考え方
sort
は常に昇順でソートしようとします。by
で返す値が元の値の負の値であれば、sort
は「より小さな負の値」を先に並べようとし、結果的に元の値は「より大きな値」が先に並ぶことになります。
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# 各要素を負の値に変換し、それを基準にソート(結果的に降順)
sorted_desc_by_neg = sort(arr, by=x -> -x)
println("負の値変換による降順ソート: ", sorted_desc_by_neg)
# 出力: 負の値変換による降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
カスタム構造体の場合の例
複数のソート基準があり、一部を降順、一部を昇順にしたい場合に非常に有用です。
struct Person
name::String
age::Int
end
people = [
Person("Alice", 30),
Person("Bob", 25),
Person("Charlie", 35),
Person("David", 25)
]
# 年齢は降順、名前は昇順でソート
# 年齢を負の値に変換することで、年齢については降順ソートになる
sorted_mixed_order = sort(people, by=p -> (-p.age, p.name))
println("年齢降順・名前昇順: ", sorted_mixed_order)
# 出力: 年齢降順・名前昇順: [Person("Charlie", 35), Person("Alice", 30), Person("Bob", 25), Person("David", 25)]
この例では、(-p.age, p.name)
というタプルをソートキーとしています。Julia のタプル比較は要素ごとに順次行われるため、まず -p.age
で比較され、それが同じ場合に p.name
で比較されます。-p.age
が小さくなるほど p.age
は大きくなるため、年齢は降順、名前は昇順になります。
利点
- 複数のソート基準がある場合に、各基準に対して個別に昇順/降順を制御しやすい。
rev=true
を使わずに降順ソートを実現できる。
欠点
- ソートキー変換のロジックが少々複雑になることがあります。
- 数値データ以外(文字列など)にはこの「負の値」変換は直接適用できません(ただし、カスタムの「順序付け」を定義することは可能です)。
ソート後に reverse 関数を適用する
これは厳密には「ソートの代替方法」ではありませんが、昇順でソートした後に、その結果を物理的に反転させることで、降順の並び順を実現できます。
- 考え方
まずsort
で昇順に並べ替え、その結果をreverse
(またはreverse!
) で逆順にします。
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# 昇順ソート
sorted_asc = sort(arr)
println("昇順ソート: ", sorted_asc)
# ソートされた配列を逆順にする
reversed_sorted = reverse(sorted_asc)
println("ソート後にreverse: ", reversed_sorted)
# 出力:
# 昇順ソート: [1, 1, 2, 3, 4, 5, 6, 9]
# ソート後にreverse: [9, 6, 5, 4, 3, 2, 1, 1]
利点
- 非常にシンプルで理解しやすい。
欠点
- ソートはインプレース (
sort!
) で行われても、reverse
は通常新しい配列を作成します(reverse!
を使えばインプレースも可能)。 - ソートと逆転の2つの操作が必要になるため、効率がわずかに低下する可能性があります(特に大きなデータセットの場合)。
どの方法を選ぶべきか?
- 単にソート後の配列を逆順にしたいだけであれば、**「ソート後に
reverse
関数を適用する」**のも選択肢ですが、一般的にはrev=true
の方が直接的です。 - 非常に特殊な比較ロジックが必要で、
rev=true
では対応できない場合にのみ、**「比較関数 (lt
) をカスタム定義する」**ことを検討します。 - 複数のソート基準があり、それぞれ異なる昇順/降順を指定したい場合は、**「ソートキーを変換して負の値などで降順を表現する」方法(
by=x -> (-x.field1, x.field2)
のような形)**が非常に便利です。 - ほとんどの場合、
sort(..., rev=true)
が最も推奨される方法です。 これは最も明確で効率的であり、Julia のソートAPIの意図された使い方です。
これらの代替方法は、rev=true
が内部的にどのように機能しているか、または特定の状況でより柔軟なソートロジックを構築する必要がある場合に役立ちます。しかし、通常の降順ソートであれば、迷わず rev=true
を使うのがベストプラクティスです。
Juliaにおいて、Order.Reverse
(または rev=true
) を使わずに、ソート順を逆転させるための代替方法をいくつかご紹介します。これらの方法は、特定のシナリオでより柔軟な制御が必要な場合や、ソートのロジックをより明示的に記述したい場合に役立ちます。
lt キーワード引数を使ったカスタム比較関数
sort
関数や sort!
関数は、lt
(less than) キーワード引数を使って、要素間の比較方法をカスタマイズできます。通常、lt(a, b)
は a < b
の場合に true
を返しますが、このロジックを逆にすることで降順ソートを実現できます。
例
数値の降順ソート
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# lt 関数で b < a を比較することで、降順ソートを実現
# (aがbより小さい場合にtrueを返すのが通常のislessだが、
# ここではbがaより小さい場合にtrueを返すようにしているため、結果的に降順になる)
sorted_desc_lt = sort(arr, lt=(a, b) -> b < a)
println("lt を使った降順ソート: ", sorted_desc_lt)
# rev=true と比較
println("rev=true を使った降順ソート: ", sort(arr, rev=true))
出力
lt を使った降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
rev=true を使った降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
利点
rev=true
とは独立して機能するため、より複雑なカスタムソートロジックと組み合わせる際に、ロジックの意図が明確になる場合があります。- ソートロジックを非常に細かく制御できます。
欠点
lt
関数のロジックが直感的でないと、意図しないソート結果になる可能性があります。例えば、lt=(a, b) -> a > b
とすると、期待通りの降順ソートになりますが、lt
が「less than」を意味することを考えると混乱を招くかもしれません。- 多くの場合は
rev=true
の方が簡潔です。
ソートの基準となる値を by
キーワード引数で変換し、その変換された値が逆順になるようにすることで、rev=true
を使わずに降順ソートを実現できます。これは主に数値データに適用できます。
例
数値の降順ソート(値を負の値に変換)
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# 各要素を負の値に変換して昇順ソートする (結果的に元の値は降順になる)
sorted_desc_by_neg = sort(numbers, by=x -> -x)
println("by を使った降順ソート (負の値): ", sorted_desc_by_neg)
# rev=true と比較
println("rev=true を使った降順ソート: ", sort(numbers, rev=true))
出力
by を使った降順ソート (負の値): [9, 6, 5, 4, 3, 2, 1, 1]
rev=true を使った降順ソート: [9, 6, 5, 4, 3, 2, 1, 1]
利点
- 複数のソートキーを組み合わせる場合、一部のキーだけを降順にしたい場合に役立ちます。(例:
by=p -> (-p.age, p.name)
のように、年齢は降順、名前は昇順にしたい場合) lt
を直接操作するよりも、ソートの「基準」が明確になる場合があります。
欠点
- 変換によって元の値の意味が変わるため、デバッグが複雑になる可能性があります。
- 数値データ以外(文字列やカスタム型)には、適切な「負の値に変換する」ようなロジックを見つけるのが難しい場合があります。
これは厳密には「ソート方法の代替」ではありませんが、結果的に降順のデータを得るための方法です。まず昇順にソートし、その結果の配列を逆順に並べ替えます。
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# まず昇順にソート
sorted_asc = sort(arr)
println("昇順ソート: ", sorted_asc)
# その後、結果を逆順にする
reversed_sorted = reverse(sorted_asc)
println("昇順ソート後 reverse: ", reversed_sorted)
# sort!(arr) と reverse!(arr) を組み合わせてインプレースで実現することも可能
arr_mutable = [3, 1, 4, 1, 5, 9, 2, 6]
sort!(arr_mutable)
reverse!(arr_mutable)
println("in-place 昇順ソート後 reverse!: ", arr_mutable)
出力
昇順ソート: [1, 1, 2, 3, 4, 5, 6, 9]
昇順ソート後 reverse: [9, 6, 5, 4, 3, 2, 1, 1]
in-place 昇順ソート後 reverse!: [9, 6, 5, 4, 3, 2, 1, 1]
利点
reverse
関数が直感的で理解しやすいです。- ソートロジックと逆順ロジックを明確に分離できます。
欠点
sortperm
のようにインデックスを返す関数と組み合わせる場合には、この方法は直接適用できません。- 2段階の処理になるため、ソートと同時に逆順にする
sort(..., rev=true)
よりも効率が悪い場合があります(特に大きなデータセットの場合、中間配列の作成と2回目の走査が発生するため)。
Julia で降順ソートを行う最も推奨される方法は、ほとんどのユースケースで sort(..., rev=true)
を使用することです。これは簡潔で、意図が明確であり、効率的です。
しかし、以下のような代替方法も存在し、特定の状況で有用です。
- sort と reverse の組み合わせ
ソートと逆順の操作を個別に表現したい場合や、一時的な中間結果を許容できる場合。 - by キーワード引数と値の変換
数値データで、ソート基準の変換を明示的に行いたい場合や、複数のソートキーの一部だけを逆順にしたい場合。 - lt キーワード引数
ソートの比較ロジックを完全にカスタム制御したい場合。