どのプログラミング言語でもリストの中のリスト(2次元リスト、2次元配列)やリストの中に辞書(連想配列のリスト)のような構造はよく出現します。
Pythonでは、JSONファイルをパースしたら2次元リストや辞書のリストのような形をよく見かけますよね。
今回は次のようなリストをソートする方法を解説しています。
- リストのリスト(2次元配列)で、ある位置の要素をキーとしてソート
- 辞書のリストで、辞書のキーでリストをソート
- 辞書のリストでキーが存在しないレコードを最後尾に
- 辞書のリストで複数の要素を組み合わせてソート
リストをソートするのではなく、辞書のキー・値をソートして取得する方法は別のページで解説しています。
結論
普通にリストをソートするコード
l_org = [2, 1, 4, 3]
l_new = sorted(l_org)
print(l_new)
# [1, 2, 3, 4]
リストのリストを特定の要素でソートする
引数keyにソート条件としたい要素を指定する
l_org = [[2, 0, 0], [1, 0, 0], [4, 0, 0], [3, 0, 0]]
l_new = sorted(l_org, key=lambda x: x[0])
print(l_new)
# [[1, 0, 0], [2, 0, 0], [3, 0, 0], [4, 0, 0]]
辞書のリストを特定のキーでソートする
引数keyにソート条件とする要素を指定する
l_org = [
{'id': 1, 'name': 'Albert', 'score': 60},
{'id': 2, 'name': 'Lexie', 'score': 55},
{'id': 3, 'name': 'Chang', 'score': 70},
{'id': 4, 'name': 'Deanna', 'score': 49},
]
l_new = sorted(l_org, key=lambda x: x['score'])
pprint(l_new)
# [{'id': 4, 'name': 'Deanna', 'score': 49},
# {'id': 2, 'name': 'Lexie', 'score': 55},
# {'id': 1, 'name': 'Albert', 'score': 60},
# {'id': 3, 'name': 'Chang', 'score': 70}]
実行環境
Python 3.9.2
Python ソート仕様のおさらい
Python で配列(リスト)、タプルなどのシーケンス変数をソートするには sort()
またはsorted()
関数を使います。
sorted()
関数ではソートされた変数が返されます。
l_org = [2, 1, 4, 3]
l_new = sorted(l_org)
print(l_new)
# [1, 2, 3, 4]
一方、sort()
関数は渡した変数自体がソートされます。
つまり引数として渡した変数自体が書き換えられる破壊的なソートです。sort()
の返り値はNone
なので注意が必要。
l_org = [2, 1, 4, 3]
l_new = l_org.copy()
l_new.sort()
print(l_new)
# [1, 2, 3, 4]
逆順ソートは引数reverse
をTrue
にします。
l_new = sorted(l_org, reverse=True)
print(l_new)
# [4, 3, 2, 1]
数値のほか、アルファベット・日本語などの文字列もソートできます。
l_org = ['bravo', 'alpha', 'delta', 'charlie']
l_new = sorted(l_org)
print(l_new)
# ['alpha', 'bravo', 'charlie', 'delta']
リストのリストを特定の要素でソートする
listの変数にlistが含まれる2次元リストを、特定の要素でソートしたいときはどうすればいいでしょうか。
これにはsorted()
またはsort()
関数の引数key
を使用します。
引数key
には、ソートのキーとする要素に対して何らかの処理を行う関数(呼び出し可能オブジェクト)を指定します。
例えば、次のような2次元リストがあるとします。
l_org = [
[2, 0, 0],
[1, 0, 0],
[4, 0, 0],
[3, 0, 0]
]
各リストの第1要素(0番目)の数値で昇順ソートしたいときは、以下のようにkey
引数を指定します。ここではラムダ式(無名関数)で各リストの0番目の要素をキーとして指定しています。もちろん、def
で定義した関数でも問題ありません。
from pprint import pprint
l_org = [
[2, 0, 0],
[1, 0, 0],
[4, 0, 0],
[3, 0, 0]
]
l_new = sorted(l_org, key=lambda x: x[0])
pprint(l_new, width=20)
# [[1, 0, 0],
# [2, 0, 0],
# [3, 0, 0],
# [4, 0, 0]]
※pprint()
は多次元リストや辞書をみやすく表示する組み込み関数です。
補足
簡単に説明すると、引数key
に渡した関数が返す値(return)に基づいてソートが実行されます。
各リストの第1要素で昇順ソートされているのが分かりますね。
逆順(降順)にしたい場合は引数reverse=True
を渡します。
l_new = sorted(l_org, key=lambda x: x[0], reverse=True)
pprint(l_new, width=20)
# [[4, 0, 0],
# [3, 0, 0],
# [2, 0, 0],
# [1, 0, 0]]
辞書のリストを特定のキーでソートする
次のような辞書のリストがあります。
l_org = [
{'id': 1, 'name': 'Albert', 'score': 60},
{'id': 2, 'name': 'Lexie', 'score': 55},
{'id': 3, 'name': 'Chang', 'score': 70},
{'id': 4, 'name': 'Deanna', 'score': 49},
]
キー'score'
でソートしたいとき、どうすればよいでしょうか?
普通にsorted()
でソートしようとするとエラーになります。これだとdictオブジェクト自体を比較しようとしてしまうためです。
sorted(l_org)
# TypeError: '<' not supported between instances of 'dict' and 'dict'
多次元リストの場合と同じように、引数key
に関数を指定してソートしましょう。
l_new = sorted(l_org, key=lambda x: x['score'])
pprint(l_new)
# [{'id': 4, 'name': 'Deanna', 'score': 49},
# {'id': 2, 'name': 'Lexie', 'score': 55},
# {'id': 1, 'name': 'Albert', 'score': 60},
# {'id': 3, 'name': 'Chang', 'score': 70}]
sort()
を使う場合も同じようにできます。
l_new = l_org.copy()
l_new.sort(key=lambda x: x['score'])
pprint(l_new)
# [{'id': 4, 'name': 'Deanna', 'score': 49},
# {'id': 2, 'name': 'Lexie', 'score': 55},
# {'id': 1, 'name': 'Albert', 'score': 60},
# {'id': 3, 'name': 'Chang', 'score': 70}]
逆順にする場合
l_new = sorted(l_org, key=lambda x: x['score'], reverse=True)
pprint(l_new)
# [{'id': 3, 'name': 'Chang', 'score': 70},
# {'id': 1, 'name': 'Albert', 'score': 60},
# {'id': 2, 'name': 'Lexie', 'score': 55},
# {'id': 4, 'name': 'Deanna', 'score': 49}]
要素がないレコードに対応する
APIのレスポンスでは、要素がない(null)場合にキー自体を返さないことがよくあります。こうした、要素の欠落があるレコードにも対処しなければならないケースもあるでしょう。
'score'
が欠落したデータを取得し、JSONをパースしたら、レコードの構造が以下のようになったとします。
l_part = [
{'id': 1, 'name': 'Albert', 'score': 60},
{'id': 2, 'name': 'Lexie'},
{'id': 3, 'name': 'Chang'},
{'id': 4, 'name': 'Deanna', 'score': 49},
]
このような場合に ラムダ式で'score'
をキーに指定しても、'score'
の要素がない辞書があるのでKeyError
となります。
sorted(l_part, key=lambda x: x['score'], reverse=True)
# KeyError: 'score'
そこで、辞書のget()
関数を活用します。
get()
はキーの値を返しますが、そのキーが存在しない場合は引数であらかじめデフォルト値として指定した値を返します。
d_sample = {'a': 1, 'b': 2}
print(d_sample.get('c', -1))
# -1
上のラムダ式をget()
を使って書き換えると、
l_sorted = sorted(l_part, key=lambda x: x.get('score', -1))
pprint(l_sorted)
# [{'id': 2, 'name': 'Lexie'},
# {'id': 3, 'name': 'Chang'},
# {'id': 4, 'name': 'Deanna', 'score': 49},
# {'id': 1, 'name': 'Albert', 'score': 60}]
'score'
キーがないレコードは'score'
が-1とみなされているので一番先頭に並びます。
もし、適切な数値がない場合は、get()
のデフォルト値を無限大や無限小に指定すればよいでしょう。無限大はmath.inf
などで表現できます。
import math
l_sorted = sorted(l_part, key=lambda x: x.get('score', math.inf))
pprint(l_sorted)
# [{'id': 4, 'name': 'Deanna', 'score': 49},
# {'id': 1, 'name': 'Albert', 'score': 60},
# {'id': 2, 'name': 'Lexie'},
# {'id': 3, 'name': 'Chang'}]
複数のキー・要素の組み合わせでソートする
同じキーを持ちまったく同じ値が入っているようなとき(非ユニークなカラム)でも何らかの基準でソートしたいときがあります。
複数のキーを組み合わせてソートする場合は、上と同じように引数key
を活用します。
key
には任意の関数を指定できるので、各要素を組み合わせたり計算したりしてソートのキーとすることもできます。
ここでは、'name'
列と'score'
列を組み合わせた値をもとにソートするサンプルを示します。
l_part = [
{'id': 1, 'name': 'Albert', 'score': 60},
{'id': 2, 'name': 'Helix', 'score': 70},
{'id': 3, 'name': 'Albert', 'score': 40},
{'id': 4, 'name': 'Nuk', 'score': 50},
]
def join_values(elem):
return elem['name'] + str(elem['score'])
l_sorted = sorted(l_part, key=join_values)
pprint(l_sorted)
# [{'id': 3, 'name': 'Albert', 'score': 40},
# {'id': 1, 'name': 'Albert', 'score': 60},
# {'id': 2, 'name': 'Helix', 'score': 70},
# {'id': 4, 'name': 'Nuk', 'score': 50}]
key
に指定しているjoin_values()
を変更すれば、任意のキー・値を使った処理に書き換えることができます。
Pythonの学習法について
Python の勉強が辛くなっていませんか?
Pythonは比較的取り組みやすい言語と言われていますが、プログラミング初心者にとっては分からないことだらけ。
ゼロから独学で勉強するのは厳しい道のりです。
今回、様々な現場、システム、言語を経験してきた現役エンジニアの立場から、初心者でも挫折しない学習方法を解説する記事を書きました。もちろん、お金をかけずに習得できる方法も解説しています。
できるだけストレスがかからない勉強法を解説しているので、ぜひ参考にしてみてくださいね。
今回参考にしたページ・資料