Python Tech 辞書

Pythonで2次元リスト、辞書のリストをキーでソートする方法の完全解説

どのプログラミング言語でもリストの中のリスト(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]

 

逆順ソートは引数reverseTrueにします。

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は比較的取り組みやすい言語と言われていますが、プログラミング初心者にとっては分からないことだらけ。

ゼロから独学で勉強するのは厳しい道のりです。

今回、様々な現場、システム、言語を経験してきた現役エンジニアの立場から、初心者でも挫折しない学習方法を解説する記事を書きました。もちろん、お金をかけずに習得できる方法も解説しています。

できるだけストレスがかからない勉強法を解説しているので、ぜひ参考にしてみてくださいね。

 

今回参考にしたページ・資料

組み込み関数 — Python 3.9.4 ドキュメント

 

  • この記事を書いた人

次世代ペンギン

長いのでペンギンとお呼びください。システム開発・プログラミングのお仕事をしています。甘味とコーヒーは生命線。多くの人に役立つ情報のシェアが目標です。

人気の記事

1

会社員でプログラマーとして働いている人、インフラやネットワークのエンジニアとして働いている人の中には、フリーランスのプログラマーとして独立、もしくは転向したい人もいるので ...

2

キャリアアップのため、または高収入を目指して、しっかりプログラミングを学びたいという人が増えてきましたね。 この記事では現役のエンジニアである私が、実際に仕事で稼げるよう ...

3

フリーランスのプログラマーにとって収入の向上に最も直結するのはスキルです。 必要なスキル、スキルの獲得方法が気になる人も多いでしょう。 また、これからフリーランスを目指す ...

4

Vuetifyの v-progress-circular コンポーネントは、数値データや処理状況を環状(円状)のデザインで教えてくれるUIデザインです。 ローディングのス ...

5

Vuexのstore(ストア)を使うと、各コンポーネント間で個別にデータのやり取りすることなく、データを一元的に管理できます。Vueでは欠かせない機能といえるでしょう。 ...

-Python, Tech, 辞書
-,