LINQで使える集合演算のExceptが思っていた動きと少し違ったので記録しておく

2020年7月19日

こちらAtcoderの過去問を解いていた時に気づいた問題です。
以下問題文を上記のリンクから確認されたことを前提に話を進めます。

Exceptは便利で、ある集合からの差集合を求める時に使います。
っで、今回はある文字列から一部の文字列の差集合を求めることにしました。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using Console = System.Console;

namespace Atcoder
{
    
    class Program
    {
        private const long COMDIV = 1000000007;

        static void Main(string[] args)
        {
            var except = "aiueo";
            var W = Console.ReadLine();
            Console.WriteLine(new string(W.Except(except).ToArray()));
        }
    }
}

上記で問題ないかなと思いましたが、 これだと
aokkksslというようなパターンだと上手くいかないようです。
期待した結果は、 kkksslなのですが、出力される結果はkslでした。

結果から見ると、ExceptにはDistinctも含めるような処理もあるのかと思い、調べてみるとこちらにいきつきました。
最後の部分だけ訳すと、Except はセットベースの操作なので、結果として得られる値をDistinctする効果もあります。とのことでした。

公式のEnumerable.Except Methodを確認してみると、Remarksに以下のように書かれていました。

2つの集合の集合差は、2つ目の集合に現れない1つ目の集合のメンバーとして定義されます。
このメソッドは、2番目の集合に現れない1番目の要素を返します。2 番目にある要素のうち、1 番目に現れない要素は返されません。一意な要素のみが返されます。

このように、よくExceptはもとになる集合に重複する要素がある場合は、一意にして返すというような動きがあるようです。
というわけで、念のためintで実験。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using Console = System.Console;

namespace Atcoder
{

    class Program
    {
        private const long COMDIV = 1000000007;

        static void Main(string[] args)
        {
            List<int> baseSet = new List<int>() { 1, 1, 3, 2, 5, 4, 4, 3,6,7,8,9 };
            List<int> except = new List<int>() { 8,9 };
            Console.WriteLine(string.Join(",",baseSet.Except(except).ToList()));

        }
    }
}

結果は、1,3,2,5,4,6,7となっており、重複が排除された形となっていました。

結局以下のようにして問題を解きました。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using Console = System.Console;

namespace Atcoder
{

    class Program
    {
        private const long COMDIV = 1000000007;

        static void Main(string[] args)
        {
            HashSet<char> except = new HashSet<char>(){'a', 'i' , 'u' , 'e' , 'o' };
            var W = Console.ReadLine().ToList();
            StringBuilder ret = new StringBuilder();
            foreach (var c in W)
            {
                if (!except.Contains(c)) ret.Append(c);
            }
            Console.WriteLine(ret.ToString());

        }
    }
}