検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
>>
effective_python

比較特殊関数を実装していないオブジェクトに対する sort() の key パラメータを利用した並べ替え 投稿一覧へ戻る

Published 2020年6月20日8:27 by mootaro23

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

ご存知のように list タイプでは sort() が提供されていて、デフォルトでは要素が昇順に並べ替えられます。

numbers = [93, 86, 11, 68, 70]
numbers.sort()
print(numbers) # [11, 68, 70, 86, 93]



sort() はほぼ全ての組み込みタイプに適用可能です。


では、オブジェクトに対してはどのように機能するでしょう?


class Friend:
def __init__(self, name: str, weight: float):
self.name = name
self.weight = weight

def __repr__(self):
return f"Friend({self.name!r}, {self.weight})"

friends = [
Friend('Netaro', 45),
Friend('Momotaro', 50),
Friend('Kintaro', 88),
Friend('Kitaro', 50),
]

friends.sort() # TypeError: '<' not supported between instances of 'Friend' and 'Friend'



エラーになりました。
これは、引数なしの sort() が Friend オブジェクトでは定義されていない比較特殊関数を必要としているからです。


ただ比較特殊関数を定義しなくても、オブジェクトが並べ替えの対象としたい属性を有している場合なら、
sort() の key パラメータにその属性値を返す関数を渡すことで通常の並べ替えを行うことができます。
当たり前ですがその返り値は比較可能なものでなければだめですよ。


print(f"並べ替え前: {friends}")

# 並べ替え前: [Friend('Netaro', 45), Friend('Momotaro', 50), Friend('Kintaro', 88), Friend('Kitaro', 50)]



friends.sort(key=lambda x: x.name)
print(f"名前で並べ替え後: {friends}")

# 名前で並べ替え後: [Friend('Kintaro', 88), Friend('Kitaro', 50), Friend('Momotaro', 50), Friend('Netaro', 45)]




もちろん key 提供関数が "weight" を与えるようにすれば体重順に並べ替えることもできます。


friends.sort(key=lambda x: x.weight)
print(f"体重で並べ替え後: {friends}")

# 体重で並べ替え後: [Friend('Netaro', 45), Friend('Kitaro', 50), Friend('Momotaro', 50), Friend('Kintaro', 88)]




では、複数の項目を対象に並べ替えをしたい場合はどうしましょうか?


最も簡単な方法はタプルを利用することかもしれません。


複数の要素から成るタプル同士の比較では、まずインデックス[0] の値の比較が行われ、もし等しければインデックス[1] で比較が行われます。


friends リストをまず体重で、続いて名前で並べ替えると次のように成ります。


friends.sort(key=lambda x: (x.weight, x.name))
print(friends)

# [Friend('Netaro', 45), Friend('Kitaro', 50), Friend('Momotaro', 50), Friend('Kintaro', 88)]




このタプルを利用した並べ替えの短所の1つは、並べ替えの順番 (昇順か降順か) が複数の比較要素すべてに同様に適用されてしまうことです。


friends.sort(key=lambda x: (x.weight, x.name), reverse=True)
print(friends)

# [Friend('Kintaro', 88), Friend('Momotaro', 50), Friend('Kitaro', 50), Friend('Netaro', 45)]




同じ体重の人 ('Momotaro' と 'Kitaro') では名前の順番も降順になってしまっています。


降順に並べ替えたい対象が数値である場合の対処は簡単です。
その値を -1 倍し、後はデフォルトの並べ替え順 (昇順) で出力するだけです。


friends.sort(key=lambda x: (-x.weight, x.name))
print(friends)

# [Friend('Kintaro', 88), Friend('Kitaro', 50), Friend('Momotaro', 50), Friend('Netaro', 45)]




体重は降順に、体重が同じ人では、名前で昇順にちゃんと並んでいます。


しかし、-1 をかけて降順にするこの方法は全てのタイプに適用できるわけではありません。
この例で name を降順にしたいからといって -x.name としても、String タイプの -1 倍はナンジャラホイ、ということでエラーになります。


このような状況を考慮して Python では stable sorting algorithm が採用されています。
これは、list タイプの sort() で並べ替えが行われる場合、対象となるリスト内の要素の順番は保持される、というものです。
つまり、比較結果が等しい場合は元々リストにセットされていた順番に値が並べられる、ということです。


このアルゴリズムを前提に、同じリストに対して異なる並べ替え基準を設定した sort() を複数回実行することで、目的とする並べ替え結果を得ることができます。


次のように並べ方の異なる sort() を2回実行して、weight はデフォルトの昇順、name は降順に並べ替えます。


friends.sort(key=lambda x: x.name, reverse=True)
friends.sort(key=lambda x: x.weight)
print(friends)

# [Friend('Netaro', 45), Friend('Momotaro', 50), Friend('Kitaro', 50), Friend('Kintaro', 88)]




全体は weight の昇順で並んでいますが、weight が等しい 'Momotaro' と 'Kitaro' は name の降順で並んでいます、大成功です!!


この方法を利用すると、比較要素数がどんなに多くても、それぞれの要素で並べ替え順序の異なる思い通りの結果を得ることができます。
大切なことは sort() をどの順番で実行するか、ということです。


ただし、おススメは key 関数からタプルを返し、-1 倍が許されているタイプに適用することで並べ替え順をリバースする方法です。
前出のようにコードもすっきりとして読解性も高くなります。
並べ替え順を指定した sort() を複数回実行する方法は、あくまでも -1 倍が許されていないタイプを対象にしなければいけないときに限りましょう。
この記事に興味のある方は次の記事にも関心を持っているようです...
- People who read this article may also be interested in following articles ... -
【Python 雑談・雑学 + coding challenge】sorted 組み込み関数の key パラメータをうまく使って、カスタムオブジェクトを簡単にソートしよう! __getitem__、__len__ 特殊関数 ( special methods, dunder methods ) を実装すれば立派なシーケンス ( sequence ) です
【 Effective Python, 2nd Edition 】__set_name__ デスクリプタ専用特殊関数 ( special method for descriptor ) を利用してデスクリプタインスタンスを割り当てたクラス変数名を取得し、コードの冗長性を排除しよう!
【 Effective Python, 2nd Edition 】クラスインスタンスを関数として利用可能にする __call__ 特殊関数を含んだクラスを定義してフック ( hook ) として利用することで、既存の API の機能拡張を計ろう!
【Python 雑談・雑学】 関数への引数の渡し方・受け取り方 - *args、**kwargs を利用した引数の渡し方、パラメータの受け取り方、総復習 -
【 Effective Python, 2nd Edition 】今回も懲りずにメタクラス ( metaclass ) - __init_subclass__() 特殊関数でメタクラスをもっと活用しよう!
【 Effective Python, 2nd Edition 】Python においてクラス属性に厳密な private が無いのは何故? できる限り利用すべきではない理由とは? それでも private の使用が有効な状況とは?
【 Effective Python, 2nd Edition + coding challenge 】プログラムを並列処理 ( concurrency ) パターンへ移行するタイミングとツールを考えるシリーズ 第2回 - Conway's Game of Life coding challenge の実装例と課題、の巻