📘
超小ネタ Python 昇順、降順のソートをシンプルに実装したい
Pythonで辞書型の値が配列にまとまっているときに、ライブラリを使わずソートをする方法をまとめました。
文字列が含まれているパターンのソートに少し悩んだので備忘として残しておきます。
テストデータ
import pprint
test_data = [
{
'order_date': '2024年7月1日',
'order_number': 'A00001',
'item_name': 'ふわふわパン3号',
'item_quantity': 1
},
{
'order_date': '2024年7月1日',
'order_number': 'A00002',
'item_name': 'ふわふわパン1号',
'item_quantity': 3
},
{
'order_date': '2024年7月1日',
'order_number': 'A00003',
'item_name': 'ふわふわパン2号',
'item_quantity': 2
},
{
'order_date': '2024年7月2日',
'order_number': 'A00004',
'item_name': 'ふんわりマフィン5号',
'item_quantity': 5
},
{
'order_date': '2024年7月2日',
'order_number': 'A00005',
'item_name': 'ふんわりマフィン4号',
'item_quantity': 4
}
]
基本的なソート方法
基本的なソート方法
文字列の項目だけで単純にソート
昇順
sorted_data = sorted(test_data, key=lambda x: x['item_name'])
pprint.pprint(sorted_data)
item_nameの昇順
[{'item_name': 'ふわふわパン1号',
'item_quantity': 3,
'order_date': '2024年7月1日',
'order_number': 'A00002'},
{'item_name': 'ふわふわパン2号',
'item_quantity': 2,
'order_date': '2024年7月1日',
'order_number': 'A00003'},
{'item_name': 'ふわふわパン3号',
'item_quantity': 1,
'order_date': '2024年7月1日',
'order_number': 'A00001'},
{'item_name': 'ふんわりマフィン4号',
'item_quantity': 4,
'order_date': '2024年7月2日',
'order_number': 'A00005'},
{'item_name': 'ふんわりマフィン5号',
'item_quantity': 5,
'order_date': '2024年7月2日',
'order_number': 'A00004'}]
降順
sorted_data = sorted(test_data, key=lambda x: x['item_name'], reverse=True)
pprint.pprint(sorted_data)
item_nameの降順
[{'item_name': 'ふんわりマフィン5号',
'item_quantity': 5,
'order_date': '2024年7月2日',
'order_number': 'A00004'},
{'item_name': 'ふんわりマフィン4号',
'item_quantity': 4,
'order_date': '2024年7月2日',
'order_number': 'A00005'},
{'item_name': 'ふわふわパン3号',
'item_quantity': 1,
'order_date': '2024年7月1日',
'order_number': 'A00001'},
{'item_name': 'ふわふわパン2号',
'item_quantity': 2,
'order_date': '2024年7月1日',
'order_number': 'A00003'},
{'item_name': 'ふわふわパン1号',
'item_quantity': 3,
'order_date': '2024年7月1日',
'order_number': 'A00002'}]
文字列を含む複数項目でソート
昇順
sorted_data = sorted(test_data, key=lambda x: (x['order_date'], x['item_name']))
pprint.pprint(sorted_data)
order_dateの昇順、item_nameの昇順
[{'item_name': 'ふわふわパン1号',
'item_quantity': 3,
'order_date': '2024年7月1日',
'order_number': 'A00002'},
{'item_name': 'ふわふわパン2号',
'item_quantity': 2,
'order_date': '2024年7月1日',
'order_number': 'A00003'},
{'item_name': 'ふわふわパン3号',
'item_quantity': 1,
'order_date': '2024年7月1日',
'order_number': 'A00001'},
{'item_name': 'ふんわりマフィン4号',
'item_quantity': 4,
'order_date': '2024年7月2日',
'order_number': 'A00005'},
{'item_name': 'ふんわりマフィン5号',
'item_quantity': 5,
'order_date': '2024年7月2日',
'order_number': 'A00004'}]
降順
sorted_data = sorted(test_data, key=lambda x: (x['order_date'], x['item_name']), reverse=True)
pprint.pprint(sorted_data)
order_dateの降順、item_nameの降順
[{'item_name': 'ふんわりマフィン5号',
'item_quantity': 5,
'order_date': '2024年7月2日',
'order_number': 'A00004'},
{'item_name': 'ふんわりマフィン4号',
'item_quantity': 4,
'order_date': '2024年7月2日',
'order_number': 'A00005'},
{'item_name': 'ふわふわパン3号',
'item_quantity': 1,
'order_date': '2024年7月1日',
'order_number': 'A00001'},
{'item_name': 'ふわふわパン2号',
'item_quantity': 2,
'order_date': '2024年7月1日',
'order_number': 'A00003'},
{'item_name': 'ふわふわパン1号',
'item_quantity': 3,
'order_date': '2024年7月1日',
'order_number': 'A00002'}]
文字列を含む複数項目の各項目で昇順降順を切り替えてソート
この方法が分からなくて悩んだ
整数の項目であれば-(マイナス)を付けて降順にできる
文字列であってもreverse=True
を付ければ降順にできる
しかし、文字列の場合かつ文字列以外の項目も含めたソートをしたい場合はどうすればいいのか
というわけで以下の関数を追加して挑戦してみる
# 文字列を1文字ずつord関数にかけて、その文字のUnicodeコードポイントを整数として取得
# 降順にしたいので取得した整数に-(マイナス)を付けて、配列にする
def to_ascii_list_reverse(s):
return [-ord(char) for char in s]
昇順、降順が混在
sorted_data = sorted(test_data, key=lambda x: (x['order_date'], to_ascii_list_reverse(x['item_name'])))
pprint.pprint(sorted_data)
order_dateの昇順、item_nameの降順
[{'item_name': 'ふわふわパン3号',
'item_quantity': 1,
'order_date': '2024年7月1日',
'order_number': 'A00001'},
{'item_name': 'ふわふわパン2号',
'item_quantity': 2,
'order_date': '2024年7月1日',
'order_number': 'A00003'},
{'item_name': 'ふわふわパン1号',
'item_quantity': 3,
'order_date': '2024年7月1日',
'order_number': 'A00002'},
{'item_name': 'ふんわりマフィン5号',
'item_quantity': 5,
'order_date': '2024年7月2日',
'order_number': 'A00004'},
{'item_name': 'ふんわりマフィン4号',
'item_quantity': 4,
'order_date': '2024年7月2日',
'order_number': 'A00005'}]
一見、正しくソートされているように見えるが、sortedと同じ挙動になるのかテストデータを増やして試してみた
import pprint
def to_ascii_list_reverse(s):
return [-ord(char) for char in s]
strings = ["ぶどう酒", "いちじくゼリー", "バナナスプリット", "デーツ", "りんごジュース", "チェリーパイ", "さくらんぼ", "ばなな",
"だて", "りんご", "いちじく", "ぶどう", "cherry", "fig", "date", "grape", "apple", "banana", "&*()_",
"+-*/=", "cherry789", "grape789", "fig456", "banana456", "date123", "apple123", "!@#$%"]
# to_ascii_list_reverseを使って降順にソート
sorted_by_ascii_list_reverse = sorted(strings, key=to_ascii_list_reverse)
# 通常のsortedを使って降順にソート
sorted_by_default_reverse = sorted(strings, reverse=True)
print("to_ascii_list_reverseを使った降順ソート:")
pprint.pprint(sorted_by_ascii_list_reverse)
print("\n通常のsortedを使った降順ソート:")
pprint.pprint(sorted_by_default_reverse)
to_ascii_list_reverseを使った降順ソート:
['バナナスプリット',
'デーツ',
'チェリーパイ',
'りんご',
'りんごジュース',
'ぶどう',
'ぶどう酒',
'ばなな',
'だて',
'さくらんぼ',
'いちじく',
'いちじくゼリー',
'grape',
'grape789',
'fig',
'fig456',
'date',
'date123',
'cherry',
'cherry789',
'banana',
'banana456',
'apple',
'apple123',
'+-*/=',
'&*()_',
'!@#$%']
通常のsortedを使った降順ソート:
['バナナスプリット',
'デーツ',
'チェリーパイ',
'りんごジュース',
'りんご',
'ぶどう酒',
'ぶどう',
'ばなな',
'だて',
'さくらんぼ',
'いちじくゼリー',
'いちじく',
'grape789',
'grape',
'fig456',
'fig',
'date123',
'date',
'cherry789',
'cherry',
'banana456',
'banana',
'apple123',
'apple',
'+-*/=',
'&*()_',
'!@#$%']
微妙に異なる・・・やはり素人がソートのロジックに首を突っ込むのは骨が折れそう
いきなり結論
答えはココに
ソートは、 安定 (stable) であることが保証されています。これはレコードの中に同じキーがある場合、元々の順序が維持されるということを意味します。
つまり、ソートしたい項目を逆順でsorted関数にかけてあげればいいみたい
order_dateの昇順、item_nameの降順なら先にitem_nameの降順にして次にorder_dateの昇順にする
sorted_data = sorted(test_data, key=lambda x: (x['item_name']), reverse=True)
sorted_data = sorted(sorted_data, key=lambda x: (x['order_date']))
pprint.pprint(sorted_data)
[{'item_name': 'ふわふわパン3号',
'item_quantity': 1,
'order_date': '2024年7月1日',
'order_number': 'A00001'},
{'item_name': 'ふわふわパン2号',
'item_quantity': 2,
'order_date': '2024年7月1日',
'order_number': 'A00003'},
{'item_name': 'ふわふわパン1号',
'item_quantity': 3,
'order_date': '2024年7月1日',
'order_number': 'A00002'},
{'item_name': 'ふんわりマフィン5号',
'item_quantity': 5,
'order_date': '2024年7月2日',
'order_number': 'A00004'},
{'item_name': 'ふんわりマフィン4号',
'item_quantity': 4,
'order_date': '2024年7月2日',
'order_number': 'A00005'}]
良さそう、自分で変なソートロジックを組まなくてよくなった
まとめ
- ソート条件の項目がそんなに多くなく、ソート対象のデータもそこまで多くないのであれば、何度かsortedを使って、期待するソート順を実現するのも良さそう
- 複雑化しそうであれば、比較関数を使ったほうが良さそう
参考
参考にさせていただきました、ありがとうございます!
Discussion