我有一个scala.collection.immutable.HashSet
,我想从中随机选择一个元素。
我可以用这样的扩展方法解决问题:
implicit class HashSetExtensions[T](h: HashSet[T]) {
def nextRandomElement (): Option[T] = {
val list = h.toList
list match {
case null | Nil => None
case _ => Some (list (Random.nextInt (list.length)))
}
}
}
...但转换为列表会很慢。什么是最有效的解决方案?
答案 0 :(得分:2)
警告此答案仅供实验使用。对于真实项目,您可能应该使用自己的集合类型。
所以我在HashSet source做了一些研究,我认为没有机会在没有包违规的情况下提取最有价值class HashTrieSet
的内部结构。
我确实提出了这段代码,该代码已扩展为Ben Reich's solution:
package scala.collection
import scala.collection.immutable.HashSet
import scala.util.Random
package object random {
implicit class HashSetRandom[T](set: HashSet[T]) {
def randomElem: Option[T] = set match {
case trie: HashSet.HashTrieSet[T] => {
trie.elems(Random.nextInt(trie.elems.length)).randomElem
}
case _ => Some(set.size) collect {
case size if size > 0 => set.iterator.drop(Random.nextInt(size)).next
}
}
}
}
应在src/scala/collection/random
文件夹中的某处创建文件
请注意scala.collection
包 - 这会使elems
HashTrieSet
部分可见。这只是我能想到的解决方案,它可能比O(n)
更好。当前版本的O(ln(n))
任何操作都应具有复杂度immutable.HashSet
。
另一个警告 - HashSet
的私有结构不属于scala的标准库API,因此它可以更改任何版本,使此代码错误(尽管它没有改变自2.8)
答案 1 :(得分:1)
由于size
O(1)
HashSet
iterator
,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
}
}
尽可能懒惰,我认为此解决方案相对有效:
match
如果你想要获得每一分之一的效率,你可以在这里使用Some/collect
而不是这里使用的更简洁的size
习语。
您可以查看mutable HashSet
实现以查看iterator
方法。那里定义的iterator
方法基本上只是在FlatHashTable
上调用toList
。如果您正在使用这些方法,那么这些方法的相同基本效率适用于immutable HashSet
。作为比较,您可以看到HashSet
上的List
实现在TraversableOnce
的类型层次结构中一直向上,并且使用更原始的元素,这些元素可能效率较低(当然)必须迭代整个集合才能生成Traversable
。如果 要将整个集合转换为Array
集合,则应使用具有常量查找的Vector
或HashSet
。
您可能还注意到上述方法中Set[T]
并没有什么特别之处,如果您愿意,可以丰富Set
(尽管不能保证这会是当然,在其他AnyVal
实现上效率很高。
作为旁注,在为扩展方法实现丰富类时,应始终考虑通过扩展os.environ
来创建隐式的,用户定义的值类。您可以在docs和this answer上了解一些优势和限制。