集合の基礎部分を絡めたSymPy操作

集合のごくごく基礎（表面的なところ）を勉強・復習を兼ねてPythonとSymPyの各インターフェイスで扱っていきます。

※記事執筆者は理系出身ではなく数学や用語などで色々粗い点等はご容赦ください。

# 使うもの

- Python 3.9.0 （一部3.8や3.9など特有の型アノテーションの機能も使っています）
- SymPy 1.9
- Jupyter (VS Code上のものを利用)

※SymPyは以下のようなコマンドでインストールすることができます。

```
$ pip install sympy==1.9
```

また、SymPyの基本に関しては以前記事にしたのでそちらも必要に応じてご確認ください。

https://qiita.com/simonritchie/items/44242479f0df86fffff5

# 集合のPythonビルトイン上での扱い方

Pythonビルトインでは集合を表すには`{}`の括弧を使います。辞書と同じ括弧ですが、（辞書のキーと値といったように）コロンは使わずにコンマ区切りで表現します。

たとえば10, 20, 30の3つの値からなる集合を作るには以下のように書きます。

```py
set_value: set = {10, 20, 30}
print(set_value)
```

```
{10, 20, 30}
```

他の値、例えばリストなどから集合に変換するにはsetクラスを使ってキャストすると対応ができます。集合は同じ値を複数持たないため、もしリストなどの中に同じ値が含まれていれば1つのみ値が残ります。これを利用して集合をさらにリストなどに変換してビルトインのリストを一意（ユニーク）な値のみのリストに変換する際などにも（NumPyなどを通さない場合等に）利用されたりもします。

以下の例では10の値が複数含まれるリストを集合に変換していますが、結果的に10の値が1つのみ残っていることが確認できます。

```py
set_value: set = set([10, 10, 10, 20, 30])
print(set_value)
```

```
{10, 20, 30}
```

# SymPyでの集合の扱い方

SymPyではFiniteSetクラスを使います。コンストラクタに集合に設定する各値を指定します。

```py
from sympy import FiniteSet

set_value: FiniteSet = FiniteSet(10, 20, 30)
```

Jupyter上で出力してみると以下のように表示されます。

```py
set_value
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/3ae3eeeb-fb8a-ea25-b9a5-f42413785c23.png)

ビルトインのsetクラスと同様にFiniteSetクラスでも重複している値は取り除かれ一意になります。

```py
set_value: FiniteSet = FiniteSet(10, 10, 10, 20, 30)
set_value
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/4e2cd0fe-aa5b-f344-aeed-ee2ece2d1c68.png)

複数の型（整数と浮動小数点数など）を含めたり、分数（Fractionクラス）の値などを含めたりもできます。余談ですが、SymPyでは他の数式表現などと同様に値や要素の順番は担保されません（ルールに従って順番が変わったりします）。以下の例では小さい値ほど前の方に配置されていることが確認できます。

```py
from fractions import Fraction

set_value: FiniteSet = FiniteSet(10, 10.5, Fraction(2, 3), 5)
set_value
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/47864da2-5d61-5410-a815-7fb3b015de1b.png)

# 集合内に特定の値が含まれているかを調べる

ビルトインのsetでもSymPyのFiniteSetでもどちらもinを使って集合内に特定の値が含まれているかどうかを調べることができます（ただしFiniteSetの方はPylanceの型チェックなどでは引っかかるようで・・・SymPyの影響なので動作自体は問題無く動作します）。

```py
set_value: set = {10, 20, 30}
20 in set_value
```

```
True
```

```py
40 in set_value
```

```
False
```

```py
set_value: FiniteSet = FiniteSet(10, 20, 30)
20 in set_value
```

```
True
```

```py
40 in set_value
```

```
False
```

# リストやタプルなどからFiniteSetを作る

ビルトインのリストやタプルなどからFiniteSetを作りたい場合にはPythonの言語仕様的に`*`のアスタリスク記号をリストなどの前に配置して引数に渡すと各引数に位置引数として展開してくれるので対応ができます。

```py
list_value: list[int] = [10, 20, 30]
set_value: FiniteSet = FiniteSet(*list_value)
set_value
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/39cbfafc-0b65-7843-901c-38e967140517.png)

# 空のFiniteSet

空の集合のインスタンスを作りたい場合にはFiniteSetのコンストラクタで引数を指定せずにインスタンスを作成します。Jupyter上で表示してみると∅という表示になります。0に見えますが0ではなく∅です。

```py
set_value: FiniteSet = FiniteSet()
set_value
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/482bc6e5-ae5b-902d-693c-75532604687a.png)

# FiniteSetに対してループを回す

FiniteSetのインスタンスはそのままループでイテレーションを回すことができます。ただし前述の通り引数指定時と比べて順番が変わったりするケースもあるため、ループで渡される各値の順番には留意する必要があります。

```py
set_value: FiniteSet = FiniteSet(3, 2, 1)
for value in set_value:
    print(value)
```

```
1
2
3
```

# FiniteSetの比較

FiniteSetはFiniteSet同士で比較演算子（==や!=など）を使うことができます。

```py
set_value_1: FiniteSet = FiniteSet(1, 2, 3)
set_value_2: FiniteSet = FiniteSet(1, 2, 3)

set_value_1 == set_value_2
```

```
True
```

```py
set_value_1: FiniteSet = FiniteSet(1, 2, 3)
set_value_2: FiniteSet = FiniteSet(1, 2)

set_value_1 == set_value_2
```

```
False
```

リストやタプル等とは異なりコンストラクタの引数で値の順番が異なっていても一致します。

```py
set_value_1: FiniteSet = FiniteSet(1, 2, 3)
set_value_2: FiniteSet = FiniteSet(3, 2, 1)

set_value_1 == set_value_2
```

```
True
```

# 部分集合の判定を行う

部分集合（subset / 下位集合）かどうかの判定をしたい場合にはissubsetメソッドで真偽値を取ることができます。

例えば集合Aが集合Bの部分集合（$A ⊆ B$）かどうかの判定は以下のように行います。

```py
set_A: FiniteSet = FiniteSet(1, 2)
set_B: FiniteSet = FiniteSet(1, 2, 3)
set_A.issubset(set_B)
```

```
True
```

```py
set_A: FiniteSet = FiniteSet(1, 4)
set_B: FiniteSet = FiniteSet(1, 2, 3)
set_A.issubset(set_B)
```

```
False
```

なお、このメソッドは各集合の値が同じでもTrueとなります。値が同じ場合には真部分集合の判定が必要となり別のメソッドを使うことが必要になります（後の節で触れます）。

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(1, 2, 3)
set_A.issubset(set_B)
```

```
True
```

# 上位集合の判定を行う

上位集合（superset）の判定を行うにはissupersetメソッドを使います。部分集合（issubset）と逆の挙動をします。

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(1, 2)
set_A.issuperset(set_B)
```

```
True
```

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(1, 4)
set_A.issuperset(set_B)
```

```
False
```

# 真部分集合かどうかの判定を行う

部分集合の条件に加えて2つの集合の値が一致していない条件となる真部分集合（proper subset）かどうかの判定を行うにはis_proper_subsetメソッドを使います。

例えば集合Aが集合Bの真部分集合（$A ⊆ B$ 且つ $A ≠ B$）かどうかを判定したい場合には以下のように書きます。

```py
set_A: FiniteSet = FiniteSet(1, 2)
set_B: FiniteSet = FiniteSet(1, 2, 3)
set_A.is_proper_subset(set_B)
```

```
True
```

通常の部分集合と異なり2つの集合の値が一致している場合はFalseとなります。

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(1, 2, 3)
set_A.is_proper_subset(set_B)
```

```
False
```

# 真上位集合かどうかの判定を行う

真部分集合の逆の処理となる真上位集合（proper superset）かどうかの判定を行うにはis_proper_supersetメソッドを使います。

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(1, 2)
set_A.is_proper_superset(set_B)
```

```
True
```

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(1, 2, 3)
set_A.is_proper_superset(set_B)
```

```
False
```

# FiniteSetの冪集合を求める

FiniteSetの集合の冪集合（power set）を求めるにはpowersetメソッドを使います。例えば`{1, 2, 3}`という集合に対して使うと空の集合（
∅）、`{1}`, `{2}`, , ..., `{1, 3}` ..., `{1, 2, 3}`といったように各集合の値が作られます。

```py
set_value: FiniteSet = FiniteSet(1, 2, 3)
power_set_value = set_value.powerset()
power_set_value
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f724f232-6724-f67a-b406-171d537342f0.png)

作成される集合の件数は、元の集合の値の件数を$|s|$とすると$2^{|s|}$となります。つまり元の集合の値の件数が3であれば$2^3 = 8$となり、元の集合の件数が4であれば$2^4 = 16$となります。

```py
set_value: FiniteSet = FiniteSet(1, 2, 3)
power_set_value = set_value.powerset()
len(power_set_value)
```

```
8
```

```py
set_value: FiniteSet = FiniteSet(1, 2, 3, 4)
power_set_value = set_value.powerset()
len(power_set_value)
```

```
16
```

# FiniteSetの和集合を求める

FiniteSetの和集合を求めるにはunionメソッドを使います。2つの集合をOR条件で統合したような集合を得ることができます（どちらかの集合に含まれる値は結果の集合に残ります）。数式上だと$∪$の記号を使って$A ∪ B$といったような表記がされたりします。

例えば`{1, 2}`という値を持つ集合Aと`{2, 3}`という値を持つ集合Bの和集合は`{1, 2, 3}`という結果になります。

```py
set_A: FiniteSet = FiniteSet(1, 2)
set_B: FiniteSet = FiniteSet(2, 3)
union_set = set_A.union(set_B)
union_set
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/2d291857-0fa4-3b48-0acb-cef81c19e066.png)

# FiniteSetの積集合を求める

FiniteSetの積集合（交差集合）を求めるにはintersectメソッドを使います。AND条件で統合したような集合を得ることができます（両方の集合に含まれる値のみ結果の集合に残ります）。数式上だと$∩$の記号を使って$A ∩ B$といったような表記がされたりします。

例えば`{1, 2, 3}`という値を持つ集合Aと`{2, 3, 4}`という値を持つ集合Bの積集合は`{2, 3}`となります。

```py
set_A: FiniteSet = FiniteSet(1, 2, 3)
set_B: FiniteSet = FiniteSet(2, 3, 4)
intersection_set = set_A.intersect(set_B)
intersection_set
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c8b1e079-b80c-0f9d-9a51-a2cabda47799.png)

# デカルト積を求める

2つの集合のデカルト積（Cartesian Product）を求めるにはそれぞれの集合を`*`の記号で計算すれば対応ができます。例えば`{1, 2}`という集合と`{3, 4}`という集合の2つのデカルト積を求めると`(1, 3), (1, 4), (2, 3), (2, 4)`という4つのタプルが得られます。

```py
set_A: FiniteSet = FiniteSet(1, 2)
set_B: FiniteSet = FiniteSet(3, 4)
cartesian_prod_set = set_A * set_B
cartesian_prod_set
```

Jupyter上では×の記号を使った表記になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/e8e7c052-dcba-d887-4627-227cf071af15.png)

値はループを回すことができ、それぞれの値をタプルで得ることができます。

```py
for tuple_val in cartesian_prod_set:
    print(tuple_val)
```

```
(1, 3)
(2, 3)
(1, 4)
(2, 4)
```

# 参考文献・参考サイトまとめ

https://www.amazon.com/Doing-Math-Python-Programming-Statistics/dp/1593276400/

https://qiita.com/simonritchie/items/44242479f0df86fffff5

https://ja.wikipedia.org/wiki/%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88

https://atmarkit.itmedia.co.jp/ait/articles/1906/25/news015_2.html

https://mathlandscape.com/power-set/

https://note.nkmk.me/python-set/