【初心者向け】Pythonのset(集合)入門|重複排除から集合演算まで

その他

「リストの中から重複をなくしたい」
「AとBに共通する要素だけを取り出したい」

そんな疑問を解決するために、今回はset(セット・集合)について、初心者の方でも理解できるように詳しく説明していきます。

スポンサーリンク

setとは?「重複を許さない集合」のこと

setは、数学の集合と同じように、要素の重複を許さず、順番を持たないデータ構造です。

setの特徴

  • 重複なし:同じ要素は1つしか持てない
  • 順序なし:要素の順番は保証されない
  • 変更可能:要素の追加・削除ができる
  • 高速検索:要素の存在確認が非常に速い

基本的な作り方

# 波括弧{}を使って作成
fruits = {"りんご", "バナナ", "みかん"}
print(fruits)  # {'みかん', 'バナナ', 'りんご'} ※順番は不定

# set()関数を使って作成
numbers = set([1, 2, 3, 4, 5])
print(numbers)  # {1, 2, 3, 4, 5}

# 空のsetを作成
empty_set = set()  # 注意:{}は辞書になる
print(empty_set)   # set()

リストとsetの変換:重複を簡単に除去

リストからsetへ(重複除去)

# 重複のあるリスト
numbers = [1, 2, 2, 3, 3, 3, 4, 5]
print("元のリスト:", numbers)  # [1, 2, 2, 3, 3, 3, 4, 5]

# setに変換(重複が自動で除去される)
unique_numbers = set(numbers)
print("setに変換:", unique_numbers)  # {1, 2, 3, 4, 5}

# 実用的な例:名前の重複を除去
names = ["太郎", "花子", "太郎", "次郎", "花子", "美香"]
unique_names = set(names)
print("重複除去後:", unique_names)  # {'次郎', '美香', '太郎', '花子'}

setからリストへ(順序が必要な場合)

fruits_set = {"りんご", "バナナ", "みかん"}

# リストに戻す
fruits_list = list(fruits_set)
print(fruits_list)  # ['みかん', 'バナナ', 'りんご'] ※順番は不定

# ソートしてから戻す
sorted_fruits = sorted(fruits_set)
print(sorted_fruits)  # ['みかん', 'りんご', 'バナナ'] ※あいうえお順

setに要素を追加・削除する方法

要素を追加する

fruits = {"りんご", "バナナ"}
print("最初:", fruits)  # {'バナナ', 'りんご'}

# 1つずつ追加
fruits.add("みかん")
print("追加後:", fruits)  # {'バナナ', 'みかん', 'りんご'}

# 同じ要素を追加しても重複しない
fruits.add("りんご")  # 既に存在する
print("重複追加後:", fruits)  # {'バナナ', 'みかん', 'りんご'} ※変化なし

add()の引数に追加したい要素を指定。

複数の要素を一度に追加する

fruits = {"りんご", "バナナ"}

# update()でリストから複数追加
fruits.update(["ぶどう", "もも", "すいか"])
print(fruits)  # {'すいか', 'もも', 'ぶどう', 'バナナ', 'りんご'}

# 文字列からも追加できる(1文字ずつ追加される)
letters = set()
letters.update("python")
print(letters)  # {'y', 'h', 'n', 'p', 't', 'o'}

要素を削除する

fruits = {"りんご", "バナナ", "みかん", "ぶどう"}

# remove():存在しない場合はエラー
fruits.remove("バナナ")
print(fruits)  # {'ぶどう', 'みかん', 'りんご'}

# discard():存在しなくてもエラーにならない
fruits.discard("なし")  # 存在しないがエラーなし
print(fruits)  # {'ぶどう', 'みかん', 'りんご'}

# pop():ランダムに1つ削除して返す
removed = fruits.pop()
print(f"削除された要素: {removed}")
print(f"残りの要素: {fruits}")

# clear():すべての要素を削除
fruits.clear()
print(fruits)  # set()

remove()の引数に追加したい要素を指定。

集合演算:setの最も強力な機能

setの最大の特徴は、数学の集合演算が簡単にできることです。

和集合(union):2つのsetを合体

group_a = {"太郎", "花子", "次郎"}
group_b = {"花子", "美香", "健太"}

# 方法1:|演算子を使用
all_members = group_a | group_b
print("全メンバー:", all_members)  # {'健太', '太郎', '次郎', '美香', '花子'}

# 方法2:union()メソッドを使用
all_members2 = group_a.union(group_b)
print("全メンバー:", all_members2)  # 同じ結果

積集合(intersection):共通する要素だけ

course_a = {"太郎", "花子", "次郎", "美香"}
course_b = {"花子", "次郎", "健太", "さくら"}

# 方法1:&演算子を使用
both_courses = course_a & course_b
print("両方受講:", both_courses)  # {'次郎', '花子'}

# 方法2:intersection()メソッドを使用
both_courses2 = course_a.intersection(course_b)
print("両方受講:", both_courses2)  # 同じ結果

差集合(difference):片方にだけある要素

all_students = {"太郎", "花子", "次郎", "美香", "健太"}
absent_students = {"次郎", "健太"}

# 方法1:-演算子を使用
present_students = all_students - absent_students
print("出席者:", present_students)  # {'太郎', '美香', '花子'}

# 方法2:difference()メソッドを使用
present_students2 = all_students.difference(absent_students)
print("出席者:", present_students2)  # 同じ結果

対称差集合(symmetric difference):どちらか一方にだけある要素

team_a = {"太郎", "花子", "次郎"}
team_b = {"次郎", "美香", "健太"}

# 方法1:^演算子を使用
exclusive_members = team_a ^ team_b
print("どちらか一方のメンバー:", exclusive_members)  # {'太郎', '健太', '美香', '花子'}

# 方法2:symmetric_difference()メソッドを使用
exclusive_members2 = team_a.symmetric_difference(team_b)
print("どちらか一方のメンバー:", exclusive_members2)  # 同じ結果

実践的な使用例

重複データの除去と分析

# アンケートの回答データ
responses = [
    "Python", "Java", "Python", "C++", "JavaScript", 
    "Python", "Ruby", "Java", "Go", "Python"
]

# 重複を除去して回答の種類を確認
unique_responses = set(responses)
print(f"回答の種類: {len(unique_responses)}種類")
print(f"内容: {sorted(unique_responses)}")

# 各言語の人気度を計算
for language in unique_responses:
    count = responses.count(language)
    print(f"{language}: {count}票")

ユーザーの権限管理

# ユーザーの権限管理
admin_permissions = {"read", "write", "delete", "admin"}
editor_permissions = {"read", "write"}
viewer_permissions = {"read"}

def check_permissions(user_type, required_permission):
    """ユーザーが特定の権限を持っているかチェック"""
    if user_type == "admin":
        permissions = admin_permissions
    elif user_type == "editor":
        permissions = editor_permissions
    else:
        permissions = viewer_permissions
    
    return required_permission in permissions

# 使用例
print(check_permissions("editor", "write"))   # True
print(check_permissions("viewer", "delete"))  # False

# 追加の権限が必要な操作を確認
def get_missing_permissions(user_permissions, required_permissions):
    """不足している権限を返す"""
    return required_permissions - user_permissions

user_perms = {"read", "write"}
required_perms = {"read", "write", "delete", "admin"}
missing = get_missing_permissions(user_perms, required_perms)
print(f"不足している権限: {missing}")  # {'admin', 'delete'}

データベースの更新処理

# データベースの更新処理の例
existing_users = {"user1", "user2", "user3", "user4"}
new_user_list = {"user2", "user3", "user5", "user6"}

# 新規追加するユーザー
users_to_add = new_user_list - existing_users
print(f"追加するユーザー: {users_to_add}")  # {'user6', 'user5'}

# 削除するユーザー
users_to_remove = existing_users - new_user_list
print(f"削除するユーザー: {users_to_remove}")  # {'user1', 'user4'}

# 変更がないユーザー
unchanged_users = existing_users & new_user_list
print(f"変更なし: {unchanged_users}")  # {'user2', 'user3'}

文字列の文字種別分析

def analyze_text(text):
    """テキストの文字種別を分析する"""
    text_set = set(text.lower())
    
    vowels = set("aiueo")
    consonants = set("bcdfghjklmnpqrstvwxyz")
    digits = set("0123456789")
    
    found_vowels = text_set & vowels
    found_consonants = text_set & consonants
    found_digits = text_set & digits
    
    print(f"母音: {sorted(found_vowels)}")
    print(f"子音: {sorted(found_consonants)}")
    print(f"数字: {sorted(found_digits)}")
    
    # 特殊文字
    special_chars = text_set - vowels - consonants - digits - {' '}
    print(f"特殊文字: {sorted(special_chars)}")

# 使用例
analyze_text("Hello Python 2024!")

setの制限と注意点

ハッシュ可能な要素のみ追加可能

# 間違い:リストや辞書は追加できない
s = set()
# s.add([1, 2, 3])     # TypeError: unhashable type: 'list'
# s.add({"a": 1})      # TypeError: unhashable type: 'dict'

# 正しい:不変な型は追加可能
s.add(1)              # 数値
s.add("text")         # 文字列
s.add((1, 2, 3))      # タプル
print(s)              # {1, 'text', (1, 2, 3)}

順序は保証されない

# setは順序を保持しない
numbers = {3, 1, 4, 1, 5, 9, 2, 6}
print(numbers)  # 順序は不定

# 順序が必要な場合はリストに変換
sorted_numbers = sorted(numbers)
print(sorted_numbers)  # [1, 2, 3, 4, 5, 6, 9]

空のsetの作り方に注意

# 間違い:これは辞書になる
empty_dict = {}
print(type(empty_dict))  # <class 'dict'>

# 正しい:空のsetを作る
empty_set = set()
print(type(empty_set))   # <class 'set'>

setのパフォーマンス

setは要素の検索が非常に高速です。

import time

# 大量のデータを準備
large_list = list(range(100000))
large_set = set(range(100000))

# リストでの検索時間
start = time.time()
result = 99999 in large_list
list_time = time.time() - start

# setでの検索時間
start = time.time()
result = 99999 in large_set
set_time = time.time() - start

print(f"リストでの検索時間: {list_time:.6f}秒")
print(f"setでの検索時間: {set_time:.6f}秒")
print(f"setは約{list_time/set_time:.0f}倍高速")

よくある質問

Q:同じ要素を何度add()しても大丈夫?
A:はい、問題ありません。setでは重複は自動的に排除されます。

s = set()
s.add("apple")
s.add("apple")  # 重複
s.add("apple")  # 重複
print(s)  # {'apple'} ※1つだけ保持

Q:順番を保持したいsetのような機能はない?
A:Python 3.7以降では、辞書が挿入順序を保持するようになったので、辞書のキーを使って順序付きsetのような使い方ができます。

# 順序を保持する「setのような」使い方
ordered_set = {}
for item in ["c", "a", "b", "a", "c"]:
    ordered_set[item] = None

print(list(ordered_set.keys()))  # ['c', 'a', 'b'] ※挿入順序を保持

Q:frozensetとの違いは?
A:frozensetは不変(immutable)なsetです。作成後に要素の追加・削除ができません。

# 通常のset(変更可能)
normal_set = {1, 2, 3}
normal_set.add(4)  # OK

# frozenset(変更不可)
frozen_set = frozenset([1, 2, 3])
# frozen_set.add(4)  # AttributeError

Q:setの要素をfor文で取り出す順序は?
A:順序は保証されません。毎回異なる順序になる可能性があります。

s = {1, 2, 3, 4, 5}
for item in s:
    print(item)  # 順序は不定

まとめ

pythonのsetは、重複のないデータ管理と高速検索に最適なデータ構造です。

おさらい:

  • 重複排除:リストからsetに変換するだけで重複を除去
  • 集合演算:和集合(|)、積集合(&)、差集合(-)、対称差集合(^)
  • 高速検索:in演算子での検索がリストより圧倒的に高速
  • 制限事項:順序なし、ハッシュ可能な要素のみ

コメント

タイトルとURLをコピーしました