Reputation: 603
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?
Upvotes: 1
Views: 3275
Reputation: 6852
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.
Upvotes: 4
Reputation: 52681
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
Upvotes: 1