1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Python]複数条件でソート

Last updated at Posted at 2023-02-13

まえがき

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

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

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式を渡しています。
キーをかっこで囲み、複数与えることで複数条件でも実装できますね。めっちゃ便利じゃん。

前提

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

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

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

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

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

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

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

実装

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

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

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

# 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

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

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

# 名前比較(昇順)
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

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

# 年齢比較(降順)
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

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

# 名前比較(昇順)
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を使わなかった場合は以下のようになります。

# 名前比較(昇順)
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

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

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

出力

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

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

まず、上の二行に注目。

Adam 19
Anderson 21

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

そして、4-5行目にも注目。

Mike 53
Mike 52

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

おわりに

マサカリ、お待ちしています。

1
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?