2012年2月25日土曜日

Python組み込みクラス(set 集合型)を使った論理式

この前ソースを眺めていたら、次のようなコードがありました。

groups = set([row.group_id for row in rows])
groups_required = set([row.group_id for row in rows2])

if groups.intersection(groups_required):
    r = True
else:
    r = False

このコードで if 文は何を判定しているのか、理解できなかったので調べてみました。

set クラス

Python組み込みで set 及び frozenset クラスがある。Pythonドキュメントの記述を紹介する。

set(集合)型 — set, frozenset (抜粋)

set オブジェクトは順序付けされていない hashable (ハッシュ可能な) オブジェクトのコレクションです。よくある使い方には、メンバーシップのテスト、数列から重複を削除する、そして論理積、論理和、差集合、対称差など数学的演算の計算が含まれます。

バージョン 2.4 で追加.

他のコレクションと同様、 sets は x in set, len(set) および for x in set をサポートします。順序を持たないコレクションとして、 sets は要素の位置と (要素の) 挿入位置を保持しません。したがって、 sets はインデックス、スライス、その他のシーケンス的な振る舞いをサポートしません。

set および frozenset という、2つの組み込みset型があります。 set は変更可能な — add() や remove() のようなメソッドを使って内容を変更できます。変更可能なため、ハッシュ値を持たず、また辞書のキーや他のsetの要素として用いることができません。 frozenset 型はイミュータブルで、ハッシュ化可能 (hashable) です — 作成後に内容を改変できません。そのため、辞書のキーや他の集合の要素として使えます。

参考: Python 2.7ja1 documentation - 5.7. set(集合)型 — set, frozenset

一読だけでは何のことかわからない。しかし setは書き換え可能だが frozensetは改変不可、そして frozensetはハッシュ化可能で辞書のキーや他の集合の要素として使えるが、set はハッシュ値を持たないためキーや他の集合の要素としては使えないことがわかる。その他は、ほぼ同じ機能を持ったクラスのようだ。

set は重複を持たない、順序を持たない要素の集合オブジェクトということのようだ。さっそく試してみよう。

>>> spam = set('monty python')
>>> spam
set([' ', 'h', 'm', 'o', 'n', 'p', 't', 'y'])
適当な文字列を set に渡すと、重複を排除した要素に分解することがわかる。また順序も保存していない。

>>> spam = set(['red','green','blue','red'])
>>> spam
set(['blue', 'green', 'red'])
今度はリスト値を渡してみる。同様に重複や順序を保存しない集合値になる。

set は、いくつかの演算機能(メソッド)を持っている。これを試してみる。

>>> spam = set(['red','green','blue','red'])
>>> len(spam)
3
>>> 'blue' in spam
True
>>> 'yellow' not in spam
True
len は要素の数をカウントして返す。in は要素に含まれるかどうか、not in は要素に含まれていないかどうかをチェックしてブール値で返す。

この他にもたくさんの演算機能がある。詳細は次のサイトを参考にしてください。

参考
Python 2.7ja1 documentation - 5.7. set(集合)型 — set, frozenset
w.koshigoe.jp - set型(集合型)
バリケンのPython日記

intersection メソッド

それでは本題の if 文に話を戻す。if 文では intersection メソッドが使われている。このメソッドは次のような説明されている。

intersection(other, ...)
set & other & ...

set と全ての other に共通する要素を持つ、新しい set を返します。

バージョン 2.6 で変更: 複数のイテラブルからの入力を受け入れるようになりました。

Python 2.7ja1 documentation - 5.7. set(集合)型 — intersection

複数の setオブジェクトから、共通する要素だけを抜き出す機能のようだ、試してみる。

>>> spam = set(['red','green','blue','cyan','magenta','yellow'])
>>> ham = set(['blue','cyan','white','black'])
>>> spam.intersection(ham)
set(['blue', 'cyan'])
>>> spam = set(['red','green','blue','cyan','magenta','yellow'])
>>> ham = set(['white','black'])
>>> spam.intersection(ham)
set([])

共通する要素が存在しない場合は空値になる。つまり条件式に使用する真理値は、空値やゼロは Falseとされ、それ以外の値が入ったオブジェクトは True とみなされる。

参考: Python 2.7ja1 documentation - 5.1. 真理値テスト

つまり最初に出た次のコードは、

groups = set([row.group_id for row in rows])
groups_required = set([row.group_id for row in rows2])

if groups.intersection(groups_required):
    r = True
else:
    r = False
groups と groups_required を比べて、両方のオブジェクトに共通する要素がある(論理積)場合は r=True とし、共通の要素が存在しない場合は r=False と動作することがわかる。結構このようなロジックは使うのではないだろうか。

ちなみに intersection は次のようにアンド(&)演算子でも記述可能だ。

>>> spam = set(['red','green','blue','cyan','magenta','yellow'])
>>> ham = set(['blue','cyan','white','black'])
>>> spam & ham
set(['blue', 'cyan'])
おまけ

最後に、setクラスを使った集合同士の論理式を示してみます。

等価
(集合が等しい)
比較演算子のイコール(==)を使用します。
>>> spam = set(['red','green','blue'])
>>> ham = set(['red','green','blue'])
>>> spam==ham
True
非交和
(共通の要素を持たない)
isdisjoint メソッドを使用します。Python2.6 以上で使用可能です。
>>> spam = set(['red','green','blue'])
>>> ham = set(['cyan','magenta','yellow'])
>>> spam.isdisjoint(ham)
True
部分集合
issuperset, issubsetメソッドを使用します。
>>> spam = set(['red','green','blue','black'])
>>> ham = set(['red','green','blue'])
>>> spam.issuperset(ham)
True
もしくは、比較演算子の以上・以下(>=,<=)を使用します。
>>> spam >= ham
True
真部分集合(集合同士は等しくない)では比較演算子のより大きい・より小さい(>,<)を使用します。
>>> spam = set(['red','green','blue'])
>>> ham = set(['red','green','blue'])
>>> spam > ham
False
論理和
union メソッドを使用します。
>>> spam = set(['red','green','blue','black'])
>>> ham = set(['red','green','blue','white'])
>>> spam.union(ham)
set(['blue', 'green', 'black', 'white', 'red'])
>>> bool(spam.union(ham))
True
もしくは パイプ(|)演算子を使用します。
>>> spam | ham
set(['blue', 'green', 'black', 'white', 'red'])
>>> bool(spam | ham)
True
論理積
intersection メソッドを使用します。
>>> spam = set(['red','green','blue','black'])
>>> ham = set(['cyan','magenta','yellow','black'])
>>> spam.intersection(ham)
set(['black'])
>>> bool(spam.intersection(ham))
True
もしくは アンド()演算子を使用します。
>>> spam & ham
set(['black'])
>>> bool(spam & ham)
True
論理差
(他の集合の要素を除く)
difference メソッドを使用します。
>>> spam = set(['red','green','blue','black'])
>>> ham = set(['cyan','magenta','yellow','black'])
>>> spam.difference(ham)
set(['blue', 'green', 'red'])
>>> bool(spam.difference(ham))
True
もしくは マイナス()演算子を使用します。
>>> spam - ham
set(['blue', 'green', 'red'])
>>> bool(spam - ham)
True
排他的論理和
symmetric_difference メソッドを使用します。
>>> spam = set(['red','green','blue','black'])
>>> ham = set(['cyan','magenta','yellow','black'])
>>> spam.symmetric_difference(ham)
set(['blue', 'yellow', 'green', 'cyan', 'magenta', 'red'])
>>> bool(spam.symmetric_difference(ham))
True
もしくは カレット()演算子を使用します。
>>> spam ^ ham
set(['blue', 'yellow', 'green', 'cyan', 'magenta', 'red'])
>>> bool(spam ^ ham)
True

参考: Python 2.7ja1 documentation - 5.7. set(集合)型 — set, frozenset