Python 3: Deep Dive (Part 3 - Dictionaries, Sets, JSON): 集合演算 (セクション5-2/12)
Python の集合操作には、和集合(`|`)、積集合(`&`)、差集合(`-`)、対称差(`^`)などの基本演算があり、これらはメソッドまたは演算子を使って実行できます。
集合の更新操作(`|=`, `&=`, `-=`, `^=`)を使用すると、新しい集合を作成せずに既存の集合を直接変更でき、メモリ効率が向上します。
実践的な応用として、テキストからのストップワード除去やAPIから取得したデータのフィルタリングなど、データ処理タスクを効率的に実行できます。
Python プログラミングの世界では、集合(セット)は一意の要素のコレクションを扱うための強力な機能を提供する基本的なデータ構造です。これまでの講座では、集合の作成と基本的な操作について学んできました。このブログ記事では、「Python 3: Deep Dive (Part 3 - Dictionaries, Sets, JSON)」のセクション5:集合のレッスン32から35に焦点を当て、高度な集合演算についてより深く掘り下げていきます。
高度な集合演算の導入
Python の集合は、一意で不変な要素の順序なしコレクションです。メンバーシップテストに非常に効率的で、和集合、積集合、差集合、対称差などの数学的な集合演算を実行できます。これらの演算とその効果的な使用方法を理解することで、Python プログラミングのスキルを大幅に向上させることができます。
この記事では以下の内容を探求します:
メソッドと演算子の両方を使用して高度な集合演算を実行する方法
これらの演算の微妙な違い(順序と結合性を含む)
更新演算を使用して集合を変更する方法
実世界のアプリケーションを示す実践的な例
和集合と積集合
2つのアプローチ:メソッドと演算子
Python は集合演算を実行するための2つの方法を提供します:
メソッド:`.union()`、`.intersection()` などの関数で、任意のイテラブル(集合に限らない)を受け入れることができます。
演算子:和集合の `|` や積集合の `&` などの記号で、両方のオペランドが集合である必要があります。
和集合
和集合は、2つの集合からすべての一意な要素を結合します。
`|` 演算子の使用:
s1 = {1, 2, 3}
s2 = {3, 4, 5}
union_set = s1 | s2
print(union_set) # 出力: {1, 2, 3, 4, 5}
`.union()` メソッドの使用:
union_set = s1.union(s2)
print(union_set) # 出力: {1, 2, 3, 4, 5}
複数の集合との使用:
s3 = {5, 6, 7}
union_set = s1.union(s2, s3)
print(union_set) # 出力: {1, 2, 3, 4, 5, 6, 7}
イテラブルの使用:
union_set = s1.union([8, 9], (10, 11))
print(union_set) # 出力: {1, 2, 3, 8, 9, 10, 11}
積集合
積集合は、両方の集合に存在する要素のみを含みます。
`&` 演算子の使用:
intersection_set = s1 & s2
print(intersection_set) # 出力: {3}
`.intersection()` メソッドの使用:
intersection_set = s1.intersection(s2)
print(intersection_set) # 出力: {3}
複数の集合との使用:
s3 = {3, 5, 7}
intersection_set = s1.intersection(s2, s3)
print(intersection_set) # 出力: {3}
イテラブルの使用:
intersection_set = s1.intersection([2, 3, 4])
print(intersection_set) # 出力: {2, 3}
注意:`.union()` や `.intersection()` などのメソッドを使用する場合、集合に限らず任意のイテラブルを渡すことができます。一方、演算子(`|`、`&`)は両方のオペランドが集合である必要があります。
差集合と対称差
差集合
2つの集合 `s1` と `s2` の差集合は、`s1` にあって `s2` にない要素の集合です。
`-` 演算子の使用:
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5}
difference_set = s1 - s2
print(difference_set) # 出力: {1, 2}
`.difference()` メソッドの使用:
difference_set = s1.difference(s2)
print(difference_set) # 出力: {1, 2}
複数の集合との使用:
s3 = {1}
difference_set = s1.difference(s2, s3)
print(difference_set) # 出力: {2}
重要:
差集合演算は可換ではありません:`s1 - s2` は `s2 - s1` と同じではありません。
差集合演算は左結合的です:`s1 - s2 - s3` は `(s1 - s2) - s3` と等しくなります。
順序に注意:
s1 = {1, 2, 3}
s2 = {2, 3}
s3 = {3}
result1 = s1 - (s2 - s3)
print(result1) # 出力: {1, 3}
result2 = (s1 - s2) - s3
print(result2) # 出力: {1}
対称差
2つの集合の対称差は、どちらか一方の集合にあり、両方には存在しない要素を含みます。
`^` 演算子の使用:
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}
sym_diff_set = s1 ^ s2
print(sym_diff_set) # 出力: {1, 2, 5, 6}
`.symmetric_difference()` メソッドの使用:
sym_diff_set = s1.symmetric_difference(s2)
print(sym_diff_set) # 出力: {1, 2, 5, 6}
注意:対称差は可換的です:`s1 ^ s2` は `s2 ^ s1` と等しくなります。
集合の包含関係
集合の包含関係では、ある集合が別の集合のサブセット(部分集合)またはスーパーセット(上位集合)であるかどうかを確認できます。
サブセット(`<=`):`s1` のすべての要素が `s2` に含まれているかどうかを確認します。
s1 = {1, 2}
s2 = {1, 2, 3}
print(s1 <= s2) # 出力: True
print(s1.issubset(s2)) # 出力: True
真部分集合(`<`):`s1` が `s2` のサブセットであり、`s2` と等しくないことを確認します。
print(s1 < s2) # 出力: True
スーパーセット(`>=`):`s1` が `s2` のすべての要素を含んでいるかどうかを確認します。
s3 = {1, 2, 3, 4}
print(s3 >= s2) # 出力: True
print(s3.issuperset(s2)) # 出力: True
真上位集合(`>`):`s1` が `s2` のスーパーセットであり、`s2` と等しくないことを確認します。
print(s3 > s2) # 出力: True
注意:
包含関係の演算は、数値の比較のように直感的ではない場合があります。
例えば:
s1 = {1, 2, 3}
s2 = {4, 5, 6}
print(s1 <= s2) # 出力: False
print(s1 >= s2) # 出力: False
更新演算による集合の変更
和集合や積集合などの集合演算は新しい集合を返しますが、場合によっては元の集合をその場で変更したいことがあります。Python はこの目的のために更新メソッドと演算子を提供しています。
更新メソッドと演算子
和集合更新(`|=`):
s1 = {1, 2, 3}
s2 = {4, 5}
s1 |= s2
print(s1) # 出力: {1, 2, 3, 4, 5}
またはメソッドを使用:
s1.update(s2)
積集合更新(`&=`):
s1 = {1, 2, 3}
s2 = {2, 3, 4}
s1 &= s2
print(s1) # 出力: {2, 3}
またはメソッドを使用:
s1.intersection_update(s2)
差集合更新(`-=`):
s1 = {1, 2, 3, 4}
s2 = {2, 3}
s1 -= s2
print(s1) # 出力: {1, 4}
またはメソッドを使用:
s1.difference_update(s2)
対称差更新(`^=`):
s1 = {1, 2, 3, 4}
s2 = {3, 4, 5}
s1 ^= s2
print(s1) # 出力: {1, 2, 5}
またはメソッドを使用:
s1.symmetric_difference_update(s2)
演算の順序
重要:
更新演算の右辺(RHS)が最初に評価されます。
差集合更新では注意が必要です:
s1 = {1, 2, 3, 4}
s2 = {2, 3}
s3 = {3, 4}
s1 -= s2 - s3 # 最初に s2 - s3 を評価し、次に s1 - 結果を評価
print(s1) # 出力: {1, 3, 4}
メソッドを使用すると異なる結果になる場合があります:
s1 = {1, 2, 3, 4}
s1.difference_update(s2, s3) # ((s1 - s2) - s3) と同等
print(s1) # 出力: {1}
実践的な例
例1:単語のフィルタリング
複数の文章から一意な単語をすべて収集し、一般的なストップワードを除外したい場合を考えてみましょう。
def combine_sentences(target_set, *sentences):
for sentence in sentences:
target_set.update(sentence.split())
def remove_stop_words(target_set, stop_words):
target_set.difference_update(stop_words)
result = set()
combine_sentences(
result,
"lumberjacks sleep all night",
"the mystery of silly walks",
"this parrot is a late parrot"
)
stop_words = {"the", "and", "is", "a", "of", "or"}
remove_stop_words(result, stop_words)
print(result)
出力:
{'parrot', 'this', 'walks', 'mystery', 'late', 'lumberjacks', 'night', 'silly', 'sleep', 'all'}
例2:フィルター付きデータ収集
APIからページ分割されたデータ(例:都市名)を取得し、不要なエントリをフィルタリングする場合を想定してみましょう。
データソースのシミュレーション:
def fetch_data():
yield ['Paris', 'Beijing', 'New York', 'London', 'Madrid', 'Mumbai']
yield ['Hyderabad', 'New York', 'Milan', 'Phoenix', 'Berlin', 'Cairo']
yield ['Stockholm', 'Cairo', 'Paris', 'Barcelona', 'San Francisco']
データ収集とフィルタリング:
def filter_cities(target_set, *cities_to_remove):
target_set.difference_update(cities_to_remove)
result = set()
data_source = fetch_data()
for page in data_source:
result.update(page)
filter_cities(result, 'Paris', 'London')
print(result)
出力:
{'Hyderabad', 'New York', 'Phoenix', 'San Francisco', 'Barcelona', 'Mumbai', 'Stockholm', 'Cairo', 'Madrid', 'Milan', 'Beijing', 'Berlin'}
結論
Python における高度な集合演算を理解することで、一意な要素のコレクションを扱う一般的なプログラミングタスクに対して、より効率的でエレガントな解決策を提供することができます。数学的な演算を実行する場合でも、データをフィルタリングする場合でも、その場で集合を操作する場合でも、これらのツールは、クリーンで効率的なコードを書く能力を向上させます。
重要なポイント:
イテラブルとの柔軟性が必要な場合はメソッドを使用します。
演算子は簡潔ですが、両方のオペランドが集合である必要があります。
特に差集合演算では、演算の順序と結合性に注意を払います。
更新メソッドによる集合の変更は、パフォーマンスとメモリ使用量を改善できます。
これらの高度な集合演算を習得することで、より熟練した Python 開発者への道を着実に進んでいます。ハッピーコーディング!