グループ化してセット演算子を適用する。

要素の同一性を利用するのはDistinctだけじゃなくてセット演算子全部なので、グループ化してセット演算しやすくするクラスを作ってみたけどなんか複雑になっちゃった。

■グループ化クラス

public class GroupUniverse<T, TKey>
{
  Func<T, TKey> keySelector;
  GroupSet set;

  public GroupUniverse(Func<T, TKey> keySelector)
  {
    this.keySelector = keySelector;
    this.set = new GroupSet(keySelector, Enumerable.Empty<T>());
  }

  public GroupSet Set { get { return this.set; } }

  public GroupSet Group(IEnumerable<T> source)
  {
    this.set.Append(source);
    return new GroupSet(this.keySelector, source);
  }

  public class GroupSet
  {
    Func<T, TKey> keySelector;
    ILookup<TKey, T> lookup;

    public GroupSet(Func<T, TKey> keySelector, IEnumerable<T> source)
    {
      this.lookup = source.ToLookup(keySelector);
      this.keySelector = keySelector;
    }

    public ILookup<TKey, T> Lookup { get { return this.lookup; } }

    public Func<T, TKey> KeySelector { get { return this.keySelector; } }

    public IEnumerable<TKey> Keys { get { return this.Lookup.Select(x => x.Key); }}

    public IEnumerable<T> Over()
    {
      return this.lookup.Select(x => x.First());
    }

    public void Append(IEnumerable<T> source)
    {
      this.lookup = this.lookup.SelectMany(x => x).Union(source).ToLookup(this.keySelector);
    }

    public GroupSet Intersect(IEnumerable<TKey> source)
    {
      return new GroupSet(this.keySelector, this.Lookup.Where(x => source.Contains(x.Key)).SelectMany(x => x));
    }
  }
}

■使い方

// a, bというシーケンスがある。Itemでグループ化してExceptする。
var s = new GroupUniverse<ClassX, int>(x => x.Item);
var ag = s.Group(a);
var bg = s.Group(b);
var temp = ag.Keys.Except(bg.Keys);
var dest = s.Set.Intersect(temp).Over();