【C#】和集合、積集合、差集合、対象差を求める

# 集合演算用のLINQのメソッド

* LINQで使える集合演算用のメソッドは、Union、Intersect、Exceptの3つです。
* Concatメソッドは、ここでは「和集合（重複データも含まれる）を求める」としていますが、[Microsoftのリファレンス](https://docs.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.concat?view=netframework-4.7.2)を確認すると、「2つのリストを結合する」という処理になっているので、厳密には集合演算用のメソッドとは言えないと思います。
* SQLが分かる方は、LINQのメソッドをSQLの集合演算と比較した方が理解しやすいかもしれません。
 * ちなみに、INTERSECT、EXCEPT、MINUSは一部のRDBMSでしか使えません。（[※参考資料](http://www.sql-reference.com/index.html#collection)）
     * INTERSECT：Oracle、PostgreSQL
     * EXCEPT：PostgreSQL
     * MINUS：Oracle

|LINQのメソッド|処理内容|対応するSQLの集合演算|
|---|---|---|
|Union|和集合（重複データは除かれる）を求める|UNION|
|Concat|和集合（重複データも含まれる）を求める|UNION ALL|
|Intersect|積集合を求める|INTERSECT|
|Except|差集合を求める|EXCEPT, MINUS|


# テストコード1

* 都市の名前が入った2つのリスト（list1, list2）を用意して、それらのリストの和集合、積集合、差集合を求める処理です。

```c#:UnitTestSetOperation.cs
[TestClass]
public class UnitTestSetOperation
{

    [TestMethod]
    public void TestMethod1()
    {
        List<string> list1 = new List<string>() {"Tokyo", "Osaka", "Yokohama", "Nagoya", "Kobe"};

        List<string> list2 = new List<string>() {"Tokyo", "Yokohama", "Sapporo", "Fukuoka"};

        // 和集合（重複データは除かれる）
        System.Console.WriteLine("和集合(Union)：" + string.Join(",", list1.Union(list2)));

        // 和集合（重複データも含まれる）
        System.Console.WriteLine("和集合(Concat)：" + string.Join(",", list1.Concat(list2)));

        // 積集合
        System.Console.WriteLine("積集合(Intersect)：" + string.Join(",", list1.Intersect(list2)));

        // 差集合
        System.Console.WriteLine("差集合(Except)[list1-list2]：" + string.Join(",", list1.Except(list2)));
        System.Console.WriteLine("差集合(Except)[list2-list1]：" + string.Join(",", list2.Except(list1)));
    }
}
```

# テストコード1の実行結果

* Exceptメソッドは「レシーバから、引数として指定した変数の内容を引く」という処理なので、レシーバと引数のどちらに設定するかによって結果が変化します。

```text:実行結果
和集合(Union)：Tokyo,Osaka,Yokohama,Nagoya,Kobe,Sapporo,Fukuoka
和集合(Concat)：Tokyo,Osaka,Yokohama,Nagoya,Kobe,Tokyo,Yokohama,Sapporo,Fukuoka
積集合(Intersect)：Tokyo,Yokohama
差集合(Except)[list1-list2]：Osaka,Nagoya,Kobe
差集合(Except)[list2-list1]：Sapporo,Fukuoka
```

# テストコード2

* 都市の名前が入った2つのリスト（list1, list2）を用意して、それらのリストの和集合を作った後、都市名の昇順（あるいは降順）に並べ替える処理です。
 * 単なる「リストに対する集合演算」よりも、このような「集合演算＋並べ替え」などの複合的な処理が行われるシーンの方が多いかもしれません。

```c#:UnitTestSetOperation.cs
[TestMethod]
public void TestMethod2()
{
    List<string> list1 = new List<string>() { "Tokyo", "Osaka", "Yokohama", "Nagoya", "Kobe" };

    List<string> list2 = new List<string>() { "Tokyo", "Yokohama", "Sapporo", "Fukuoka" };

    // 和集合（重複データは除かれる）を求めた後、文字列の昇順にソートする。
    System.Console.WriteLine("和集合＋昇順：" + string.Join(",", list1.Union(list2).OrderBy(cityName=>cityName)));

    // 和集合（重複データは除かれる）を求めた後、文字列の降順にソートする。
    System.Console.WriteLine("和集合＋降順：" + string.Join(",", list1.Union(list2).OrderByDescending(cityName => cityName)));
}
```

# テストコード2の実行結果

```text:実行結果
和集合＋昇順：Fukuoka,Kobe,Nagoya,Osaka,Sapporo,Tokyo,Yokohama
和集合＋降順：Yokohama,Tokyo,Sapporo,Osaka,Nagoya,Kobe,Fukuoka
```

# テストコード3

* 「対象差（対象差集合）」とは「Aに属し、Bに属さないもの」と「Bに属し、Aに属さないもの」の両方を合わせた集合なので、和集合と差集合を組み合わせて以下のように実装してみました。
  * 対象差をベン図で描くと、排他的論理和と同じ領域で表されます。

```c#:UnitTestSetOperation.cs
[TestClass]
public class UnitTestSetOperation
{
    [TestMethod]
    public void TestMethod3()
    {
        List<string> list1 = new List<string>() { "Tokyo", "Osaka", "Yokohama", "Nagoya", "Kobe" };

        List<string> list2 = new List<string>() { "Tokyo", "Yokohama", "Sapporo", "Fukuoka" };

        // 対象差
        var set = list1.Except(list2).Union(list2.Except(list1));
        System.Console.WriteLine("対象差：" + string.Join(",", set));
    }
}
```

```text:実行結果
対象差：Osaka,Nagoya,Kobe,Sapporo,Fukuoka
```

# 参考URL

* [対称差 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%AF%BE%E7%A7%B0%E5%B7%AE)