Python Tech

[Python] listのlist (またはdictのlist) のkeyでソートする方法まとめ

2021年9月15日

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

 

一方、list.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引数を指定します。引数keyには関数などの呼び出し可能オブジェクトを指定できます。

defで定義した関数でもよいのですが、ここではラムダ式(無名関数)で各リストの0番目の要素をキーとして指定。

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は多次元リストや辞書をみやすく表示する組み込み関数です。

逆順の場合は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 3.9.4 ドキュメント

 

  • この記事を書いた人

次世代ペンギン

長いのでペンギンとお呼びください。システム開発・プログラミングのお仕事をしています。甘味とコーヒーは生命線。日常での学びを記事にしています。

人気の記事

1

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

2

先日の記事では、初心者からフリーランスプログラマーになる難しさと、それでもなんとか稼げるようになるにはどうしたらいいか解説しました。 今回の記事では、稼げるフリーランサーになるために必要な要素を、もう ...

3

気休めだけの甘い言葉は書きません。 最近は多くの企業やサイトで、使い捨てられるプログラマーが欲しいがために、甘い言葉で初心者プログラマを誘い出しています。 この記事では、まずは「現実」をちゃんと理解し ...

4

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

5

※画像はずとまよの新曲とは一切関係ありません   ずっと真夜中でいいのに。(以下、ずとまよ)の新曲『勘ぐれい』のMVが12月1日に公開されましたね。 MV前に公開されていた原曲を聴き、神曲な ...

6

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

-Python, Tech
-,

© 2021 ペンギンのーと