Reputation: 2429
I can't explain this behavior of Scala sets.
Let's start with a few definitions.
import scala.collection.mutable.Set
case class Item(name: String, content: Set[Int])
val items: Set[Item] = Set.empty
I'll add an item to my set.
items += Item("name", Set(1, 2, 3))
I'll empty my inner set.
items.filter(_.name == "name") foreach (_.content -= 1)
items
// res7: scala.collection.mutable.Set[Item] = Set(Item(name,Set(2, 3)))
So far so good.
items.filter(_.name == "name") foreach (_.content -= 2)
items.filter(_.name == "name") foreach (_.content -= 3)
items
// res12: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Perfect! Now, what I REALLY want to do is remove the entries with an empty inner set.
items.retain(_.content.nonEmpty)
items
// res12: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Didn't work. Maybe I did the opposite test.
items.retain(_.content.isEmpty)
items
// res14: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Didn't work either. Maybe the filter doesn't work.
items.filter(_.content.nonEmpty)
// res15: scala.collection.mutable.Set[Item] = Set()
The filter works fine. Maybe I can't change it because it's a val.
items += Item("name", Set.empty)
items
// res17: scala.collection.mutable.Set[Item] = Set(Item(name,Set()), Item(name,Set()))
I CAN change it. And add... more of the same? Maybe they're all different.
items += Item("name", Set.empty)
items
// res19: scala.collection.mutable.Set[Item] = Set(Item(name,Set()), Item(name,Set()))
They're not all different. So can I remove any of them?
items -= Item("name", Set.empty)
items
// res21: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
I can remove ONE. Can I remove the other, the one I've been trying to remove from the start?
items -= Item("name", Set.empty)
items
// res23: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Nope. What's happening? I'm very confused.
EDIT, SOLUTION:
Using this Stackoverflow post, Scala: Ignore case class field for equals/hascode?, I solved this by changing the way I declare the case class:
case class Item(name: String)(val content: Set[Int])
This way, the inner set is disregarded for hashcode and equals evaluations, but still accessible as a field.
Upvotes: 3
Views: 367
Reputation: 170713
Hashcode of Item
changes when you change content
. Since a set created by Set(...)
is a hash set, it can't work correctly if hashes of its elements change. Note that it doesn't matter whether the Set[Item]
is mutable or not; only that content
is mutable.
If you put mutable objects into a hash set or use them as keys of a hash map, you must make sure that either 1) they aren't mutated while there or 2) their hashCode
method is stable.
Upvotes: 6