如何从Scala不可变HashSet中有效地选择随机元素

时间:2015-04-22 15:32:55

标签: scala scala-collections

我有一个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)))
    }
  }
}

...但转换为列表会很慢。什么是最有效的解决方案?

2 个答案:

答案 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 iteratorimplicit 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集合,则应使用具有常量查找的VectorHashSet

您可能还注意到上述方法中Set[T]并没有什么特别之处,如果您愿意,可以丰富Set(尽管不能保证这会是当然,在其他AnyVal实现上效率很高。

作为旁注,在为扩展方法实现丰富类时,应始终考虑通过扩展os.environ来创建隐式的,用户定义的值类。您可以在docsthis answer上了解一些优势和限制。