ScalaでCollection内オブジェクトの任意の条件で集合演算をする

動機

Collection内のObjectのKeyだけ比較して集合演算したい!みたいなことって割とあると思います。

試してみた

keyvalueをもつcase classを定義し、keyの値で集合演算をしたい場合で考えます。
何も考えずにやると以下のようにうまくいきません。
list内の Obj(b,1) と del内の Obj(b,2) を同じオブジェクトと判定できず除外してくれません。

case class Obj(key: String, value: Int)

val list = List(
  Obj("a", 1),
  Obj("b", 2),
  Obj("c", 3)
)

val del = List(
  Obj("b", 11),
  Obj("b", 12),
  Obj("d", 13)
)

list diff del // => res0: List[Obj] = List(Obj(a,1), Obj(b,2), Obj(c,3))

直してみた

ObjequalshashCode をoverrideしてやればうまくいきます。良かったですね。
(equals内の実装は適当なのでプロダクションに入れるときはinstanceが異なる場合なども考慮してちゃんと実装しましょう)

case class Obj(key: String, value: Int) {
  override def equals(arg0: Any): Boolean = {
    key == arg0.asInstanceOf[Obj].key
  }

  override def hashCode(): Int = {
    key.hashCode
  }
}

val list = List(
  Obj("a", 1),
  Obj("b", 2),
  Obj("c", 3)
)

val del = List(
  Obj("b", 11),
  Obj("b", 12),
  Obj("d", 13)
)

list diff del // => res0: List[Obj] = List(Obj(a,1), Obj(c,3))

説明

Javaオブジェクトの比較には Object#equals, Object#hashCode を利用します。
Scalaでも同様にこれらのメソッドを任意に書き換えてあげれば良いようです。
今回は diff でしか検証してませんが、恐らく他の集合演算メソッドでも同様だと思います。

追記

コメントでご指摘頂いたとおり、case classのequals(), hashCode() をoverrideするのは注意が必要です。
特に理由が無ければ別の方法を取るべきだと思います。詳細はコメントを御覧ください。

以上です。