Question

I have a map with values keyed by tuple. In my case I need tuples match irrespective of elements order so, for example, I need this to hold:

(1, 2) == (2, 1) is true

How to override tuple equals operator, so for any types X, Y :

(a:X, b:Y) == (b:Y, a:X) is true  ?   

Corrected later: Another idea would be to write my own Pair class with required behavior. How equals() override will look like in this case?

Was it helpful?

Solution

Of course you don't really want to change the semantics of tuples, because the universe would implode. You just want something like tuples -- except for their equality semantics.

Why not just use a case class? Tuples are really just ready-made classes to hold a few things together when it isn't worth the trouble to define a class. Well, if you want to change equality, I'd say it's worth the trouble! Plus you can use more meaningful names than _1 and _2.

I don't know what those names would be, of course, but here's some code. As Travis points out, messing with equality is tricky, in part because you also have to think about the hash code. Hopefully this is reasonable:

scala> case class Foo[T]( a:T, b:T ) {
  override def equals( that:Any ) = that match {
    case that:Foo[T] => ( that canEqual this ) && (
      this.a == that.a && this.b == that.b ||
      this.a == that.b && this.b == that.a
    )
    case _ => false
  }
  override def canEqual( that:Any ) = that.isInstanceOf[Foo[T]]
  override def hashCode = a.hashCode + b.hashCode
}
defined class Foo

scala> Foo(1,2) == Foo(1,2)
res15: Boolean = true

scala> Foo(1,2) == Foo(2,1)
res16: Boolean = true

scala> Foo(1,2) == Foo(2,2)
res17: Boolean = false

scala> collection.immutable.HashSet( Foo(0,1), Foo(1,0), Foo(1,2) )
res18: scala.collection.immutable.HashSet[Foo[Int]] = Set(Foo(0,1), Foo(1,2))

Note that res18 contains Foo(1,0) but not Foo(0,1) because of that definition of equality.

OTHER TIPS

Well, you can't override the behavior defined for Tuple2. After all, those two tuples are not equal, so you can't tell scala to say that they are.

You could define your own kind of tuple that has the equality property you want, but then you wouldn't be able to use the nice parentheses syntax.

The easiest way is to just define your own equality function that you call:

 def myEquals[X,Y](a: (X,Y), b: (Y,X)) = a._1 == b._2 && a._2 == b._1

So then you have:

 scala> myEquals((1,'a'), ('a',1))
 res0: Boolean = true

You could also define a new equality operator for Tuple2

implicit class NewEqualityTuple2[X,Y](a: (X,Y)) {
  def =<>=(b: (Y,X)) = a._1 == b._2 && a._2 == b._1
}

Giving this:

scala> (1,'a') =<>= ('a',1)
res1: Boolean = true
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top