Sorting Techniques
******************

著者:
   Andrew Dalke and Raymond Hettinger

Python のリストにはリストをインプレースに変更する、組み込みメソッド
"list.sort()" があります。他にもイテラブルからソートしたリストを作成す
る組み込み関数 "sorted()" があります。

このドキュメントでは Python を使った様々なソートのテクニックを探索しま
す。


ソートの基本
============

単純な昇順のソートはとても簡単です: "sorted()" 関数を呼ぶだけです。そ
うすれば、新たにソートされたリストが返されます:

   >>> sorted([5, 2, 3, 1, 4])
   [1, 2, 3, 4, 5]

"list.sort()" メソッドを呼びだしても同じことができます。この方法はリス
トをインプレースに変更します (そして sorted との混乱を避けるため
"None" を返します)。多くの場合、こちらの方法は "sorted()" と比べると不
便です - ただし、元々のリストが不要な場合には、わずかですがより効率的
です。

   >>> a = [5, 2, 3, 1, 4]
   >>> a.sort()
   >>> a
   [1, 2, 3, 4, 5]

違いは他にもあります、 "list.sort()" メソッドはリストにのみ定義されて
います。一方 "sorted()" 関数は任意のイテラブルを受け付けます。

   >>> sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})
   [1, 2, 3, 4, 5]


Key 関数
========

"list.sort()" と "sorted()" には *key* パラメータがあります。 これは比
較を行う前にリストの各要素に対して呼び出される関数 (または呼び出し可能
オブジェクト) を指定するパラメータです。

例えば、大文字小文字を区別しない文字列比較の例:

   >>> sorted("This is a test string from Andrew".split(), key=str.casefold)
   ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']

*key* パラメータの値は関数または呼び出し可能オブジェクトであって、単一
の引数をとり、ソートに利用されるキー値を返すものでなければいけません。
この制約によりソートを高速に行えます、キー関数は各入力レコードに対して
きっちり一回だけ呼び出されるからです。

よくある利用パターンはいくつかの要素から成る対象をインデクスのどれかを
キーとしてソートすることです。例えば:

   >>> student_tuples = [
   ...     ('john', 'A', 15),
   ...     ('jane', 'B', 12),
   ...     ('dave', 'B', 10),
   ... ]
   >>> sorted(student_tuples, key=lambda student: student[2])   # sort by age
   [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

同じテクニックは名前づけされた属性 (named attributes) を使うことでオブ
ジェクトに対しても動作します。例えば:

   >>> class Student:
   ...     def __init__(self, name, grade, age):
   ...         self.name = name
   ...         self.grade = grade
   ...         self.age = age
   ...     def __repr__(self):
   ...         return repr((self.name, self.grade, self.age))

   >>> student_objects = [
   ...     Student('john', 'A', 15),
   ...     Student('jane', 'B', 12),
   ...     Student('dave', 'B', 10),
   ... ]
   >>> sorted(student_objects, key=lambda student: student.age)   # sort by age
   [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Objects with named attributes can be made by a regular class as shown
above, or they can be instances of "dataclass" or a *named tuple*.


Operator Module Functions and Partial Function Evaluation
=========================================================

The *key function* patterns shown above are very common, so Python
provides convenience functions to make accessor functions easier and
faster. The "operator" module has "itemgetter()", "attrgetter()", and
a "methodcaller()" function.

これらの関数を利用すると、上の例はもっと簡単で高速になります:

   >>> from operator import itemgetter, attrgetter

   >>> sorted(student_tuples, key=itemgetter(2))
   [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

   >>> sorted(student_objects, key=attrgetter('age'))
   [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

operator モジュールの関数は複数の段階でのソートを可能にします。例えば
、 *grade* でソートしてさらに *age* でソートする場合:

   >>> sorted(student_tuples, key=itemgetter(1,2))
   [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

   >>> sorted(student_objects, key=attrgetter('grade', 'age'))
   [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

The "functools" module provides another helpful tool for making key-
functions.  The "partial()" function can reduce the arity of a multi-
argument function making it suitable for use as a key-function.

   >>> from functools import partial
   >>> from unicodedata import normalize

   >>> names = 'Zoë Åbjørn Núñez Élana Zeke Abe Nubia Eloise'.split()

   >>> sorted(names, key=partial(normalize, 'NFD'))
   ['Abe', 'Åbjørn', 'Eloise', 'Élana', 'Nubia', 'Núñez', 'Zeke', 'Zoë']

   >>> sorted(names, key=partial(normalize, 'NFC'))
   ['Abe', 'Eloise', 'Nubia', 'Núñez', 'Zeke', 'Zoë', 'Åbjørn', 'Élana']


昇順と降順
==========

"list.sort()" と "sorted()" の両方とも *reverse* パラメータを 真偽値と
して受け付けます。このパラメータは降順ソートを行うかどうかの フラグと
して利用されます。 例えば、学生のデータを *age* の逆順で得たい場合:

   >>> sorted(student_tuples, key=itemgetter(2), reverse=True)
   [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

   >>> sorted(student_objects, key=attrgetter('age'), reverse=True)
   [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]


ソートの安定性と複合的なソート
==============================

ソートは、 安定 (stable) であることが保証されています。これはレコード
の中に同じキーがある場合、元々の順序が維持されるということを意味します
。

   >>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
   >>> sorted(data, key=itemgetter(0))
   [('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

二つの *blue* のレコードが元々の順序を維持して、 "('blue', 1)" が
"('blue', 2)" の前にあることに注意してください。

この素晴しい性質によって複数のソートを段階的に組み合わせることができま
す。例えば、学生データを *grade* の降順にソートし、さらに *age* の昇順
にソートしたい場合には、まず *age* でソートし、次に *grade* でもう一度
ソートします:

   >>> s = sorted(student_objects, key=attrgetter('age'))     # sort on secondary key
   >>> sorted(s, key=attrgetter('grade'), reverse=True)       # now sort on primary key, descending
   [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

この処理は、リストおよびフィールド名とソート順序のタプルを複数受け取れ
るラッパー関数へ抽象化できます。

   >>> def multisort(xs, specs):
   ...     for key, reverse in reversed(specs):
   ...         xs.sort(key=attrgetter(key), reverse=reverse)
   ...     return xs

   >>> multisort(list(student_objects), (('grade', True), ('age', False)))
   [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Python では Timsort アルゴリズムが利用されていて、効率良く複数のソート
を行うことができます、これは現在のデータセット中のあらゆる順序をそのま
ま利用できるからです。


デコレート - ソート - アンデコレート (DSU)
==========================================

このイディオムは以下の3つのステップにちなんでデコレート-ソート-アンデ
コレート (Decorate-Sort-Undecorate) と呼ばれています:

* まず、元となるリストをソートしたい順序を制御する新しい値でデコレート
  します。

* 次に、デコレートしたリストをソートします。

* 最後に、デコレートを取り除き、新しい順序で元々の値のみを持つリストを
  作ります。

例えば、DSU アプローチを利用して学生データを *grade* でソートする場合:

   >>> decorated = [(student.grade, i, student) for i, student in enumerate(student_objects)]
   >>> decorated.sort()
   >>> [student for grade, i, student in decorated]               # undecorate
   [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

このイディオムはタブルが辞書編集的に比較されるため正しく動作します; 最
初の要素が比較され、同じ場合には第二の要素が比較され、以下も同様に動き
ます。

デコレートしたリストのインデクス *i* は全ての場合で含まれる必要はあり
ませんが、そうすることで二つの利点があります:

* ソートが安定になります -- もし二つの要素が同じキーを持つ場合、それら
  の順序がソートされたリストでも維持されます。

* 元々の要素が比較可能な要素を持つとは限りません、なぜならデコレートさ
  れたタブルの順序は多くの場合、最初の二つの要素で決定されるからです。
  例として元のリストは直接比較できない複素数を含むことができます。

このイディオムの別名に Schwartzian transform があります。これは Perl
プログラマの間で有名な Randal L. Schwartz にちなんでいます。

いまや Python のソートは key 関数による方法を提供しているので、このテ
クニックは不要でしょう。


比較関数
========

ソートのための絶対的な値を返すキー関数と違って、2つの入力を受け取り、
その相対的な順序を計算するような関数を比較関数といいます。

例えば、 天秤 は2つのサンプルを比較し、より軽いか、同じか、より重いか
の相対的な順序を与えます。同じように、 "cmp(a, b)" などの比較関数はよ
り小さいときは負の値を、入力が等しい場合は 0 を、より大きい場合は正の
値を返します。

他のプログラミング言語で書かれたアルゴリズムを Python に書き直すときに
比較関数を目にすることがよくあります。また、その API の一部として比較
関数を提供しているライブラリもあります。例えば、 "locale.strcoll()" は
比較関数です。

このような場合に対処するため、Python は比較関数をラップしてキー関数と
して使えるようにする関数 "functools.cmp_to_key" を提供しています。

   sorted(words, key=cmp_to_key(strcoll))  # locale-aware sort order


残りのはしばし
==============

* ロケールに対応したソートを行うには、キー関数に "locale.strxfrm()" を
  使うか、比較関数に "locale.strcoll()" を使ってください。これが必要な
  のは、同じアルファベットを使っていたとしても、文化が違えば "アルファ
  ベット順" の意味するものが変わることがあるからです。

* *reverse* パラメータはソートの安定性を保ちます (ですから、レコードの
  キーが等しい場合元々の順序が維持されます)。面白いことにこの影響はパ
  ラメータ無しで "reversed()" 関数を二回使うことで模倣することができま
  す:

     >>> data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
     >>> standard_way = sorted(data, key=itemgetter(0), reverse=True)
     >>> double_reversed = list(reversed(sorted(reversed(data), key=itemgetter(0))))
     >>> assert standard_way == double_reversed
     >>> standard_way
     [('red', 1), ('red', 2), ('blue', 1), ('blue', 2)]

* ソート関数は、2つのオブジェクトを比較する際 "<" を用います。したがっ
  て、クラスに標準のソート順序を追加することは "__lt__()" メソッドを定
  義することで達成できます。

     >>> Student.__lt__ = lambda self, other: self.age < other.age
     >>> sorted(student_objects)
     [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

  However, note that "<" can fall back to using "__gt__()" if
  "__lt__()" is not implemented (see "object.__lt__()" for details on
  the mechanics).  To avoid surprises, **PEP 8** recommends that all
  six comparison methods be implemented. The "total_ordering()"
  decorator is provided to make that task easier.

* key 関数はソートするオブジェクトに依存する必要はありません。 key 関
  数は外部リソースにアクセスすることもできます。例えば学生の成績が辞書
  に保存されている場合、それを利用して別の学生の名前のリストをソートす
  ることができます:

     >>> students = ['dave', 'john', 'jane']
     >>> newgrades = {'john': 'F', 'jane':'A', 'dave': 'C'}
     >>> sorted(students, key=newgrades.__getitem__)
     ['jane', 'dave', 'john']


Partial Sorts
=============

Some applications require only some of the data to be ordered.  The
standard library provides several tools that do less work than a full
sort:

* "min()" and "max()" return the smallest and largest values,
  respectively.  These functions make a single pass over the input
  data and require almost no auxiliary memory.

* "heapq.nsmallest()" and "heapq.nlargest()" return the *n* smallest
  and largest values, respectively.  These functions make a single
  pass over the data keeping only *n* elements in memory at a time.
  For values of *n* that are small relative to the number of inputs,
  these functions make far fewer comparisons than a full sort.

* "heapq.heappush()" and "heapq.heappop()" create and maintain a
  partially sorted arrangement of data that keeps the smallest element
  at position "0".  These functions are suitable for implementing
  priority queues which are commonly used for task scheduling.
