Scala changed set: strange behavior
I cannot 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 will add an item to my set.
items += Item("name", Set(1, 2, 3))
I will release my inner kit.
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 delete records with an empty inner set.
items.retain(_.content.nonEmpty)
items
// res12: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Does not work. I may have done the opposite test.
items.retain(_.content.isEmpty)
items
// res14: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Does not work. The filter may not be working.
items.filter(_.content.nonEmpty)
// res15: scala.collection.mutable.Set[Item] = Set()
The filter is working properly. Maybe I cannot change it because it is 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? Perhaps they are all different.
items += Item("name", Set.empty)
items
// res19: scala.collection.mutable.Set[Item] = Set(Item(name,Set()), Item(name,Set()))
They are not all different. Can I remove any of them?
items -= Item("name", Set.empty)
items
// res21: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
I can delete ONE. Can I delete another one that I tried to delete from the beginning?
items -= Item("name", Set.empty)
items
// res23: scala.collection.mutable.Set[Item] = Set(Item(name,Set()))
Nope. What's happening? I am very confused.
EDIT, SOLUTION:
Using this Stackoverflow post, Scala: Ignore case class field for equals / hascode? , I solved it by changing the way the case class was declared:
case class Item(name: String)(val content: Set[Int])
So the inner set is ignored for the hashcode and is equal to the estimates, but is still available as a field.
source to share
The hashing Item
changes when changed content
. Since the set created Set(...)
is a hash set, it cannot work correctly if the hashes of its elements change. Note that it doesn't matter if it is Set[Item]
volatile or not; only what content
is mutable.
If you are putting mutable objects in a hashset or using them as hashmap keys, you must make sure that either 1) they are not mutable, and 2) their method hashCode
is stable.
source to share