[Python]複数条件でソート

# まえがき
競プロで使うけど、イマイチよくわかってなかったので。
よりよい実装はたくさん転がってる気がしますが。

`2023/02/13`付記
コメントにて「sortedでできる」とありました。
こっちのほうが便利そうです。

```py
persons = [
    ["Eve", 19],
    ["Adam", 19],
    ["Mike", 52],
    ["Anderson", 21],
    ["Mike", 53]
]

for person in sorted(persons, key=lambda person: (person[0], -person[1])):
    print(*person)
```

`sorted`関数の`key`にlambda式を渡しています。
キーをかっこで囲み、複数与えることで複数条件でも実装できますね。めっちゃ便利じゃん。



# 前提
以下のような入力データを想定します。

```py
persons = [
    ["Eve", 19],
    ["Adam", 19],
    ["Mike", 52],
    ["Anderson", 21],
    ["Mike", 53]
]
```

ソート条件は以下の通り。上から順に適用します。

> * 名前を**辞書順**に昇順ソート
> * 年齢を降順ソート

辞書順ソートについては以下の通りとします。（要は辞書に並んでいる順です）

> 頭文字から順番に 「A」 から 「Z」 又は 「あ」 から「ん」 までを考え、若い文字(最初の文字)から順番に文字列を作る方法

https://math-souko.jp/%e8%be%9e%e6%9b%b8%e5%bc%8f%e9%a0%86%e5%88%97%e3%81%a8%e3%81%af%e3%80%80%e8%80%83%e3%81%88%e6%96%b9%e3%81%a8%e8%a7%a3%e6%b3%95/

今回は**バブルソート**を軸に実装していきます。
ここでは詳しく扱いませんが、参考になりそうなページを付記しておきます。

https://algo-method.com/tasks/439

https://www.sortvisualizer.com/bubblesort/

# 実装
完成品を先に置いておきます。

```py
def my_sort(l):
    n = len(l)
    for _ in range(n):
        flag = False
        for i in range(n-1):
            
            # 名前比較（昇順）
            if l[i][0] > l[i+1][0]:
                # 交換
                w = l[i]
                l[i] = l[i+1]
                l[i+1] = w
                flag = True
                continue
            
            elif l[i][0] < l[i+1][0]:
                continue
            
            # 年齢比較（降順）
            if l[i][1] < l[i+1][1]:
                # 交換
                w = l[i]
                l[i] = l[i+1]
                l[i+1] = w
                flag = True
                
        if not flag: break
                
    return l
```

順を追って説明していきます。まずは標準的なバブルソートから。

```py
# n回までで打ち切り（無限ループ回避）
for _ in range(n):
    flag = False
    for i in range(n-1):
        # ↓↓↓条件↓↓↓
        if l[i] > l[i+1]:
            # 交換
            w = l[i]
            l[i] = l[i+1]
            l[i+1] = w

            # 次のループを予約
            flag = True
            
    # フラグのチェック
    if not flag: break
```

２つ目のfor文以降が条件文になっているのがわかります。
ここに適宜条件を実装していく形となります。

まずは名前比較。
Pythonには標準で文字列比較が用意されています（ありがたい）

```py
# 名前比較（昇順）
if l[i][0] > l[i+1][0]:
    # 交換
    w = l[i]
    l[i] = l[i+1]
    l[i+1] = w

    # フラグ更新
    flag = True
    continue

elif l[i][0] < l[i+1][0]:
    continue
```

この場合、辞書順で昇順になる単語がより小さいということになります（たとえば`Adam` < `Eve`）

次に、年齢の降順で並べ替えます。
同名が二人以上いる場合、年齢がより大きいほうが上ということになります。

```py
# 年齢比較（降順）
if l[i][1] < l[i+1][1]:
    # 交換
    w = l[i]
    l[i] = l[i+1]
    l[i+1] = w
    flag = True
```

最後に、フラグチェックのための一文を追加しておきます。

```py
if not flag: break
```

さて、条件部分を見返してみます。

```py
# 名前比較（昇順）
if l[i][0] > l[i+1][0]:
    # 交換
    w = l[i]
    l[i] = l[i+1]
    l[i+1] = w
    flag = True
    continue
elif l[i][0] < l[i+1][0]:
    continue

# 年齢比較（降順）
if l[i][1] < l[i+1][1]:
    # 交換
    w = l[i]
    l[i] = l[i+1]
    l[i+1] = w
    flag = True
```

名前比較の部分で`continue`を使って早めに返しているのに気づきましたか？
`continue`を使わなかった場合は以下のようになります。

```py
# 名前比較（昇順）
if l[i][0] > l[i+1][0]:
    # 交換
    w = l[i]
    l[i] = l[i+1]
    l[i+1] = w
    flag = True

# 年齢比較（降順）
if l[i][0] == l[i+1][0] and l[i][1] < l[i+1][1]:
    # 交換
    w = l[i]
    l[i] = l[i+1]
    l[i+1] = w
    flag = True
```

行数が減った代わりに、ふたつめの条件式が煩雑になりました。

条件がふたつであればこれだけで済むので好みによりますが、三つ以降は条件式が膨れ上がる原因になるので、早めに返す癖をつけたほうがよさそうだな～と思います。

# 出力
さて、最初のデータを実際にソートしてみます。

```console:出力
$ /bin/python3 test.py
Adam 19
Anderson 21
Eve 19
Mike 53
Mike 52
```

まず、上の二行に注目。
```
Adam 19
Anderson 21
```

頭文字が同じ名前については二文字目以降が勘案されているのが確認できます。

そして、4-5行目にも注目。
```
Mike 53
Mike 52
```

同名の二人についても、年齢の降順でソートされています。

# おわりに
マサカリ、お待ちしています。