【Python】配列のさまざまなソート

:::note info
使用Version：Python 3.11.2
:::

:::note info
公式ドキュメント：ソートのテクニック
https://docs.python.org/ja/3/howto/sorting.html
:::

# 概要
下記配列に対して様々なソートを実行する
```Python:testsheetリスト
testsheet = [
   ['Matt',   23, 80 ],
   ['Zill',   55, 46 ],
   ['May',    70, 90 ],
   ['May',    70, 80 ],
   ['Tyler',  55, 70 ],
   ['Tomothy',55, 70 ],
   ['太郎',    55, 70 ],
   ['はるか',   40, 70 ],
   ['$imon',   40, 70 ],
   ['ωatson',   40, 70 ],
   ['Tomothy',43, 80 ]
]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/dcb8173a-a212-fe33-0187-7d13ff0ee343.png)

:::note info
リストの出力例
```Python
for e in result: # resultはソート後のリストを指す
    print(*e, sep=',')
```
:::

# Lv1 sorted関数
<b>`sorted()`または`sort()`を使う</b> ことで多重キーでのソートが可能
```Python
result = sorted(testsheet)
```
- 1番目の要素、2番目の要素、3番目の要素、（略）、の順で優先的に昇順ソートする
- 文字列はUnicodeコードに基づいてソートされる
  - [Unicode一覧 0000-0FFF - Wikipedia](https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF)
- `sorted()`
  - 組み込み関数
  - 「ソートした新たなリスト」を生成する
- `sort()`
  - リスト型のメソッド
  - 「元のリスト」をソートする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/2d65baf8-0ecc-5f01-a0df-855d4c479ecb.png)

:::note info
文字のUnicodeコードを確認する際はord関数を用いる
```Python
for e in result:
    print('0x%04X'%(ord(e[0][0])))
```
:::
:::note info
下記でも同様のソート処理が可能（詳細後述）
```Python
result = sorted(testsheet, key=lambda x:(x[0],x[1],x[2]), reverse=False)
```
:::

# Lv2 降順ソート
<b>reverseパラメータを指定する</b> ことで昇順または降順の指定が可能
```Python
# 全要素を降順でソートする
result = sorted(testsheet, reverse=True)
```
下記のように全ての要素に対し降順でソートできる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/4c34f625-3c9e-583d-1e9e-2234f0be6562.png)

:::note info
下記でも同様のソート処理が可能（詳細後述）
```Python
result = sorted(testsheet, key=lambda x:(x[0],x[1],x[2]), reverse=True)
result = sorted(testsheet, key=lambda x:(-x[0],-x[1],-x[2]))
```
:::

# Lv3 ソートキーの指定
<b>`key` パラメータに関数を指定する</b> ことでソートキーを定義できる

たとえば以下のようにソートする場合を考える
- "<font color="bf3030">名前</font>"、"<font color="30bf30">国語</font>"をキーに昇順ソートする
- "数学"をキーにはしない

この条件について以下のようにラムダ式で記述できる
```Python3
result = sorted(testsheet, key=lambda x:(x[0],x[1]))
```
実行例は下記の通り（Lv1とはMayさん行の並びが異なる）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/e7332b0b-b0fc-9e5f-152e-d41e69028f52.png)

:::note info
Mayさん行の順番については[公式ドキュメント](https://docs.python.org/ja/3/howto/sorting.html#sort-stability-and-complex-sorts)においても言及されている
> ソートは、 安定 (stable) であることが保証されています。これはレコードの中に同じキーがある場合、元々の順序が維持されるということを意味します。
:::

# Lv4 昇順と降順の併用
`key` パラメータにてマイナスを付けることで降順ソートが可能
たとえば「国語を昇順、数学を降順に並べたい」場合には下記のように定義する
```Python
result = sorted(testsheet, key=lambda x:(x[1],-x[2]))
```
国語の点数が同点になったら数学の点数で降順に並べられていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/76620aac-6dfb-5b3a-4a94-47702b4df0bb.png)

# Lv5 任意の優先度
単純な昇順降順にとどまらず独自の優先度を持ち込むことも可能
<b>`key` パラメータに別途関数を指定する</b> ことで「<font color="bf3030">■</font>カスタムソート、<font color="30bf30">■</font>数学で降順ソート、<font color="3030bf">■</font>名前で昇順ソート」のような処理を実現できる
```Python
# 贔屓生徒リスト
favsheet = ["はるか","May","Zill"]

# 贔屓レベルを出力する関数。値が低いほど贔屓度が高いものとする
#（はるか(0) > May(1) > Zill(2) > その他大勢(3) の順に贔屓する）
def get_fav_index(x):
    for i, name in enumerate(favsheet):
        if name == x[0]:
            return i
    return len(favsheet)
'''
別の書き方
get_fav_index = lambda x:\
    [i if name == x[0] else len(favsheet) for i, name in enumerate(favsheet)]
'''

# 贔屓ソート、数学で降順ソート、名前で昇順ソート
result = sorted(testsheet, key=lambda x:(get_fav_index(x),-x[2],x[0]))
```

これにより贔屓された3名（4名）が表の上に来る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194558/abbc6ec5-0839-a901-527a-d88e380d3acb.png)

:::note info
独自の優先度とはいえ結局は数値を返すような関数を利用することになる
そのため最終的には昇順または降順でのソートに帰結する
:::

## 参考文献
- [多重キーでのソート](https://pashango-p.hatenadiary.org/entry/20090614/1244984058)
- [Pythonでリストをソートするsortとsortedの違い](https://note.nkmk.me/python-list-sort-sorted/)
- [ソート HOW TO](https://docs.python.org/ja/3/howto/sorting.html)
- [Pythonのソートのkeyパラメータについて](https://selfs-ryo.com/detail/key_of_python_sort)