【Python】ソート順を保ったまま0を含むデータを末尾に移動させる方法

2021年6月7日 2023年11月8日
カテゴリ: プログラミング
Python アルゴリズム

はじめに

日付や数量などの情報を含むデータを複数格納したリストに対して日付順でソートすることがよくありますが、数量が0の場合は他のデータのソート順は保ったまま数量が0のデータだけ末尾に移動させたいことってありますよね。
複数条件でソートすればうまくいきそう!と思って色々こねくり回してもなかなか思った感じにならないんですよね。

結論から言えば一度日付でソートしたデータを、数量が0より大きいかどうかをキーにしてもう一度ソートすればよいだけです。

ソートのキホン

Pythonのリストは同じ種類のデータで構成される場合、簡単にソートできます。
たとえばこんな感じのリストがある場合

list1 = [100, 25, 75, 45, 1]

ソートするにはリストのメソッドsortまたは、組込み関数のsortedを使います。

list1.sort() # sortメソッドは元のリストを直接ソートする
list1 = sorted(list1) # sorted関数は元のリストはそのままで、ソートしたリストを返す

それぞれ昇順でソートされますが、降順でソートしたい場合は次のようにします。

list1.sort(reverse=True)
list1 = sorted(list1, reverse=True)

sortメソッドはリストにしかありませんが、sorted関数はリストだけでなくディクショナリなど任意のイテラブルオブジェクトに使用できます。
※sorted関数の戻り値はリストになります。

リストやディクショナリを含むリストのソート

リストを含むリスト

リストを含むリストをソートする場合、次のようになります。

list2 = [
    [9, 13, 2, 10, 34, 24, 26],
    [9, 8, 25, 16, 30, 3, 18],
    [5, 7, 32, 8, 19, 21, 22],
    [19, 30, 7, 18, 3, 6, 14],
]

list2 = sorted(list2)

"""
ソートしたデータ
list2 = [
    [5, 7, 32, 8, 19, 21, 22],
    [9, 8, 25, 16, 30, 3, 18],
    [9, 13, 2, 10, 34, 24, 26],
    [19, 30, 7, 18, 3, 6, 14],
]
"""

リスト同士の比較は先頭の要素から順に見ていくので上記のような結果になります。
また、各要素が比較可能でない場合はエラーとなります。

list3 = [
    ["9", 13, 2, 10, 34, 24, 26], # 先頭の要素のみ文字列(str)
    [9, 8, 25, 16, 30, 3, 18],
    [5, 7, 32, 8, 19, 21, 22],
    [19, 30, 7, 18, 3, 6, 14],
]

list3 = sorted(list3)

"""
エラーになる
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'str'
"""

各要素が比較可能であればデータの種類が混在していてもソート可能です。

list4 = [
    ["9", 13, 2, 10, 34, 24, 26],
    ["9", 8, 25, 16, 30, 3, 18],
    ["5", 7, 32, 8, 19, 21, 22],
    ["19", 30, 7, 18, 3, 6, 14],
]

list4 = sorted(list4)

"""
ソートしたデータ
list4 = [
    ["19", 30, 7, 18, 3, 6, 14],
    ["5", 7, 32, 8, 19, 21, 22],
    ["9", 8, 25, 16, 30, 3, 18],
    ["9", 13, 2, 10, 34, 24, 26],
]
"""

文字列同士の比較は左から一文字ずつ見ていくので上記のように"19"が"5"よりも小さいと判定されます。

ディクショナリを含むリストのソート

ディクショナリを含むリストの場合、ソートするにはkeyを指定する必要があります。

list5 = [
  {"text": "abc", "value": 1},
  {"text": "python", "value": 10},
  {"text": "cat", "value": 5},
  {"text": "python", "value": 1},
]

list5 = sorted(list5, key=lambda x: x["text"])

"""
ソートしたデータ
list5 = [
  {"text": "abc", "value": 1},
  {"text": "cat", "value": 5},
  {"text": "python", "value": 10},
  {"text": "python", "value": 1},
]
"""

上記ではtextをキーとしましたが、valueをキーとすると次のようになります。

list5 = sorted(list5, key=lambda x: x["value"])

"""
ソートしたデータ
list5 = [
  {"text": "abc", "value": 1},
  {"text": "python", "value": 1},
  {"text": "cat", "value": 5},
  {"text": "python", "value": 10},
]
"""

また、複数のキーを指定することもできます。

list5 = sorted(list5, key=lambda x: (x["text"], x["value"]))

"""
ソートしたデータ
list5 = [
  {"text": "abc", "value": 1},
  {"text": "cat", "value": 5},
  {"text": "python", "value": 1},
  {"text": "python", "value": 10},
]
"""

キーの優先順位は左側の条件から順になるので、キーの並び順が変わると結果も変わってきます。

ソート順を保ったまま0以下のデータを含むものを末尾に移動させる

やっと本題です。
こんな感じのデータがあるとします。

from datetime import date
data = [
    {"name": "data1", "date" : date(2021,6,1), "stock" : 5},
    {"name": "data2", "date" : date(2021,6,5), "stock" : 4},
    {"name": "data3", "date" : date(2021,6,10), "stock" : 0},
    {"name": "data4", "date" : date(2021,6,10), "stock" : 2},
    {"name": "data5", "date" : date(2021,6,3), "stock" : -2},
]

これを日付で降順ソートする場合、こうします。

data = sorted(data, key=lambda x:x["date"], reverse=True)

"""
ソートしたデータ
data = [
    {"name": "data3", "date" : date(2021,6,10), "stock" : 0},
    {"name": "data4", "date" : date(2021,6,10), "stock" : 2},
    {"name": "data2", "date" : date(2021,6,5), "stock" : 4},
    {"name": "data5", "date" : date(2021,6,3), "stock" : -2},
    {"name": "data1", "date" : date(2021,6,1), "stock" : 5},
]
"""

日付でソートした順序を保ったまま、0以下のデータを末尾に移動させたい場合は次のようにします。

data = sorted(
    sorted(data, key=lambda x:x["date"], reverse=True), # 日付で降順ソート
    key=lambda x:x["stock"]>0, reverse=True
)

"""
ソートしたデータ
data = [
    {"name": "data4", "date" : date(2021,6,10), "stock" : 2},
    {"name": "data2", "date" : date(2021,6,5), "stock" : 4},
    {"name": "data1", "date" : date(2021,6,1), "stock" : 5},
    {"name": "data3", "date" : date(2021,6,10), "stock" : 0},
    {"name": "data5", "date" : date(2021,6,3), "stock" : -2},
]
"""

日付の並び順を保ったまま数量が0以下のものが末尾に移動したのがわかるでしょうか。

ここでキモとなるのは key=lambda x:x["stock"]>0 とした部分です。
key=lambda x:x["stock"] としてしまうと数量順で並び変わってしまい、日付の順はバラバラになってしまいます。
そこで、 >0 とすることで0より大きい場合はTrue(1)、0以下の場合はFalse(0)となるため日付の順序を崩さずに0以下のものを末尾に移動させることができるのです。

関連の記事

OpenPyXl でチェックボックスを押したい

【Django】CSS や JavaScript の変更が反映されないときの対処法

Sphinxで作ったドキュメントの外部リンクを新しいタブで開く方法を考える

「GAKKOU」 問題を Python で解いてみよう