eje211
eje211

Reputation: 2429

Scala mutable set: strange behavior

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

Answers (1)

Alexey Romanov
Alexey Romanov

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

Related Questions