Interactive Extensionsを自分で実装する – 6.Distinct

Interactive Extensions(Ix)を自分で実装する、第6回目はDistinctです。

標準のクエリ演算子の中にもDistinctはありますが、IxにあるDistinctとは少しシグネチャが違います。
標準のクエリ演算子のDistinct:

public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource>);

InteractiveExtensionsのDistinct:

public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer);

ややこしく見えますが、Ixの2つのDitinctは標準の2つのDistinctに比べてそれぞれ

Func<TSource, TKey> keySelector

が追加になっているだけです。

使用例1

static IEnumerable<int> GetDistinctSequence()
{
    yield return 1;
    yield return 4;
    yield return 2;
    yield return 4;
    yield return 1;
}
static void DistinctTest1()
{
    var sequence = GetDistinctSequence();
    var result = sequence.Distinct(a => a % 2); // 2の剰余をkeyにする
    foreach (var item in result)
    {
        Console.WriteLine(item);
    }
}

distinct_1
ソースに {1,4,2,4,1} というシーケンスを設定しました。このまま標準のDistinct(重複排除)をすると、{1,4,2}となります。 しかしここではシーケンスの剰余が一致するかで比較するようにしました。2の剰余は{1,0,0,0,1}となりますので重複排除すると1つめのアイテムと2つめのアイテムだけが残ります。 そのため出力は{1,4}となるわけです。

使用例2

static void DistinctTest2()
{
    string[] distinctTestArray = new[]{
        "tocs",
        "Tocs",
        "tOES",
        "TOCS",
        "toGs",
        "tocs"
    };
    var sequence = Enumerable.Range(0, 6);
    var distinctWithComparer = sequence.Distinct(
        a => distinctTestArray[a],                  // int => stringのFunc
        StringComparer.InvariantCultureIgnoreCase); // 比較時は大文字小文字気にしない
    foreach (var item in distinctWithComparer){
        Console.WriteLine(item);
    }
}

distinct_2
まどろっこしい例ですみません。重複評価対象にしようとしているシーケンスはdistinctTestArrayです。ただ、IxのDistinctはFunc<TSource, TKey>を取るのでムリヤリこれを使おうとすると、何かの出力がstringにならないといけないので元のシーケンスはEnumerable.Range(0,6)で{0,1,2,3,4,5}のシーケンスをひねり出しました。{0,1,2,3,4,5}のムリヤリ作ったシーケンスからFuncを介してdistinctTestArrayができます。

そのあと、これを渡したIEqualityComparerで評価するわけですが.NETに用意されているStringComparerをそのまま拝借して、大文字小文字の区別なしでの比較をさせています。そうすると、{“tocs”, “Tocs”, “toES”, “TOCS”, “toGs”, “tocs”}のうち、tocsが重複していますのでそれを排除した{“tocs”, “toES, “toGs}が残ります。ただこれはint => stringのFuncを介して作られたものなので、最終的に戻ってくるのはint値で、{0, 2, 4}となるわけです。標準クエリのDistictは使う頻度そこそこ出てきますが、IxのDistinctオーバーロードを使うのは1年に1回あるかないか・・

それでは実装してみます。(Let’s Implement It!)

Distinct…シーケンスの中から指定した一致判断方法で重複を排除したシーケンスを返す。

namespace EmulateInteractiveExtensions
{
    public static class EmulateIxExtensions
    {
        public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source,
                                                                   Func<TSource, TKey> keySelector)
        {
            var comparer = EqualityComparer<TKey>.Default;
            return source.Distinct(keySelector, comparer);
        }
        public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source,
                                                                   Func<TSource, TKey> keySelector,
                                                                   IEqualityComparer<TKey> comparer)
        {
            if (source == null || keySelector == null || comparer == null){
                throw new ArgumentNullException();
            }

            List<TKey> alreadyAppeared = new List<TKey>();
            foreach (var item in source)
            {
                bool exist = false;
                TKey thisKey = keySelector(item);
                foreach (var savedKey in alreadyAppeared){
                    if (comparer.Equals(thisKey, savedKey)){
                        exist = true;
                        break;
                    }
                }
                if (!exist){
                    alreadyAppeared.Add(thisKey);
                    yield return item;
                }
            }
        }
    }
}

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中