集合演算と対象者の抽出
重複のない集合を使い、条件に合う対象者と組合せを求める方法を学びます。
説明
集合は、要素を重複なく集めたものです。たとえば利用者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
requests = ['u1', 'u2', 'u2', 'u4']; applied = set(requests); requests, sorted(applied), len(applied)(['u1', 'u2', 'u2', 'u4'], ['u1', 'u2', 'u4'], 3)
trained = {'u2', 'u3', 'u4'}; sorted(trained)['u2', 'u3', 'u4']
sorted(applied | trained)['u1', 'u2', 'u3', 'u4']
eligible = applied & trained; sorted(eligible)['u2', 'u4']
sorted(applied - trained), sorted(trained - applied)(['u1'], ['u3'])
sorted(applied ^ trained), sorted((applied - trained) | (trained - applied))(['u1', 'u3'], ['u1', 'u3'])
users = {'u1', 'u2', 'u3', 'u4', 'u5'}; sorted(users - applied)['u3', 'u5']
imported = applied | {'u9'}; imported <= users, sorted(imported - users)(False, ['u9'])
from itertools import product; slots = ['AM', 'PM']; bookings = list(product(sorted(eligible), slots)); bookings[('u2', 'AM'), ('u2', 'PM'), ('u4', 'AM'), ('u4', 'PM')]unavailable = {('u2', 'PM')}; sorted(set(bookings) - unavailable)[('u2', 'AM'), ('u4', 'AM'), ('u4', 'PM')]演習完了