一覧CS-03-3 · 論理と集合

集合演算と対象者の抽出

重複のない集合を使い、条件に合う対象者と組合せを求める方法を学びます。

説明

集合は、要素を重複なく集めたものです。たとえば利用者IDの集合を使うと、「研修済みかつ申請済み」「申請済みだが未受講」といった対象を求められます。集合では、要素の順序は意味を持ちません。

このモジュールでは、同じ利用者一覧を使って和集合・共通部分・差集合・対称差を求めます。続いて「全体」を指定した補集合を扱い、最後に利用者と時間帯を組み合わせる直積を作ります。出力は見比べやすいようID順に並べますが、並べ替えは集合演算そのものとは別の処理です。

1重複したIDから集合を作る

申請記録に同じIDが複数回現れても、申請者としては一人です。重複を除いた集合を作ります。

requests = ['u1', 'u2', 'u2', 'u4']重複を含む申請記録; applied = set(requests)重複のない申請者集合; requests, sorted(applied), len(applied)記録・申請者・人数

元の記録は4件ですが、申請者はu1、u2、u4の3人です。setでは重複がなくなります。

2研修済みの利用者

別の条件として、研修済みの利用者を用意します。

trained = {'u2', 'u3', 'u4'}研修済みの集合; sorted(trained)ID順に表示

研修済みはu2、u3、u4です。申請者の一覧とは一部だけ重なっています。以後、この二つの集合から対象者を求めます。

3どちらかの集合に含まれる人

申請済み、または研修済みの利用者を全員集めます。これを和集合と呼びます。

sorted(applied | trained申請済みと研修済みの和集合)

u1、u2、u3、u4です。両方に含まれるu2とu4も、結果では一度ずつ現れます。

4両方の条件を満たす人

申請済みで、かつ研修済みの利用者を求めます。これは二つの集合の共通部分です。

eligible = applied & trained共通部分を保存; sorted(eligible)対象者を表示

u2とu4が両方の条件を満たします。たとえば、この二条件を利用要件とする場合、この二人が対象者です。

5申請済みだが未受講の人

申請者の集合から研修済みの集合を引きます。これを差集合と呼びます。

sorted(applied - trained)申請済み・未受講, sorted(trained - applied)研修済み・未申請

申請済みで未受講なのはu1、研修済みで未申請なのはu3です。差集合は左右を入れ替えると意味も結果も変わります。

6片方だけの集合に含まれる人

どちらか片方だけに含まれる利用者を求めます。これを対称差と呼びます。

sorted(applied ^ trained)対称差, sorted((applied - trained) | (trained - applied))二つの差集合を合わせる

どちらの式もu1とu3です。対称差は、向きの違う二つの差集合を合わせたものと同じです。

7未申請者を求めるには全体が必要

「申請していない人」を求めるには、誰を全利用者とするかを決める必要があります。今回はu1〜u5を全体とします。

users = {'u1', 'u2', 'u3', 'u4', 'u5'}今回の全利用者; sorted(users - applied)全体に対する未申請者

未申請者はu3とu5です。全体usersからappliedを引いた集合は、この全体に対するappliedの補集合です。全体が変われば補集合も変わります。

8全体の外にあるIDの検出

データに全利用者一覧へ存在しないIDが混ざった場合を調べます。

imported = applied | {'u9'}未知のIDを含める; imported <= users全要素が利用者一覧に含まれるか, sorted(imported - users)一覧の外にあるID

部分集合の判定はFalseとなり、全体に含まれないu9が見つかりました。対象一覧を処理する前に、IDが正しい範囲に収まるかも調べられます。

9利用者と時間帯の直積

対象者u2・u4に、午前・午後の二つの時間帯を組み合わせます。二つの集合の全組合せを直積と呼びます。

from itertools import product全組合せを作る機能; slots = ['AM', 'PM']二つの時間帯; bookings = list(product(sorted(eligible), slots))利用者と時間帯の直積; bookings全候補を表示

u2/AM、u2/PM、u4/AM、u4/PMの4組です。利用者2人×時間帯2種類で4通りになります。ここでは各要素が、利用者と時間帯からなる組になっています。

10組合せから利用できない枠を除く

u2の午後だけを利用不可とします。利用者そのものではなく、利用者と時間帯の組を除きます。

unavailable = {('u2', 'PM')}利用不可の組; sorted(set(bookings) - unavailable)候補の直積から除外

u2の午前、u4の午前と午後の計3枠が残ります。u2という利用者全体を除外した場合とは違い、u2の午前は候補に残ります。

まとめ

和集合はどちらか、共通部分は両方、差集合は片方からもう片方を除いた対象です。対称差は片方だけに含まれる要素です。

補集合には全体集合の指定が必要です。直積では一つの要素が複数項目の組になるため、利用者全体の除外と一つの時間枠の除外を別々に表せます。

操作履歴と解説を振り返れます。

Python 3

1
requests = ['u1', 'u2', 'u2', 'u4']; applied = set(requests); requests, sorted(applied), len(applied)
(['u1', 'u2', 'u2', 'u4'], ['u1', 'u2', 'u4'], 3)
2
trained = {'u2', 'u3', 'u4'}; sorted(trained)
['u2', 'u3', 'u4']
3
sorted(applied | trained)
['u1', 'u2', 'u3', 'u4']
4
eligible = applied & trained; sorted(eligible)
['u2', 'u4']
5
sorted(applied - trained), sorted(trained - applied)
(['u1'], ['u3'])
6
sorted(applied ^ trained), sorted((applied - trained) | (trained - applied))
(['u1', 'u3'], ['u1', 'u3'])
7
users = {'u1', 'u2', 'u3', 'u4', 'u5'}; sorted(users - applied)
['u3', 'u5']
8
imported = applied | {'u9'}; imported <= users, sorted(imported - users)
(False, ['u9'])
9
from itertools import product; slots = ['AM', 'PM']; bookings = list(product(sorted(eligible), slots)); bookings
[('u2', 'AM'), ('u2', 'PM'), ('u4', 'AM'), ('u4', 'PM')]
10
unavailable = {('u2', 'PM')}; sorted(set(bookings) - unavailable)
[('u2', 'AM'), ('u4', 'AM'), ('u4', 'PM')]

演習完了