Since size
is O(1)
on HashSet
, and iterator
is as lazy as possible, I think this solution would be relatively efficient:
implicit class RichHashSet[T](val h: HashSet[T]) extends AnyVal {
def nextRandom: Option[T] = Some(h.size) collect {
case size if size > 0 => h.iterator.drop(Random.nextInt(size)).next
}
}
And if you're trying to get every ounce of efficiency you could use match
here instead of the more concise Some/collect
idiom used here.
You can look at the mutable HashSet
implementation to see the size
method. The iterator
method defined there basically just calls iterator
on FlatHashTable
. The same basic efficiencies of these methods apply to immutable HashSet
if that's what you're working with. As a comparison, you can see the toList
implementation on HashSet
is all the way up the type hierarchy at TraversableOnce
and uses far more primitive elements which are probably less efficient and (of course) the entire collection must be iterated to generate the List
. If you were going to convert the entire set to a Traversable
collection, you should use Array
or Vector
which have constant-time lookup.
You might also note that there is nothing special about HashSet
in the above methods, and you could enrich Set[T]
instead, if you so chose (although there would be no guarantee that this would be as efficient on other Set
implementations, of course).
As a side note, when implementing enriched classes for extension methods, you should always consider making an implicit, user-defined value class by extending AnyVal
. You can read about some of the advantages and limitations in the docs, and on this answer.