Pythonのfilterとsort

Pythonでちょっとしたツールを作る事が増えたのですが、やはりコレクション操作は重要だなと実感しました。コレクションから特定の値を抜き出したりとか、特定の値になるまでループしたりとか、やはり何するにもこの辺りはつきまとってくるんですね。

filter

と言うわけで最初はPythonでフィルター操作どうやるんだっけと言う話です。

devdocs でさっと検索してみると filter の説明が。

https://devdocs.io/python~3.8/library/functions#filter

バッテリー同梱なんて言われている言語ですから当然基本ライブラリに含まれていますね。

第一引数にフィルター関数を、第二引数には対象となるリストをセットする感じ。

>>> def filter_function(parameter):
...     parameter == 'a'
...
>>> filter(filter_function, ['a', 'b', 'c'])

第一引数の関数はラムダでも行けます。

>>> list(filter(lambda p: p == 'a', ['a', 'b', 'c']))

戻り値が iterator なので list 関数でリスト化できます。つなげて書くとこんな感じ。

>>> list(filter(lambda p: p in filter_strings, ['a', 'b', 'c']))

リスト内包表記でも同じことを実現できます。

>>> [item for item in ['a', 'b', 'c'] if item == 'a']
['a']
>>> [item for item in range(0, 30) if item <= 15]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

sort

ソートについては以下のドキュメントに詳しく載ってました。

https://docs.python.org/ja/3/howto/sorting.html

Pythonでのソートは sorted 関数でできます。

>>> sorted([1, 3, 2, 0])
[0, 1, 2, 3]

引数にソートに使用する値を取り出す関数を与えれば複雑なリストでもソートできます。

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

operator モジュールにはこれらを簡略化する関数が定義されているので一般的にはこれを使うことになるのかなと。 itemgetterイテレーション可能な値から特定のインデックス値を指定する関数です。 attrgetter は名前付けされた属性(Classのインスタンスなど)に対して属性を指定する関数です。

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

>>> 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=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

itemgetterattrgetter は複数段階でのソートを可能にしてくれます。

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

reverse パラメータを使うと昇順と降順を指定できます。 True の場合が降順になるようです。

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

Javaを使っていると compareTo メソッドみたいな役割がないのか気になりますが、Pythonでは lt メソッドがそれに該当します。

from operator import itemgetter, attrgetter


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))

    def __lt__(self, other):
        return self.age < other.age


student_objects = [
    Student('john', 'A', 15),
    Student('jane', 'B', 12),
    Student('dave', 'B', 10),
]

sorted(student_objects)
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Pythonはメソッドをクラス定義後に追加できるみたいなので、外部のライブラリに定義されている場合は以下のようにすることでソート可能なクラスにできますね。

Student.__lt__ = lambda self, other: self.age < other.age