python 段階的なソート

段階的なソートについて説明します。
段階的なソートとは、例えば3行3列の2次元リストがある時に、2列目を基準にソートした後に3列目を基準にソートすることです。

sortedを1回使用する場合と複数回使用する場合がありますが、 sortedを1回使用する場合の方がおすすめです。考え方が直感的であり、コードも短いためです。但し、任意の列を降順にソートする場合は、sortedを複数回使用する必要があります。

段階的なソート

下記、リストを血液型→体重の優先度でソートすることを考えます。優先度が高い列の値が辞書順で先か、小さいほどソート後に上の行になります 。
本章では、リストの列を基準として昇順にソート行うことを前提に説明をします。つまり、ソートすると辞書順で先である文字列、小さい数値ほど上の行になります。

human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]

sortedを1回使用する場合

itemgetterを使用

下記により、段階的なソートができます。

sorted(ソート対象のリスト, key=itemgetter(ソートの基準とする列1, ソートの基準とする列 2、…))

優先度が高い列を左から順に、itemgetterの引数として与えてください。
血液型→体重の優先度でソートするため、下記コードでは itemgetter(血液型の列、体重の列)としてソートしています。

from operator import itemgetter

human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]

#正しい例:
s1 = sorted(human, key=itemgetter(1, 2))  
print(*s1, sep="\n")
"""出力
('masasi', 'A', 70)
('tadasi', 'O', 50)
('kiyosi', 'O', 100)
"""

#誤った例:
s2 = sorted(human, key=itemgetter(2, 1)) 
print(*s2, sep="\n")
"""出力
('tadasi', 'O', 50)
('masasi', 'A', 70)
('kiyosi', 'O', 100)
"""

ラムダ式を使用

ラムダ式を使用した方法を示します。

human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]

#正しい例:
s1 = sorted(human, key=lambda x:(x[1], x[2]))
print(*s1, sep="\n")
"""出力
('masasi', 'A', 70)
('tadasi', 'O', 50)
('kiyosi', 'O', 100)
"""

sortedを複数回使用する場合

itemgetterを使用

下記により、段階的なソートができます。

リスト① = sorted(ソート対象のリスト, key=itemgetter(ソートの基準とする列1))

リスト② = sorted(ソート対象のリスト(リスト①), key=itemgetter(ソートの基準とする列 2))

優先度が低い列から順にソートしてください。そうしないと、最終結果が望んだ優先度順でソートされたものになりません。
血液型→体重の優先度でソートしたい場合は、体重を基準としてソートした後に、血液型を基準にソートする必要があります。

from operator import itemgetter

human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]

#正しい例:体重でソート → 血液型でソート
s1 = sorted(human, key=itemgetter(2))
print(*s1, sep="\n")
"""出力
('tadasi', 'O', 50)
('masasi', 'A', 70)
('kiyosi', 'O', 100)
"""
s1 = sorted(s1, key=itemgetter(1)) 
print(*s1, sep="\n")
"""出力
('masasi', 'A', 70)
('tadasi', 'O', 50)
('kiyosi', 'O', 100)
"""

#誤った例:血液型でソート → 体重でソート
s2 = sorted(human, key=itemgetter(1)) 
print(*s2, sep="\n")
"""出力
('masasi', 'A', 70)
('tadasi', 'O', 50)
('kiyosi', 'O', 100)
"""
s2 = sorted(s2, key=itemgetter(2))
print(*s2, sep="\n")
"""出力
('tadasi', 'O', 50)
('masasi', 'A', 70)
('kiyosi', 'O', 100)
"""

ラムダ式を使用

ラムダ式を使用した方法を示します。

human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]

#正しい例:体重でソート → 血液型でソート
s1 = sorted(human, key=lambda x: x[2])
print(*s1, sep="\n")
"""出力
('tadasi', 'O', 50)
('masasi', 'A', 70)
('kiyosi', 'O', 100)
"""
s1 = sorted(s1, key=lambda x: x[1]) 
print(*s1, sep="\n")
"""出力
('masasi', 'A', 70)
('tadasi', 'O', 50)
('kiyosi', 'O', 100)
"""

降順にソート

2次元リストの任意の列を降順にソートすることを考えます。
「sortedを1回使用する方法」でreverse=Trueとして降順にソートする場合、2次元リストの全ての列が降順にソートされてしまいます。
そのため、2次元リストの任意の列のみを降順にソートする場合は、「sortedを複数回使用する方法」でソートする必要があります。

下記コード( 血液型→体重の優先度でソート)から、 「sortedを1回使用する方法」でreverse=Trueとして降順にソートする場合、2次元リストの全ての列が降順にソートされることがわかります。

from operator import itemgetter
human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]

s1 = sorted(human, key=itemgetter(1, 2), reverse=True)  
print(*s1, sep="\n")
"""出力
('kiyosi', 'O', 100)
('tadasi', 'O', 50)
('masasi', 'A', 70)
"""

下記コード ( 血液型→体重の優先度でソート) では、 2次元リストの任意の列のみを降順にソートしています。

from operator import itemgetter
human = [
    #名前    ,    血液型, 体重
    ('tadasi',   'O',     50  ),
    ('kiyosi',   'O',     100 ),
    ('masasi',   'A',     70  ),
]
#体重のみ降順にソート
s1 = sorted(human, key=itemgetter(2), reverse=True)
print(*s1, sep="\n")
"""出力
('kiyosi', 'O', 100)
('tadasi', 'O', 50)
('masasi', 'A', 70)
"""
s1 = sorted(s1, key=itemgetter(1)) 
print(*s1, sep="\n")
"""出力
('masasi', 'A', 70)
('kiyosi', 'O', 100)
('tadasi', 'O', 50)
"""

#血液型のみ降順にソート
s1 = sorted(human, key=itemgetter(2))
print(*s1, sep="\n")
"""出力
('tadasi', 'O', 50)
('masasi', 'A', 70)
('kiyosi', 'O', 100)
"""
s1 = sorted(s1, key=itemgetter(1), reverse=True) 
print(*s1, sep="\n")
"""出力
('tadasi', 'O', 50)
('kiyosi', 'O', 100)
('masasi', 'A', 70)
"""

参考

ソート HOW TO — Python 3.9.4 ドキュメント
ソートの安定性と複合的なソート

Complex sort with multiple parameters?
ラムダ式を使用した方法

コメント

タイトルとURLをコピーしました